fix(oidc): add detailed trace/debug logs (#3012)
This adds significantly more detailed logging for most OpenID Connect handlers.pull/3015/head
parent
2c3b507096
commit
9b779569f4
|
@ -1,142 +1,145 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"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/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/oidc"
|
"github.com/authelia/authelia/v4/internal/oidc"
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
func oidcAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) {
|
func oidcAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) {
|
||||||
ar, err := ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeRequest(ctx, r)
|
var (
|
||||||
if err != nil {
|
requester fosite.AuthorizeRequester
|
||||||
logging.Logger().Errorf("Error occurred in NewAuthorizeRequest: %+v", err)
|
responder fosite.AuthorizeResponder
|
||||||
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
clientID := ar.GetClient().GetID()
|
clientID := requester.GetClient().GetID()
|
||||||
client, err := ctx.Providers.OpenIDConnect.Store.GetInternalClient(clientID)
|
|
||||||
|
|
||||||
if err != nil {
|
ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' is being processed", requester.GetID(), clientID)
|
||||||
err := fmt.Errorf("unable to find related client configuration with name '%s': %v", ar.GetID(), err)
|
|
||||||
ctx.Logger.Error(err)
|
if client, err = ctx.Providers.OpenIDConnect.Store.GetInternalClient(clientID); err != nil {
|
||||||
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession := ctx.GetSession()
|
userSession := ctx.GetSession()
|
||||||
|
|
||||||
requestedScopes := ar.GetRequestedScopes()
|
requestedScopes := requester.GetRequestedScopes()
|
||||||
requestedAudience := ar.GetRequestedAudience()
|
requestedAudience := requester.GetRequestedAudience()
|
||||||
|
|
||||||
isAuthInsufficient := !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel)
|
isAuthInsufficient := !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel)
|
||||||
|
|
||||||
if isAuthInsufficient || (isConsentMissing(userSession.OIDCWorkflowSession, requestedScopes, requestedAudience)) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
extraClaims := oidcGrantRequests(ar, requestedScopes, requestedAudience, &userSession)
|
extraClaims := oidcGrantRequests(requester, requestedScopes, requestedAudience, &userSession)
|
||||||
|
|
||||||
workflowCreated := time.Unix(userSession.OIDCWorkflowSession.CreatedTimestamp, 0)
|
workflowCreated := time.Unix(userSession.OIDCWorkflowSession.CreatedTimestamp, 0)
|
||||||
|
|
||||||
userSession.OIDCWorkflowSession = nil
|
userSession.OIDCWorkflowSession = nil
|
||||||
if err := ctx.SaveSession(userSession); err != nil {
|
|
||||||
ctx.Logger.Error(err)
|
if err = ctx.SaveSession(userSession); err != nil {
|
||||||
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: %+v", requester.GetID(), client.GetID(), err)
|
||||||
|
|
||||||
|
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not save the session."))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
issuer, err := ctx.ExternalRootURL()
|
if authTime, err = userSession.AuthenticatedTime(client.Policy); err != nil {
|
||||||
if 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.Logger.Errorf("Error occurred obtaining issuer: %+v", err)
|
|
||||||
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err)
|
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not obtain the authentication time."))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authTime, err := userSession.AuthenticatedTime(client.Policy)
|
ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' was successfully processed, proceeding to build Authorization Response", requester.GetID(), clientID)
|
||||||
if err != nil {
|
|
||||||
ctx.Logger.Errorf("Error occurred obtaining authentication timestamp: %+v", err)
|
subject := userSession.Username
|
||||||
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, ar, err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := ctx.Providers.OpenIDConnect.Fosite.NewAuthorizeResponse(ctx, ar, &oidc.OpenIDSession{
|
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeResponse(rw, requester, responder)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func oidcAuthorizeHandleAuthorizationOrConsentInsufficient(
|
func oidcAuthorizeHandleAuthorizationOrConsentInsufficient(
|
||||||
ctx *middlewares.AutheliaCtx, userSession session.UserSession, client *oidc.InternalClient, isAuthInsufficient bool,
|
ctx *middlewares.AutheliaCtx, userSession session.UserSession, client *oidc.InternalClient, isAuthInsufficient bool,
|
||||||
rw http.ResponseWriter, r *http.Request,
|
rw http.ResponseWriter, r *http.Request,
|
||||||
ar fosite.AuthorizeRequester) {
|
requester fosite.AuthorizeRequester, issuer string) {
|
||||||
issuer, err := ctx.ExternalRootURL()
|
|
||||||
if err != nil {
|
|
||||||
ctx.Logger.Error(err)
|
|
||||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectURL := fmt.Sprintf("%s%s", issuer, string(ctx.Request.RequestURI()))
|
redirectURL := fmt.Sprintf("%s%s", issuer, string(ctx.Request.RequestURI()))
|
||||||
|
|
||||||
ctx.Logger.Debugf("User %s must consent with scopes %s",
|
ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' requires user '%s' provides consent for scopes '%s'",
|
||||||
userSession.Username, strings.Join(ar.GetRequestedScopes(), ", "))
|
requester.GetID(), client.GetID(), userSession.Username, strings.Join(requester.GetRequestedScopes(), "', '"))
|
||||||
|
|
||||||
userSession.OIDCWorkflowSession = &session.OIDCWorkflowSession{
|
userSession.OIDCWorkflowSession = &session.OIDCWorkflowSession{
|
||||||
ClientID: client.ID,
|
ClientID: client.GetID(),
|
||||||
RequestedScopes: ar.GetRequestedScopes(),
|
RequestedScopes: requester.GetRequestedScopes(),
|
||||||
RequestedAudience: ar.GetRequestedAudience(),
|
RequestedAudience: requester.GetRequestedAudience(),
|
||||||
AuthURI: redirectURL,
|
AuthURI: redirectURL,
|
||||||
TargetURI: ar.GetRedirectURI().String(),
|
TargetURI: requester.GetRedirectURI().String(),
|
||||||
RequiredAuthorizationLevel: client.Policy,
|
RequiredAuthorizationLevel: client.Policy,
|
||||||
CreatedTimestamp: time.Now().Unix(),
|
CreatedTimestamp: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.SaveSession(userSession); err != nil {
|
if err := ctx.SaveSession(userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to save session: %v", err)
|
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)
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
||||||
|
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not save the session."))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,20 +3,33 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ory/fosite"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"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) {
|
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)
|
ctx.Providers.OpenIDConnect.Fosite.WriteIntrospectionError(rw, err)
|
||||||
|
|
||||||
return
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@ func oidcJWKs(ctx *middlewares.AutheliaCtx) {
|
||||||
ctx.SetContentType("application/json")
|
ctx.SetContentType("application/json")
|
||||||
|
|
||||||
if err := json.NewEncoder(ctx).Encode(ctx.Providers.OpenIDConnect.KeyManager.GetKeySet()); err != nil {
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,19 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ory/fosite"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
)
|
)
|
||||||
|
|
||||||
func oidcRevocation(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) {
|
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)
|
ctx.Providers.OpenIDConnect.Fosite.WriteRevocationResponse(rw, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,35 +6,54 @@ import (
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"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) {
|
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)
|
oidcSession := oidc.NewSession()
|
||||||
if err != nil {
|
|
||||||
ctx.Logger.Errorf("Error occurred in NewAccessRequest: %+v", err)
|
if requester, err = ctx.Providers.OpenIDConnect.Fosite.NewAccessRequest(ctx, req, oidcSession); err != nil {
|
||||||
ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, accessRequest, err)
|
rfc := fosite.ErrorToRFC6749Error(err)
|
||||||
|
|
||||||
|
ctx.Logger.Errorf("Access Request failed with error: %+v", rfc)
|
||||||
|
|
||||||
|
ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, requester, err)
|
||||||
|
|
||||||
return
|
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 this is a client_credentials grant, grant all scopes the client is allowed to perform.
|
||||||
if accessRequest.GetGrantTypes().ExactOne("client_credentials") {
|
if requester.GetGrantTypes().ExactOne("client_credentials") {
|
||||||
for _, scope := range accessRequest.GetRequestedScopes() {
|
for _, scope := range requester.GetRequestedScopes() {
|
||||||
if fosite.HierarchicScopeStrategy(accessRequest.GetClient().GetScopes(), scope) {
|
if fosite.HierarchicScopeStrategy(client.GetScopes(), scope) {
|
||||||
accessRequest.GrantScope(scope)
|
requester.GrantScope(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := ctx.Providers.OpenIDConnect.Fosite.NewAccessResponse(ctx, accessRequest)
|
if responder, err = ctx.Providers.OpenIDConnect.Fosite.NewAccessResponse(ctx, requester); err != nil {
|
||||||
if err != nil {
|
rfc := fosite.ErrorToRFC6749Error(err)
|
||||||
ctx.Logger.Errorf("Error occurred in NewAccessResponse: %+v", err)
|
|
||||||
ctx.Providers.OpenIDConnect.Fosite.WriteAccessError(rw, accessRequest, 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
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,23 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func oidcUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *http.Request) {
|
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)
|
oidcSession := oidc.NewSession()
|
||||||
if err != nil {
|
|
||||||
|
if tokenType, requester, err = ctx.Providers.OpenIDConnect.Fosite.IntrospectToken(
|
||||||
|
req.Context(), fosite.AccessTokenFromRequest(req), fosite.AccessToken, oidcSession); err != nil {
|
||||||
rfc := fosite.ErrorToRFC6749Error(err)
|
rfc := fosite.ErrorToRFC6749Error(err)
|
||||||
|
|
||||||
|
ctx.Logger.Errorf("UserInfo Request failed with error: %+v", rfc)
|
||||||
|
|
||||||
if rfc.StatusCode() == http.StatusUnauthorized {
|
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)
|
ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
|
||||||
|
@ -29,22 +39,25 @@ func oidcUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *htt
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientID := requester.GetClient().GetID()
|
||||||
|
|
||||||
if tokenType != fosite.AccessToken {
|
if tokenType != fosite.AccessToken {
|
||||||
errStr := "authorization header must contain an OAuth access token."
|
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())
|
||||||
rw.Header().Set("WWW-Authenticate", fmt.Sprintf("error_description=\"%s\"", errStr))
|
|
||||||
|
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))
|
ctx.Providers.OpenIDConnect.WriteErrorCode(rw, req, http.StatusUnauthorized, errors.New(errStr))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client, ok := ar.GetClient().(*oidc.InternalClient)
|
if client, err = ctx.Providers.OpenIDConnect.Store.GetInternalClient(clientID); err != nil {
|
||||||
if !ok {
|
|
||||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHint("Unable to assert type of client")))
|
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHint("Unable to assert type of client")))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims := ar.GetSession().(*oidc.OpenIDSession).IDTokenClaims().ToMap()
|
claims := requester.GetSession().(*oidc.OpenIDSession).IDTokenClaims().ToMap()
|
||||||
delete(claims, "jti")
|
delete(claims, "jti")
|
||||||
delete(claims, "sid")
|
delete(claims, "sid")
|
||||||
delete(claims, "at_hash")
|
delete(claims, "at_hash")
|
||||||
|
@ -52,27 +65,49 @@ func oidcUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *htt
|
||||||
delete(claims, "exp")
|
delete(claims, "exp")
|
||||||
delete(claims, "nonce")
|
delete(claims, "nonce")
|
||||||
|
|
||||||
if audience, ok := claims["aud"].([]string); !ok || len(audience) == 0 {
|
audience, ok := claims["aud"].([]string)
|
||||||
claims["aud"] = []string{client.GetID()}
|
|
||||||
|
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 {
|
switch client.UserinfoSigningAlgorithm {
|
||||||
case "RS256":
|
case "RS256":
|
||||||
claims["jti"] = uuid.New()
|
claims["jti"] = uuid.New()
|
||||||
claims["iat"] = time.Now().Unix()
|
claims["iat"] = time.Now().Unix()
|
||||||
|
|
||||||
keyID, err := ctx.Providers.OpenIDConnect.KeyManager.Strategy().GetPublicKeyID(req.Context())
|
if keyID, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().GetPublicKeyID(req.Context()); err != nil {
|
||||||
if err != nil {
|
ctx.Providers.OpenIDConnect.WriteError(rw, req, fosite.ErrServerError.WithHintf("Could not find the active JWK."))
|
||||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token, _, err := ctx.Providers.OpenIDConnect.KeyManager.Strategy().Generate(req.Context(), claims,
|
headers := &jwt.Headers{
|
||||||
&jwt.Headers{
|
Extra: map[string]interface{}{"kid": keyID},
|
||||||
Extra: map[string]interface{}{"kid": keyID},
|
}
|
||||||
})
|
|
||||||
if err != nil {
|
if token, _, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().Generate(req.Context(), claims, headers); err != nil {
|
||||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
|
ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -83,6 +118,6 @@ func oidcUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, req *htt
|
||||||
case "none", "":
|
case "none", "":
|
||||||
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
|
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
|
||||||
default:
|
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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ory/fosite"
|
"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/oidc"
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"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)
|
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{}) {
|
func oidcGrantRequests(ar fosite.AuthorizeRequester, scopes, audiences []string, userSession *session.UserSession) (extraClaims map[string]interface{}) {
|
||||||
extraClaims = map[string]interface{}{}
|
extraClaims = map[string]interface{}{}
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,65 @@ package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/ory/fosite/handler/openid"
|
"github.com/ory/fosite/handler/openid"
|
||||||
"github.com/ory/fosite/storage"
|
"github.com/ory/fosite/storage"
|
||||||
|
"github.com/ory/fosite/token/jwt"
|
||||||
"github.com/ory/herodot"
|
"github.com/ory/herodot"
|
||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"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.
|
// OpenIDConnectProvider for OpenID Connect.
|
||||||
type OpenIDConnectProvider struct {
|
type OpenIDConnectProvider struct {
|
||||||
Fosite fosite.OAuth2Provider
|
Fosite fosite.OAuth2Provider
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue