diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go index e91c9803d..ddee3e162 100644 --- a/internal/configuration/validator/identity_providers.go +++ b/internal/configuration/validator/identity_providers.go @@ -66,8 +66,8 @@ func validateOIDCPolicies(config *schema.OpenIDConnect, val *schema.StructValida switch name { case "": val.Push(fmt.Errorf("policy must have a name")) - case policyOneFactor, policyTwoFactor: - val.Push(fmt.Errorf("policy names can't be named one_factor or two_factor")) + case policyOneFactor, policyTwoFactor, policyDeny: + val.Push(fmt.Errorf("policy names can't be named any of %s", strJoinAnd([]string{policyOneFactor, policyTwoFactor, policyDeny}))) default: break } @@ -75,10 +75,10 @@ func validateOIDCPolicies(config *schema.OpenIDConnect, val *schema.StructValida switch policy.DefaultPolicy { case "": policy.DefaultPolicy = policyTwoFactor - case policyOneFactor, policyTwoFactor: + case policyOneFactor, policyTwoFactor, policyDeny: break default: - val.Push(fmt.Errorf("policy must be one of one_factor or two_factor")) + val.Push(fmt.Errorf("policy must be one of %s", strJoinAnd([]string{policyOneFactor, policyTwoFactor, policyDeny}))) } if len(policy.Rules) == 0 { @@ -89,10 +89,10 @@ func validateOIDCPolicies(config *schema.OpenIDConnect, val *schema.StructValida switch rule.Policy { case "": policy.Rules[i].Policy = policyTwoFactor - case policyOneFactor, policyTwoFactor: + case policyOneFactor, policyTwoFactor, policyDeny: break default: - val.Push(fmt.Errorf("policy must be one of one_factor or two_factor")) + val.Push(fmt.Errorf("policy must be one of %s", strJoinAnd([]string{policyOneFactor, policyTwoFactor, policyDeny}))) } if len(rule.Subjects) == 0 { diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go index c8f13faf0..eb9a24c5f 100644 --- a/internal/handlers/handler_oidc_authorization.go +++ b/internal/handlers/handler_oidc_authorization.go @@ -6,7 +6,6 @@ import ( "net/url" "time" - "github.com/authelia/authelia/v4/internal/authorization" "github.com/ory/fosite" "github.com/authelia/authelia/v4/internal/authorization" diff --git a/internal/handlers/handler_oidc_authorization_consent.go b/internal/handlers/handler_oidc_authorization_consent.go index a3a127849..0488a8709 100644 --- a/internal/handlers/handler_oidc_authorization_consent.go +++ b/internal/handlers/handler_oidc_authorization_consent.go @@ -8,7 +8,6 @@ import ( "path" "strings" - "github.com/authelia/authelia/v4/internal/authorization" "github.com/google/uuid" "github.com/ory/fosite" @@ -30,10 +29,12 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR var handler handlerAuthorizationConsent + policy := client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}) + switch { case userSession.IsAnonymous(): handler = handleOIDCAuthorizationConsentNotAuthenticated - case client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel, authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}): + case authorization.IsAuthLevelSufficient(userSession.AuthenticationLevel, policy): if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil { ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err) @@ -57,6 +58,14 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR return nil, true } default: + if policy == authorization.Denied { + ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: the user '%s' is not authorized to use this client", requester.GetID(), client.GetID(), userSession.Username) + + ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrClientAuthorizationUserAccessDenied) + + return nil, true + } + if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil { ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err) diff --git a/internal/handlers/handler_oidc_consent.go b/internal/handlers/handler_oidc_consent.go index 8e35ee973..1daa01628 100644 --- a/internal/handlers/handler_oidc_consent.go +++ b/internal/handlers/handler_oidc_consent.go @@ -8,7 +8,6 @@ import ( "path" "time" - "github.com/authelia/authelia/v4/internal/authorization" "github.com/google/uuid" "github.com/authelia/authelia/v4/internal/authorization" @@ -88,6 +87,14 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) { return } + if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel, authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}) { + ctx.Logger.Errorf("User '%s' can't consent to authorization request for client with id '%s' as they are not sufficiently authenticated", + userSession.Username, consent.ClientID) + ctx.SetJSONError(messageOperationFailed) + + return + } + if bodyJSON.Consent { consent.Grant() diff --git a/internal/handlers/response.go b/internal/handlers/response.go index 5a8cbb945..ed1d7fe71 100644 --- a/internal/handlers/response.go +++ b/internal/handlers/response.go @@ -221,34 +221,37 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) { return } - if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel, authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}) { + policy := client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}) + + switch { + case authorization.IsAuthLevelSufficient(userSession.AuthenticationLevel, policy), policy == authorization.Denied: + var ( + targetURL *url.URL + form url.Values + ) + + targetURL = ctx.RootURL() + + if form, err = consent.GetForm(); err != nil { + ctx.Error(fmt.Errorf("unable to get authorization form values from consent session with challenge id '%s': %w", consent.ChallengeID, err), messageAuthenticationFailed) + + return + } + + form.Set(queryArgConsentID, workflowID.String()) + + targetURL.Path = path.Join(targetURL.Path, oidc.EndpointPathAuthorization) + targetURL.RawQuery = form.Encode() + + if err = ctx.SetJSONBody(redirectResponse{Redirect: targetURL.String()}); err != nil { + ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err) + } + default: ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.GetID()) ctx.ReplyOK() return } - - var ( - targetURL *url.URL - form url.Values - ) - - targetURL = ctx.RootURL() - - if form, err = consent.GetForm(); err != nil { - ctx.Error(fmt.Errorf("unable to get authorization form values from consent session with challenge id '%s': %w", consent.ChallengeID, err), messageAuthenticationFailed) - - return - } - - form.Set(queryArgConsentID, workflowID.String()) - - targetURL.Path = path.Join(targetURL.Path, oidc.EndpointPathAuthorization) - targetURL.RawQuery = form.Encode() - - if err = ctx.SetJSONBody(redirectResponse{Redirect: targetURL.String()}); err != nil { - ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err) - } } func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, bannedUntil *time.Time, username string, authType string, errAuth error) (err error) { diff --git a/internal/oidc/errors.go b/internal/oidc/errors.go index 4041bbda4..cb284c1f8 100644 --- a/internal/oidc/errors.go +++ b/internal/oidc/errors.go @@ -32,4 +32,6 @@ var ( // ErrPAREnforcedClientMissingPAR is sent when a client has EnforcePAR configured but the Authorization Request was not Pushed. ErrPAREnforcedClientMissingPAR = fosite.ErrInvalidRequest.WithHint("Pushed Authorization Requests are enforced for this client but no such request was sent.") + + ErrClientAuthorizationUserAccessDenied = fosite.ErrAccessDenied.WithHint("The user was denied access to this client.") )