mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-22 05:27:13 +02:00
support both stateful and stateless authenticate (#4765)
Update the initialization logic for the authenticate, authorize, and proxy services to automatically select between the stateful authentication flow and the stateless authentication flow, depending on whether Pomerium is configured to use the hosted authenticate service. Add a unit test case to verify that the sign_out handler does not trigger a sign in redirect.
This commit is contained in:
parent
b9c56074aa
commit
5ccd7a520a
7 changed files with 86 additions and 12 deletions
|
@ -84,6 +84,7 @@ func (a *Authenticate) mountDashboard(r *mux.Router) {
|
|||
AllowedHeaders: []string{"*"},
|
||||
})
|
||||
sr.Use(c.Handler)
|
||||
sr.Use(a.RetrieveSession)
|
||||
|
||||
// routes that don't need a session:
|
||||
sr.Path("/sign_out").Handler(httputil.HandlerFunc(a.SignOut))
|
||||
|
@ -91,7 +92,6 @@ func (a *Authenticate) mountDashboard(r *mux.Router) {
|
|||
|
||||
// routes that need a session:
|
||||
sr = sr.NewRoute().Subrouter()
|
||||
sr.Use(a.RetrieveSession)
|
||||
sr.Use(a.VerifySession)
|
||||
sr.Path("/").Handler(a.requireValidSignatureOnRedirect(a.userInfo))
|
||||
sr.Path("/sign_in").Handler(httputil.HandlerFunc(a.SignIn))
|
||||
|
@ -475,7 +475,9 @@ func (a *Authenticate) revokeSession(ctx context.Context, w http.ResponseWriter,
|
|||
return ""
|
||||
}
|
||||
|
||||
return state.flow.RevokeSession(ctx, r, authenticator, nil)
|
||||
sessionState, _ := a.getSessionFromCtx(ctx)
|
||||
|
||||
return state.flow.RevokeSession(ctx, r, authenticator, sessionState)
|
||||
}
|
||||
|
||||
// Callback handles the result of a successful call to the authenticate service
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
@ -248,6 +249,40 @@ func TestAuthenticate_SignOut(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAuthenticate_SignOutDoesNotRequireSession(t *testing.T) {
|
||||
// A direct sign_out request would not be signed.
|
||||
f := new(stubFlow)
|
||||
f.verifySignatureErr = errors.New("no signature")
|
||||
|
||||
sessionStore := &mstore.Store{LoadError: errors.New("no session")}
|
||||
a := &Authenticate{
|
||||
cfg: getAuthenticateConfig(WithGetIdentityProvider(func(options *config.Options, idpID string) (identity.Authenticator, error) {
|
||||
return identity.MockProvider{}, nil
|
||||
})),
|
||||
state: atomicutil.NewValue(&authenticateState{
|
||||
cookieSecret: cryptutil.NewKey(),
|
||||
sessionLoader: sessionStore,
|
||||
sessionStore: sessionStore,
|
||||
sharedEncoder: mock.Encoder{},
|
||||
flow: f,
|
||||
}),
|
||||
options: config.NewAtomicOptions(),
|
||||
}
|
||||
r := httptest.NewRequest(http.MethodGet, "/.pomerium/sign_out", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
a.Handler().ServeHTTP(w, r)
|
||||
result := w.Result()
|
||||
|
||||
// The handler should serve a sign out confirmation page, not a login redirect.
|
||||
expectedStatus := "200 OK"
|
||||
if result.Status != expectedStatus {
|
||||
t.Fatalf("wrong status code: got %q want %q", result.Status, expectedStatus)
|
||||
}
|
||||
body, _ := io.ReadAll(result.Body)
|
||||
assert.Contains(t, string(body), `"page":"SignOutConfirm"`)
|
||||
}
|
||||
|
||||
func TestAuthenticate_OAuthCallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -144,13 +144,17 @@ func newAuthenticateStateFromConfig(
|
|||
}
|
||||
}
|
||||
|
||||
state.flow, err = authenticateflow.NewStateless(
|
||||
cfg,
|
||||
cookieStore,
|
||||
authenticateConfig.getIdentityProvider,
|
||||
authenticateConfig.profileTrimFn,
|
||||
authenticateConfig.authEventFn,
|
||||
)
|
||||
if cfg.Options.UseStatelessAuthenticateFlow() {
|
||||
state.flow, err = authenticateflow.NewStateless(
|
||||
cfg,
|
||||
cookieStore,
|
||||
authenticateConfig.getIdentityProvider,
|
||||
authenticateConfig.profileTrimFn,
|
||||
authenticateConfig.authEventFn,
|
||||
)
|
||||
} else {
|
||||
state.flow, err = authenticateflow.NewStateful(cfg, cookieStore)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -83,7 +83,11 @@ func newAuthorizeStateFromConfig(
|
|||
return nil, fmt.Errorf("authorize: invalid session store: %w", err)
|
||||
}
|
||||
|
||||
state.authenticateFlow, err = authenticateflow.NewStateless(cfg, nil, nil, nil, nil)
|
||||
if cfg.Options.UseStatelessAuthenticateFlow() {
|
||||
state.authenticateFlow, err = authenticateflow.NewStateless(cfg, nil, nil, nil, nil)
|
||||
} else {
|
||||
state.authenticateFlow, err = authenticateflow.NewStateful(cfg, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -827,6 +827,16 @@ func (o *Options) GetInternalAuthenticateURL() (*url.URL, error) {
|
|||
return urlutil.ParseAndValidateURL(o.AuthenticateInternalURLString)
|
||||
}
|
||||
|
||||
// UseStatelessAuthenticateFlow returns true if the stateless authentication
|
||||
// flow should be used (i.e. for hosted authenticate).
|
||||
func (o *Options) UseStatelessAuthenticateFlow() bool {
|
||||
u, err := o.GetInternalAuthenticateURL()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return urlutil.IsHostedAuthenticateDomain(u.Hostname())
|
||||
}
|
||||
|
||||
// GetAuthorizeURLs returns the AuthorizeURLs in the options or 127.0.0.1:5443.
|
||||
func (o *Options) GetAuthorizeURLs() ([]*url.URL, error) {
|
||||
if IsAll(o.Services) && o.AuthorizeURLString == "" && len(o.AuthorizeURLStrings) == 0 {
|
||||
|
|
|
@ -856,6 +856,21 @@ func TestOptions_DefaultURL(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOptions_UseStatelessAuthenticateFlow(t *testing.T) {
|
||||
t.Run("enabled by default", func(t *testing.T) {
|
||||
options := &Options{}
|
||||
assert.True(t, options.UseStatelessAuthenticateFlow())
|
||||
})
|
||||
t.Run("enabled explicitly", func(t *testing.T) {
|
||||
options := &Options{AuthenticateURLString: "https://authenticate.pomerium.app"}
|
||||
assert.True(t, options.UseStatelessAuthenticateFlow())
|
||||
})
|
||||
t.Run("disabled", func(t *testing.T) {
|
||||
options := &Options{AuthenticateURLString: "https://authenticate.example.com"}
|
||||
assert.False(t, options.UseStatelessAuthenticateFlow())
|
||||
})
|
||||
}
|
||||
|
||||
func TestOptions_GetOauthOptions(t *testing.T) {
|
||||
opts := &Options{AuthenticateURLString: "https://authenticate.example.com"}
|
||||
oauthOptions, err := opts.GetOauthOptions()
|
||||
|
|
|
@ -114,8 +114,12 @@ func newProxyStateFromConfig(cfg *config.Config) (*proxyState, error) {
|
|||
|
||||
state.programmaticRedirectDomainWhitelist = cfg.Options.ProgrammaticRedirectDomainWhitelist
|
||||
|
||||
state.authenticateFlow, err = authenticateflow.NewStateless(
|
||||
cfg, state.sessionStore, nil, nil, nil)
|
||||
if cfg.Options.UseStatelessAuthenticateFlow() {
|
||||
state.authenticateFlow, err = authenticateflow.NewStateless(
|
||||
cfg, state.sessionStore, nil, nil, nil)
|
||||
} else {
|
||||
state.authenticateFlow, err = authenticateflow.NewStateful(cfg, state.sessionStore)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue