authelia/internal/handlers/handler_oidc_authorization.go
James Elliott 4ebd8fdf4e
feat(oidc): provide cors config including options handlers (#3005)
This adjusts the CORS headers appropriately for OpenID Connect. This includes responding to OPTIONS requests appropriately. Currently this is only configured to operate when the Origin scheme is HTTPS; but can easily be expanded in the future to include additional Origins.
2022-04-07 10:58:51 +10:00

158 lines
6.7 KiB
Go

package handlers
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/ory/fosite"
"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/session"
)
// OpenIDConnectAuthorizationGET handles GET requests to the OpenID Connect 1.0 Authorization endpoint.
//
// https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
func OpenIDConnectAuthorizationGET(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, r *http.Request) {
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 := requester.GetClient().GetID()
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 := requester.GetRequestedScopes()
requestedAudience := requester.GetRequestedAudience()
isAuthInsufficient := !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel)
if isAuthInsufficient || (isConsentMissing(userSession.OIDCWorkflowSession, requestedScopes, requestedAudience)) {
oidcAuthorizeHandleAuthorizationOrConsentInsufficient(ctx, userSession, client, isAuthInsufficient, rw, r, requester, issuer)
return
}
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.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
}
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
}
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, userSession.AuthenticationMethodRefs.MarshalRFC8176(), 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
}
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,
requester fosite.AuthorizeRequester, issuer string) {
redirectURL := fmt.Sprintf("%s%s", issuer, string(ctx.Request.RequestURI()))
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 = &model.OIDCWorkflowSession{
ClientID: client.GetID(),
RequestedScopes: requester.GetRequestedScopes(),
RequestedAudience: requester.GetRequestedAudience(),
AuthURI: redirectURL,
TargetURI: requester.GetRedirectURI().String(),
Require2FA: client.Policy == authorization.TwoFactor,
CreatedTimestamp: time.Now().Unix(),
}
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 for consent: %+v", requester.GetID(), client.GetID(), err)
ctx.Providers.OpenIDConnect.Fosite.WriteAuthorizeError(rw, requester, fosite.ErrServerError.WithHint("Could not save the session."))
return
}
if isAuthInsufficient {
http.Redirect(rw, r, issuer, http.StatusFound)
} else {
http.Redirect(rw, r, fmt.Sprintf("%s/consent", issuer), http.StatusFound)
}
}