mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-02 11:56:02 +02:00
authenticate: move impersonate from proxy to authenticate (#965)
This commit is contained in:
parent
99142b7293
commit
8362f18355
6 changed files with 27 additions and 73 deletions
|
@ -78,6 +78,7 @@ func (a *Authenticate) Mount(r *mux.Router) {
|
||||||
v.Path("/").Handler(httputil.HandlerFunc(a.Dashboard))
|
v.Path("/").Handler(httputil.HandlerFunc(a.Dashboard))
|
||||||
v.Path("/sign_in").Handler(httputil.HandlerFunc(a.SignIn))
|
v.Path("/sign_in").Handler(httputil.HandlerFunc(a.SignIn))
|
||||||
v.Path("/sign_out").Handler(httputil.HandlerFunc(a.SignOut))
|
v.Path("/sign_out").Handler(httputil.HandlerFunc(a.SignOut))
|
||||||
|
v.Path("/admin/impersonate").Handler(httputil.HandlerFunc(a.Impersonate)).Methods(http.MethodPost)
|
||||||
|
|
||||||
wk := r.PathPrefix("/.well-known/pomerium").Subrouter()
|
wk := r.PathPrefix("/.well-known/pomerium").Subrouter()
|
||||||
wk.Path("/jwks.json").Handler(httputil.HandlerFunc(a.jwks)).Methods(http.MethodGet)
|
wk.Path("/jwks.json").Handler(httputil.HandlerFunc(a.jwks)).Methods(http.MethodGet)
|
||||||
|
@ -284,6 +285,29 @@ func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Impersonate takes the result of a form and adds user impersonation details
|
||||||
|
// to the user's current user sessions state if the user is currently an
|
||||||
|
// administrative user. Requests are redirected back to the user dashboard.
|
||||||
|
func (a *Authenticate) Impersonate(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
redirectURL := urlutil.GetAbsoluteURL(r).ResolveReference(&url.URL{
|
||||||
|
Path: "/.pomerium",
|
||||||
|
})
|
||||||
|
if u, err := url.Parse(r.FormValue(urlutil.QueryRedirectURI)); err == nil {
|
||||||
|
redirectURL = u
|
||||||
|
}
|
||||||
|
signinURL := urlutil.GetAbsoluteURL(r).ResolveReference(&url.URL{
|
||||||
|
Path: "/.pomerium/sign_in",
|
||||||
|
})
|
||||||
|
q := signinURL.Query()
|
||||||
|
q.Set(urlutil.QueryRedirectURI, redirectURL.String())
|
||||||
|
q.Set(urlutil.QueryImpersonateAction, r.FormValue(urlutil.QueryImpersonateAction))
|
||||||
|
q.Set(urlutil.QueryImpersonateEmail, r.FormValue(urlutil.QueryImpersonateEmail))
|
||||||
|
q.Set(urlutil.QueryImpersonateGroups, r.FormValue(urlutil.QueryImpersonateGroups))
|
||||||
|
signinURL.RawQuery = q.Encode()
|
||||||
|
httputil.Redirect(w, r, urlutil.NewSignedURL(a.sharedKey, signinURL).String(), http.StatusFound)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// reauthenticateOrFail starts the authenticate process by redirecting the
|
// reauthenticateOrFail starts the authenticate process by redirecting the
|
||||||
// user to their respective identity provider. This function also builds the
|
// user to their respective identity provider. This function also builds the
|
||||||
// 'state' parameter which is encrypted and includes authenticating data
|
// 'state' parameter which is encrypted and includes authenticating data
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -181,6 +181,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="POST" action="/.pomerium/admin/impersonate">
|
<form method="POST" action="/.pomerium/admin/impersonate">
|
||||||
|
<input type="hidden" value="{{.RedirectURL}}" name="pomerium_redirect_uri">
|
||||||
<section>
|
<section>
|
||||||
<p class="message">
|
<p class="message">
|
||||||
Administrators can temporarily impersonate another user.
|
Administrators can temporarily impersonate another user.
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -34,9 +34,6 @@ func (p *Proxy) registerDashboardHandlers(r *mux.Router) *mux.Router {
|
||||||
// dashboard endpoints can be used by user's to view, or modify their session
|
// dashboard endpoints can be used by user's to view, or modify their session
|
||||||
h.Path("/").HandlerFunc(p.UserDashboard).Methods(http.MethodGet)
|
h.Path("/").HandlerFunc(p.UserDashboard).Methods(http.MethodGet)
|
||||||
h.Path("/sign_out").HandlerFunc(p.SignOut).Methods(http.MethodGet, http.MethodPost)
|
h.Path("/sign_out").HandlerFunc(p.SignOut).Methods(http.MethodGet, http.MethodPost)
|
||||||
// admin endpoints authorization is also delegated to authorizer service
|
|
||||||
admin := h.PathPrefix("/admin").Subrouter()
|
|
||||||
admin.Path("/impersonate").Handler(httputil.HandlerFunc(p.Impersonate)).Methods(http.MethodPost)
|
|
||||||
|
|
||||||
// Authenticate service callback handlers and middleware
|
// Authenticate service callback handlers and middleware
|
||||||
// callback used to set route-scoped session and redirect back to destination
|
// callback used to set route-scoped session and redirect back to destination
|
||||||
|
@ -100,23 +97,6 @@ func (p *Proxy) UserDashboard(w http.ResponseWriter, r *http.Request) {
|
||||||
httputil.Redirect(w, r, url.String(), http.StatusFound)
|
httputil.Redirect(w, r, url.String(), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Impersonate takes the result of a form and adds user impersonation details
|
|
||||||
// to the user's current user sessions state if the user is currently an
|
|
||||||
// administrative user. Requests are redirected back to the user dashboard.
|
|
||||||
func (p *Proxy) Impersonate(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
redirectURL := urlutil.GetAbsoluteURL(r)
|
|
||||||
redirectURL.Path = dashboardPath // redirect back to the dashboard
|
|
||||||
signinURL := *p.authenticateSigninURL
|
|
||||||
q := signinURL.Query()
|
|
||||||
q.Set(urlutil.QueryRedirectURI, redirectURL.String())
|
|
||||||
q.Set(urlutil.QueryImpersonateAction, r.FormValue(urlutil.QueryImpersonateAction))
|
|
||||||
q.Set(urlutil.QueryImpersonateEmail, r.FormValue(urlutil.QueryImpersonateEmail))
|
|
||||||
q.Set(urlutil.QueryImpersonateGroups, r.FormValue(urlutil.QueryImpersonateGroups))
|
|
||||||
signinURL.RawQuery = q.Encode()
|
|
||||||
httputil.Redirect(w, r, urlutil.NewSignedURL(p.SharedKey, &signinURL).String(), http.StatusFound)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback handles the result of a successful call to the authenticate service
|
// Callback handles the result of a successful call to the authenticate service
|
||||||
// and is responsible setting returned per-route session.
|
// and is responsible setting returned per-route session.
|
||||||
func (p *Proxy) Callback(w http.ResponseWriter, r *http.Request) error {
|
func (p *Proxy) Callback(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
|
@ -66,57 +66,6 @@ func TestProxy_Signout(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxy_Impersonate(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
opts := testOptions(t)
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
malformed bool
|
|
||||||
options config.Options
|
|
||||||
ctxError error
|
|
||||||
method string
|
|
||||||
email string
|
|
||||||
groups string
|
|
||||||
csrf string
|
|
||||||
cipher encoding.MarshalUnmarshaler
|
|
||||||
sessionStore sessions.SessionStore
|
|
||||||
authorizer client.Authorizer
|
|
||||||
wantStatus int
|
|
||||||
}{
|
|
||||||
{"good", false, opts, nil, http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{}}, client.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
|
||||||
{"bad session state", false, opts, errors.New("error"), http.MethodPost, "user@blah.com", "", "", &mock.Encoder{}, &mstore.Store{Session: &sessions.State{}}, client.MockAuthorize{IsAdminResponse: true}, http.StatusFound},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
p, err := New(tt.options)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
p.encoder = tt.cipher
|
|
||||||
p.sessionStore = tt.sessionStore
|
|
||||||
postForm := url.Values{}
|
|
||||||
postForm.Add("email", tt.email)
|
|
||||||
postForm.Add("group", tt.groups)
|
|
||||||
postForm.Set("csrf", tt.csrf)
|
|
||||||
uri := &url.URL{Path: "/"}
|
|
||||||
|
|
||||||
r := httptest.NewRequest(tt.method, uri.String(), bytes.NewBufferString(postForm.Encode()))
|
|
||||||
state, _ := tt.sessionStore.LoadSession(r)
|
|
||||||
ctx := r.Context()
|
|
||||||
ctx = sessions.NewContext(ctx, state, tt.ctxError)
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
|
|
||||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
httputil.HandlerFunc(p.Impersonate).ServeHTTP(w, r)
|
|
||||||
if status := w.Code; status != tt.wantStatus {
|
|
||||||
t.Errorf("status code: got %v want %v", status, tt.wantStatus)
|
|
||||||
t.Errorf("\n%+v", opts)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxy_SignOut(t *testing.T) {
|
func TestProxy_SignOut(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
Loading…
Add table
Reference in a new issue