From 9b779569f492c16e1fcd23adc3c9cb0a38a97059 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Wed, 16 Mar 2022 09:55:38 +1100 Subject: [PATCH] fix(oidc): add detailed trace/debug logs (#3012) This adds significantly more detailed logging for most OpenID Connect handlers. --- .../handlers/handler_oidc_authorization.go | 149 +++++++++--------- .../handlers/handler_oidc_introspection.go | 23 ++- internal/handlers/handler_oidc_jwks.go | 2 +- internal/handlers/handler_oidc_revocation.go | 10 +- internal/handlers/handler_oidc_token.go | 47 ++++-- internal/handlers/handler_oidc_userinfo.go | 75 ++++++--- internal/handlers/oidc.go | 13 -- internal/oidc/types.go | 49 ++++++ internal/oidc/types_test.go | 81 ++++++++++ 9 files changed, 322 insertions(+), 127 deletions(-) create mode 100644 internal/oidc/types_test.go diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go index 395969e89..dfaa3ffb3 100644 --- a/internal/handlers/handler_oidc_authorization.go +++ b/internal/handlers/handler_oidc_authorization.go @@ -1,142 +1,145 @@ package handlers import ( + "errors" "fmt" "net/http" "strings" "time" "github.com/ory/fosite" - "github.com/ory/fosite/handler/openid" - "github.com/ory/fosite/token/jwt" - "github.com/authelia/authelia/v4/internal/logging" "github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/oidc" "github.com/authelia/authelia/v4/internal/session" ) func oidcAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) { - ar, err := ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeRequest(ctx, r) - if err != nil { - logging.Logger().Errorf("Error occurred in NewAuthorizeRequest: %+v", err) - ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err) + var ( + requester fosite.AuthorizeRequester + responder fosite.AuthorizeResponder + client *oidc.InternalClient + authTime time.Time + issuer string + err error + ) + + if requester, err = ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeRequest(ctx, r); err != nil { + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("Authorization Request failed with error: %+v", rfc) + + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, err) return } - clientID := ar.GetClient().GetID() - client, err := ctx.Providers.OpenIDConnect.Store.GetInternalClient(clientID) + clientID := requester.GetClient().GetID() - if err != nil { - err := fmt.Errorf("unable to find related client configuration with name '%s': %v", ar.GetID(), err) - ctx.Logger.Error(err) - ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err) + ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' is being processed", requester.GetID(), clientID) + + if client, err = ctx.Providers.OpenIDConnect.Store.GetInternalClient(clientID); err != nil { + if errors.Is(err, fosite.ErrNotFound) { + ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: client was not found", requester.GetID(), clientID) + } else { + ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: failed to find client: %+v", requester.GetID(), clientID, err) + } + + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, err) + + return + } + + if issuer, err = ctx.ExternalRootURL(); err != nil { + ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred determining issuer: %+v", requester.GetID(), clientID, err) + + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not determine issuer.")) return } userSession := ctx.GetSession() - requestedScopes := ar.GetRequestedScopes() - requestedAudience := ar.GetRequestedAudience() + requestedScopes := requester.GetRequestedScopes() + requestedAudience := requester.GetRequestedAudience() isAuthInsufficient := !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) if isAuthInsufficient || (isConsentMissing(userSession.OIDCWorkflowSession, requestedScopes, requestedAudience)) { - oidcAuthorizeHandleAuthorizationOrConsentInsufficient(ctx, userSession, client, isAuthInsufficient, rw, r, ar) + oidcAuthorizeHandleAuthorizationOrConsentInsufficient(ctx, userSession, client, isAuthInsufficient, rw, r, requester, issuer) return } - extraClaims := oidcGrantRequests(ar, requestedScopes, requestedAudience, &userSession) + extraClaims := oidcGrantRequests(requester, requestedScopes, requestedAudience, &userSession) workflowCreated := time.Unix(userSession.OIDCWorkflowSession.CreatedTimestamp, 0) userSession.OIDCWorkflowSession = nil - if err := ctx.SaveSession(userSession); err != nil { - ctx.Logger.Error(err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + + if err = ctx.SaveSession(userSession); err != nil { + ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred saving session: %+v", requester.GetID(), client.GetID(), err) + + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not save the session.")) return } - issuer, err := ctx.ExternalRootURL() - if err != nil { - ctx.Logger.Errorf("Error occurred obtaining issuer: %+v", err) - ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err) + if authTime, err = userSession.AuthenticatedTime(client.Policy); err != nil { + ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred checking authentication time: %+v", requester.GetID(), client.GetID(), err) + + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not obtain the authentication time.")) return } - authTime, err := userSession.AuthenticatedTime(client.Policy) - if err != nil { - ctx.Logger.Errorf("Error occurred obtaining authentication timestamp: %+v", err) - ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err) + ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' was successfully processed, proceeding to build Authorization Response", requester.GetID(), clientID) + + subject := userSession.Username + oidcSession := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(), + subject, userSession.Username, extraClaims, authTime, workflowCreated, requester) + + 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) + + ctx.Logger.Errorf("Authorization Response for Request with id '%s' on client with id '%s' could not be created: %+v", requester.GetID(), clientID, rfc) + + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, err) return } - response, err := ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeResponse(ctx, ar, &oidc.OpenIDSession{ - DefaultSession: &openid.DefaultSession{ - Claims: &jwt.IDTokenClaims{ - Subject: userSession.Username, - Issuer: issuer, - AuthTime: authTime, - RequestedAt: workflowCreated, - IssuedAt: time.Now(), - Nonce: ar.GetRequestForm().Get("nonce"), - Audience: ar.GetGrantedAudience(), - Extra: extraClaims, - }, - Headers: &jwt.Headers{Extra: map[string]interface{}{ - "kid": ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(), - }}, - Subject: userSession.Username, - Username: userSession.Username, - }, - ClientID: clientID, - }) - if err != nil { - ctx.Logger.Errorf("Error occurred in NewAuthorizeResponse: %+v", err) - ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err) - - return - } - - ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeResponse(rw, ar, response) + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeResponse(rw, requester, responder) } func oidcAuthorizeHandleAuthorizationOrConsentInsufficient( ctx *middlewares.AutheliaCtx, userSession session.UserSession, client *oidc.InternalClient, isAuthInsufficient bool, rw http.ResponseWriter, r *http.Request, - ar fosite.AuthorizeRequester) { - issuer, err := ctx.ExternalRootURL() - if err != nil { - ctx.Logger.Error(err) - http.Error(rw, err.Error(), http.StatusBadRequest) - - return - } - + requester fosite.AuthorizeRequester, issuer string) { redirectURL := fmt.Sprintf("%s%s", issuer, string(ctx.Request.RequestURI())) - ctx.Logger.Debugf("User %s must consent with scopes %s", - userSession.Username, strings.Join(ar.GetRequestedScopes(), ", ")) + ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' requires user '%s' provides consent for scopes '%s'", + requester.GetID(), client.GetID(), userSession.Username, strings.Join(requester.GetRequestedScopes(), "', '")) userSession.OIDCWorkflowSession = &session.OIDCWorkflowSession{ - ClientID: client.ID, - RequestedScopes: ar.GetRequestedScopes(), - RequestedAudience: ar.GetRequestedAudience(), + ClientID: client.GetID(), + RequestedScopes: requester.GetRequestedScopes(), + RequestedAudience: requester.GetRequestedAudience(), AuthURI: redirectURL, - TargetURI: ar.GetRedirectURI().String(), + TargetURI: requester.GetRedirectURI().String(), RequiredAuthorizationLevel: client.Policy, CreatedTimestamp: time.Now().Unix(), } if err := ctx.SaveSession(userSession); err != nil { - ctx.Logger.Errorf("Unable to save session: %v", err) - http.Error(rw, err.Error(), http.StatusInternalServerError) + ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred saving session for consent: %+v", requester.GetID(), client.GetID(), err) + + ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not save the session.")) return } diff --git a/internal/handlers/handler_oidc_introspection.go b/internal/handlers/handler_oidc_introspection.go index 227eda170..ddc898103 100644 --- a/internal/handlers/handler_oidc_introspection.go +++ b/internal/handlers/handler_oidc_introspection.go @@ -3,20 +3,33 @@ package handlers import ( "net/http" + "github.com/ory/fosite" + "github.com/authelia/authelia/v4/internal/middlewares" + "github.com/authelia/authelia/v4/internal/oidc" ) func oidcIntrospection(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) { - oidcSession := newOpenIDSession("") + var ( + responder fosite.IntrospectionResponder + err error + ) - ir, err := ctx.Providers.OpenIDConnect.Fosite.NewIntrospectionRequest(ctx, req, oidcSession) + oidcSession := oidc.NewSession() + + if responder, err = ctx.Providers.OpenIDConnect.Fosite.NewIntrospectionRequest(ctx, req, oidcSession); err != nil { + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("Introspection Request failed with error: %+v", rfc) - if err != nil { - ctx.Logger.Errorf("Error occurred in NewIntrospectionRequest: %+v", err) ctx.Providers.OpenIDConnect.Fosite.WriteIntrospectionError(rw, err) return } - ctx.Providers.OpenIDConnect.Fosite.WriteIntrospectionResponse(rw, ir) + requester := responder.GetAccessRequester() + + ctx.Logger.Tracef("Introspection Request yeilded a %s (active: %t) requested at %s created with request id '%s' on client with id '%s'", responder.GetTokenUse(), responder.IsActive(), requester.GetRequestedAt().String(), requester.GetID(), requester.GetClient().GetID()) + + ctx.Providers.OpenIDConnect.Fosite.WriteIntrospectionResponse(rw, responder) } diff --git a/internal/handlers/handler_oidc_jwks.go b/internal/handlers/handler_oidc_jwks.go index 2ba875681..37e926345 100644 --- a/internal/handlers/handler_oidc_jwks.go +++ b/internal/handlers/handler_oidc_jwks.go @@ -10,6 +10,6 @@ func oidcJWKs(ctx *middlewares.AutheliaCtx) { ctx.SetContentType("application/json") if err := json.NewEncoder(ctx).Encode(ctx.Providers.OpenIDConnect.KeyManager.GetKeySet()); err != nil { - ctx.Error(err, "failed to serve jwk set") + ctx.Error(err, "failed to serve json web key set") } } diff --git a/internal/handlers/handler_oidc_revocation.go b/internal/handlers/handler_oidc_revocation.go index c4021bdcf..84b4700cf 100644 --- a/internal/handlers/handler_oidc_revocation.go +++ b/internal/handlers/handler_oidc_revocation.go @@ -3,11 +3,19 @@ package handlers import ( "net/http" + "github.com/ory/fosite" + "github.com/authelia/authelia/v4/internal/middlewares" ) func oidcRevocation(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) { - err := ctx.Providers.OpenIDConnect.Fosite.NewRevocationRequest(ctx, req) + var err error + + if err = ctx.Providers.OpenIDConnect.Fosite.NewRevocationRequest(ctx, req); err != nil { + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("Revocation Request failed with error: %+v", rfc) + } ctx.Providers.OpenIDConnect.Fosite.WriteRevocationResponse(rw, err) } diff --git a/internal/handlers/handler_oidc_token.go b/internal/handlers/handler_oidc_token.go index 2ad2efdb4..714fcb555 100644 --- a/internal/handlers/handler_oidc_token.go +++ b/internal/handlers/handler_oidc_token.go @@ -6,35 +6,54 @@ import ( "github.com/ory/fosite" "github.com/authelia/authelia/v4/internal/middlewares" + "github.com/authelia/authelia/v4/internal/oidc" ) func oidcToken(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) { - oidcSession := newOpenIDSession("") + var ( + requester fosite.AccessRequester + responder fosite.AccessResponder + err error + ) - accessRequest, err := ctx.Providers.OpenIDConnect.Fosite.NewAccessRequest(ctx, req, oidcSession) - if err != nil { - ctx.Logger.Errorf("Error occurred in NewAccessRequest: %+v", err) - ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, accessRequest, err) + oidcSession := oidc.NewSession() + + if requester, err = ctx.Providers.OpenIDConnect.Fosite.NewAccessRequest(ctx, req, oidcSession); err != nil { + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("Access Request failed with error: %+v", rfc) + + ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, requester, err) return } + client := requester.GetClient() + + ctx.Logger.Debugf("Access Request with id '%s' on client with id '%s' is being processed", requester.GetID(), client.GetID()) + // If this is a client_credentials grant, grant all scopes the client is allowed to perform. - if accessRequest.GetGrantTypes().ExactOne("client_credentials") { - for _, scope := range accessRequest.GetRequestedScopes() { - if fosite.HierarchicScopeStrategy(accessRequest.GetClient().GetScopes(), scope) { - accessRequest.GrantScope(scope) + if requester.GetGrantTypes().ExactOne("client_credentials") { + for _, scope := range requester.GetRequestedScopes() { + if fosite.HierarchicScopeStrategy(client.GetScopes(), scope) { + requester.GrantScope(scope) } } } - response, err := ctx.Providers.OpenIDConnect.Fosite.NewAccessResponse(ctx, accessRequest) - if err != nil { - ctx.Logger.Errorf("Error occurred in NewAccessResponse: %+v", err) - ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, accessRequest, err) + if responder, err = ctx.Providers.OpenIDConnect.Fosite.NewAccessResponse(ctx, requester); err != nil { + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("Access Response for Request with id '%s' failed to be created with error: %+v", requester.GetID(), rfc) + + ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, requester, err) return } - ctx.Providers.OpenIDConnect.Fosite.WriteAccessResponse(rw, accessRequest, response) + ctx.Logger.Debugf("Access Request with id '%s' on client with id '%s' has successfully been processed", requester.GetID(), client.GetID()) + + ctx.Logger.Tracef("Access Request with id '%s' on client with id '%s' produced the following claims: %+v", requester.GetID(), client.GetID(), responder.ToMap()) + + ctx.Providers.OpenIDConnect.Fosite.WriteAccessResponse(rw, requester, responder) } diff --git a/internal/handlers/handler_oidc_userinfo.go b/internal/handlers/handler_oidc_userinfo.go index 84a8a0470..070d5ea8f 100644 --- a/internal/handlers/handler_oidc_userinfo.go +++ b/internal/handlers/handler_oidc_userinfo.go @@ -15,13 +15,23 @@ import ( ) func oidcUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) { - session := newOpenIDSession("") + var ( + tokenType fosite.TokenType + requester fosite.AccessRequester + client *oidc.InternalClient + err error + ) - tokenType, ar, err := ctx.Providers.OpenIDConnect.Fosite.IntrospectToken(req.Context(), fosite.AccessTokenFromRequest(req), fosite.AccessToken, session) - if err != nil { + oidcSession := oidc.NewSession() + + if tokenType, requester, err = ctx.Providers.OpenIDConnect.Fosite.IntrospectToken( + req.Context(), fosite.AccessTokenFromRequest(req), fosite.AccessToken, oidcSession); err != nil { rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("UserInfo Request failed with error: %+v", rfc) + if rfc.StatusCode() == http.StatusUnauthorized { - rw.Header().Set("WWW-Authenticate", fmt.Sprintf("error=%s,error_description=%s", rfc.ErrorField, rfc.GetDescription())) + rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer error="%s",error_description="%s"`, rfc.ErrorField, rfc.GetDescription())) } ctx.Providers.OpenIDConnect.WriteError(rw, req, err) @@ -29,22 +39,25 @@ func oidcUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *htt return } + clientID := requester.GetClient().GetID() + if tokenType != fosite.AccessToken { - errStr := "authorization header must contain an OAuth access token." - rw.Header().Set("WWW-Authenticate", fmt.Sprintf("error_description=\"%s\"", errStr)) + ctx.Logger.Errorf("UserInfo Request with id '%s' on client with id '%s' failed with error: bearer authorization failed as the token is not an access_token", requester.GetID(), client.GetID()) + + errStr := "Only access tokens are allowed in the authorization header." + rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer error="invalid_token",error_description="%s"`, errStr)) ctx.Providers.OpenIDConnect.WriteErrorCode(rw, req, http.StatusUnauthorized, errors.New(errStr)) return } - client, ok := ar.GetClient().(*oidc.InternalClient) - if !ok { + if client, err = ctx.Providers.OpenIDConnect.Store.GetInternalClient(clientID); err != nil { ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHint("Unable to assert type of client"))) return } - claims := ar.GetSession().(*oidc.OpenIDSession).IDTokenClaims().ToMap() + claims := requester.GetSession().(*oidc.OpenIDSession).IDTokenClaims().ToMap() delete(claims, "jti") delete(claims, "sid") delete(claims, "at_hash") @@ -52,27 +65,49 @@ func oidcUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *htt delete(claims, "exp") delete(claims, "nonce") - if audience, ok := claims["aud"].([]string); !ok || len(audience) == 0 { - claims["aud"] = []string{client.GetID()} + audience, ok := claims["aud"].([]string) + + if !ok || len(audience) == 0 { + audience = []string{client.GetID()} + } else { + found := false + + for _, aud := range audience { + if aud == clientID { + found = true + break + } + } + + if found { + audience = append(audience, clientID) + } } + claims["aud"] = audience + + var ( + keyID, token string + ) + + ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims) + switch client.UserinfoSigningAlgorithm { case "RS256": claims["jti"] = uuid.New() claims["iat"] = time.Now().Unix() - keyID, err := ctx.Providers.OpenIDConnect.KeyManager.Strategy().GetPublicKeyID(req.Context()) - if err != nil { - ctx.Providers.OpenIDConnect.WriteError(rw, req, err) + if keyID, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().GetPublicKeyID(req.Context()); err != nil { + ctx.Providers.OpenIDConnect.WriteError(rw, req, fosite.ErrServerError.WithHintf("Could not find the active JWK.")) return } - token, _, err := ctx.Providers.OpenIDConnect.KeyManager.Strategy().Generate(req.Context(), claims, - &jwt.Headers{ - Extra: map[string]interface{}{"kid": keyID}, - }) - if err != nil { + headers := &jwt.Headers{ + Extra: map[string]interface{}{"kid": keyID}, + } + + if token, _, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().Generate(req.Context(), claims, headers); err != nil { ctx.Providers.OpenIDConnect.WriteError(rw, req, err) return @@ -83,6 +118,6 @@ func oidcUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *htt case "none", "": ctx.Providers.OpenIDConnect.Write(rw, req, claims) default: - ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported userinfo signing algorithm '%s'.", client.UserinfoSigningAlgorithm))) + ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.UserinfoSigningAlgorithm))) } } diff --git a/internal/handlers/oidc.go b/internal/handlers/oidc.go index 400865053..6e4614081 100644 --- a/internal/handlers/oidc.go +++ b/internal/handlers/oidc.go @@ -2,8 +2,6 @@ package handlers import ( "github.com/ory/fosite" - "github.com/ory/fosite/handler/openid" - "github.com/ory/fosite/token/jwt" "github.com/authelia/authelia/v4/internal/oidc" "github.com/authelia/authelia/v4/internal/session" @@ -21,17 +19,6 @@ func isConsentMissing(workflow *session.OIDCWorkflowSession, requestedScopes, re len(requestedAudience) > 0 && utils.IsStringSlicesDifferentFold(requestedAudience, workflow.GrantedAudience) } -func newOpenIDSession(subject string) *oidc.OpenIDSession { - return &oidc.OpenIDSession{ - DefaultSession: &openid.DefaultSession{ - Claims: new(jwt.IDTokenClaims), - Headers: new(jwt.Headers), - Subject: subject, - }, - Extra: map[string]interface{}{}, - } -} - func oidcGrantRequests(ar fosite.AuthorizeRequester, scopes, audiences []string, userSession *session.UserSession) (extraClaims map[string]interface{}) { extraClaims = map[string]interface{}{} diff --git a/internal/oidc/types.go b/internal/oidc/types.go index 007561789..ab97c53e0 100644 --- a/internal/oidc/types.go +++ b/internal/oidc/types.go @@ -2,16 +2,65 @@ package oidc import ( "crypto/rsa" + "time" "github.com/ory/fosite" "github.com/ory/fosite/handler/openid" "github.com/ory/fosite/storage" + "github.com/ory/fosite/token/jwt" "github.com/ory/herodot" "gopkg.in/square/go-jose.v2" "github.com/authelia/authelia/v4/internal/authorization" ) +// NewSession creates a new OpenIDSession struct. +func NewSession() (session *OpenIDSession) { + return &OpenIDSession{ + DefaultSession: &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + Extra: map[string]interface{}{}, + }, + Headers: &jwt.Headers{ + Extra: map[string]interface{}{}, + }, + }, + Extra: map[string]interface{}{}, + } +} + +// NewSessionWithAuthorizeRequest uses details from an AuthorizeRequester to generate an OpenIDSession. +func NewSessionWithAuthorizeRequest(issuer, kid, subject, username string, extra map[string]interface{}, + authTime, requestedAt time.Time, requester fosite.AuthorizeRequester) (session *OpenIDSession) { + if extra == nil { + extra = make(map[string]interface{}) + } + + return &OpenIDSession{ + DefaultSession: &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + Subject: subject, + Issuer: issuer, + AuthTime: authTime, + RequestedAt: requestedAt, + IssuedAt: time.Now(), + Nonce: requester.GetRequestForm().Get("nonce"), + Audience: requester.GetGrantedAudience(), + Extra: extra, + }, + Headers: &jwt.Headers{ + Extra: map[string]interface{}{ + "kid": kid, + }, + }, + Subject: subject, + Username: username, + }, + Extra: map[string]interface{}{}, + ClientID: requester.GetClient().GetID(), + } +} + // OpenIDConnectProvider for OpenID Connect. type OpenIDConnectProvider struct { Fosite fosite.OAuth2Provider diff --git a/internal/oidc/types_test.go b/internal/oidc/types_test.go new file mode 100644 index 000000000..e69d570a7 --- /dev/null +++ b/internal/oidc/types_test.go @@ -0,0 +1,81 @@ +package oidc + +import ( + "net/url" + "testing" + "time" + + "github.com/google/uuid" + "github.com/ory/fosite" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewSession(t *testing.T) { + session := NewSession() + + require.NotNil(t, session) + + assert.Equal(t, "", session.ClientID) + assert.Equal(t, "", session.Username) + assert.Equal(t, "", session.Subject) + require.NotNil(t, session.Claims) + assert.NotNil(t, session.Claims.Extra) + assert.NotNil(t, session.Extra) + require.NotNil(t, session.Headers) + assert.NotNil(t, session.Headers.Extra) +} + +func TestNewSessionWithAuthorizeRequest(t *testing.T) { + requestID := uuid.New() + subject := uuid.New() + + formValues := url.Values{} + + formValues.Set("nonce", "abc123xyzauthelia") + + request := &fosite.AuthorizeRequest{ + Request: fosite.Request{ + ID: requestID.String(), + Form: formValues, + Client: &InternalClient{ID: "example"}, + }, + } + + extra := map[string]interface{}{ + "preferred_username": "john", + } + + requested := time.Unix(1647332518, 0) + authAt := time.Unix(1647332500, 0) + issuer := "https://example.com" + + session := NewSessionWithAuthorizeRequest(issuer, "primary", subject.String(), "john", extra, authAt, requested, request) + + require.NotNil(t, session) + require.NotNil(t, session.Extra) + require.NotNil(t, session.Headers) + require.NotNil(t, session.Headers.Extra) + require.NotNil(t, session.Claims) + require.NotNil(t, session.Claims.Extra) + + assert.Equal(t, "abc123xyzauthelia", session.Claims.Nonce) + assert.Equal(t, subject.String(), session.Claims.Subject) + assert.Equal(t, subject.String(), session.Subject) + assert.Equal(t, issuer, session.Claims.Issuer) + assert.Equal(t, "primary", session.Headers.Get("kid")) + assert.Equal(t, "example", session.ClientID) + assert.Equal(t, requested, session.Claims.RequestedAt) + assert.Equal(t, authAt, session.Claims.AuthTime) + assert.Greater(t, session.Claims.IssuedAt.Unix(), authAt.Unix()) + assert.Equal(t, "john", session.Username) + + require.Contains(t, session.Claims.Extra, "preferred_username") + assert.Equal(t, "john", session.Claims.Extra["preferred_username"]) + + session = NewSessionWithAuthorizeRequest(issuer, "primary", subject.String(), "john", nil, authAt, requested, request) + + require.NotNil(t, session) + require.NotNil(t, session.Claims) + assert.NotNil(t, session.Claims.Extra) +}