From 1db00717ee86681f7ddda3687ad838d0d329d8be Mon Sep 17 00:00:00 2001 From: James Elliott Date: Tue, 3 May 2022 15:28:58 +1000 Subject: [PATCH] fix(oidc): pre-conf consent skipped entirely for anon users (#3250) This fixes an issue where pre-configured consent is entirely skipped if the process was initiated via an anonymous user. --- .../handlers/handler_oidc_authorization.go | 2 - .../handler_oidc_authorization_consent.go | 36 +++++++++++----- internal/handlers/response.go | 43 +++++++++++++++++-- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go index a6743c042..9e7470ce9 100644 --- a/internal/handlers/handler_oidc_authorization.go +++ b/internal/handlers/handler_oidc_authorization.go @@ -101,8 +101,6 @@ func OpenIDConnectAuthorizationGET(ctx *middlewares.AutheliaCtx, rw http.Respons ctx.Logger.Tracef("Authorization Request with id '%s' on client with id '%s' creating session for Authorization Response for subject '%s' with username '%s' with claims: %+v", requester.GetID(), oidcSession.ClientID, oidcSession.Subject, oidcSession.Username, oidcSession.Claims) - ctx.Logger.Tracef("Authorization Request with id '%s' on client with id '%s' creating session for Authorization Response for subject '%s' with username '%s' with headers: %+v", - requester.GetID(), oidcSession.ClientID, oidcSession.Subject, oidcSession.Username, oidcSession.Headers) if responder, err = ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeResponse(ctx, requester, oidcSession); err != nil { rfc := fosite.ErrorToRFC6749Error(err) diff --git a/internal/handlers/handler_oidc_authorization_consent.go b/internal/handlers/handler_oidc_authorization_consent.go index 0230d4ec9..eb7579906 100644 --- a/internal/handlers/handler_oidc_authorization_consent.go +++ b/internal/handlers/handler_oidc_authorization_consent.go @@ -117,9 +117,9 @@ func handleOIDCAuthorizationConsentOrGenerate(ctx *middlewares.AutheliaCtx, root err error ) - scopes, audience := getExpectedScopesAndAudience(requester) + scopes, audience := getOIDCExpectedScopesAndAudienceFromRequest(requester) - if consent, err = getOIDCPreconfiguredConsent(ctx, client.GetID(), subject.UUID, scopes, audience); err != nil { + if consent, err = getOIDCPreConfiguredConsent(ctx, client.GetID(), subject.UUID, scopes, audience); err != nil { ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' had error looking up pre-configured consent sessions: %+v", requester.GetID(), requester.GetClient().GetID(), err) ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not lookup the consent session.")) @@ -182,16 +182,32 @@ func handleOIDCAuthorizationConsentRedirect(destination string, client *oidc.Cli http.Redirect(rw, r, destination, http.StatusFound) } -func getExpectedScopesAndAudience(requester fosite.Requester) (scopes, audience []string) { - audience = requester.GetRequestedAudience() - if !utils.IsStringInSlice(requester.GetClient().GetID(), audience) { - audience = append(audience, requester.GetClient().GetID()) - } - - return requester.GetRequestedScopes(), audience +func getOIDCExpectedScopesAndAudienceFromRequest(requester fosite.Requester) (scopes, audience []string) { + return getOIDCExpectedScopesAndAudience(requester.GetClient().GetID(), requester.GetRequestedScopes(), requester.GetRequestedAudience()) } -func getOIDCPreconfiguredConsent(ctx *middlewares.AutheliaCtx, clientID string, subject uuid.UUID, scopes, audience []string) (consent *model.OAuth2ConsentSession, err error) { +func getOIDCExpectedScopesAndAudience(clientID string, scopes, audience []string) (expectedScopes, expectedAudience []string) { + if !utils.IsStringInSlice(clientID, audience) { + audience = append(audience, clientID) + } + + return scopes, audience +} + +func getOIDCPreConfiguredConsentFromClientAndConsent(ctx *middlewares.AutheliaCtx, client fosite.Client, consent *model.OAuth2ConsentSession) (preConfigConsent *model.OAuth2ConsentSession, err error) { + if consent == nil || !consent.Subject.Valid { + return nil, fmt.Errorf("invalid consent provided for pre-configured consent lookup") + } + + scopes, audience := getOIDCExpectedScopesAndAudience(client.GetID(), consent.RequestedScopes, consent.RequestedAudience) + + // We can skip this error as it's handled at the authorization endpoint. + preConfigConsent, _ = getOIDCPreConfiguredConsent(ctx, client.GetID(), consent.Subject.UUID, scopes, audience) + + return preConfigConsent, nil +} + +func getOIDCPreConfiguredConsent(ctx *middlewares.AutheliaCtx, clientID string, subject uuid.UUID, scopes, audience []string) (consent *model.OAuth2ConsentSession, err error) { var ( rows *storage.ConsentSessionRows ) diff --git a/internal/handlers/response.go b/internal/handlers/response.go index a9f3bb74c..f7bd919a2 100644 --- a/internal/handlers/response.go +++ b/internal/handlers/response.go @@ -9,6 +9,7 @@ import ( "github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/middlewares" + "github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/oidc" "github.com/authelia/authelia/v4/internal/utils" ) @@ -59,15 +60,49 @@ func handleOIDCWorkflowResponse(ctx *middlewares.AutheliaCtx) { return } - if userSession.ConsentChallengeID != nil { + if consent.Subject.UUID, err = ctx.Providers.OpenIDConnect.Store.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil { + ctx.Logger.Errorf("Unable to find subject for the consent session: %v", err) + + respondUnauthorized(ctx, messageOperationFailed) + + return + } + + consent.Subject.Valid = true + + var preConsent *model.OAuth2ConsentSession + + if preConsent, err = getOIDCPreConfiguredConsentFromClientAndConsent(ctx, client, consent); err != nil { + ctx.Logger.Errorf("Unable to lookup pre-configured consent for the consent session: %v", err) + + respondUnauthorized(ctx, messageOperationFailed) + + return + } + + if userSession.ConsentChallengeID != nil && preConsent == nil { if err = ctx.SetJSONBody(redirectResponse{Redirect: fmt.Sprintf("%s/consent", externalRootURL)}); err != nil { ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err) } - } else { - if err = ctx.SetJSONBody(redirectResponse{Redirect: fmt.Sprintf("%s%s?%s", externalRootURL, oidc.AuthorizationPath, consent.Form)}); err != nil { - ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err) + + return + } + + if userSession.ConsentChallengeID != nil { + userSession.ConsentChallengeID = nil + + if err = ctx.SaveSession(userSession); err != nil { + ctx.Logger.Errorf("Unable to update user session: %v", err) + + respondUnauthorized(ctx, messageOperationFailed) + + return } } + + if err = ctx.SetJSONBody(redirectResponse{Redirect: fmt.Sprintf("%s%s?%s", externalRootURL, oidc.AuthorizationPath, consent.Form)}); err != nil { + ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err) + } } // Handle1FAResponse handle the redirection upon 1FA authentication.