feat: denied
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>feat-oidc-policies
parent
2f9da2b7e0
commit
f290fd90b1
|
@ -66,8 +66,8 @@ func validateOIDCPolicies(config *schema.OpenIDConnect, val *schema.StructValida
|
||||||
switch name {
|
switch name {
|
||||||
case "":
|
case "":
|
||||||
val.Push(fmt.Errorf("policy must have a name"))
|
val.Push(fmt.Errorf("policy must have a name"))
|
||||||
case policyOneFactor, policyTwoFactor:
|
case policyOneFactor, policyTwoFactor, policyDeny:
|
||||||
val.Push(fmt.Errorf("policy names can't be named one_factor or two_factor"))
|
val.Push(fmt.Errorf("policy names can't be named any of %s", strJoinAnd([]string{policyOneFactor, policyTwoFactor, policyDeny})))
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -75,10 +75,10 @@ func validateOIDCPolicies(config *schema.OpenIDConnect, val *schema.StructValida
|
||||||
switch policy.DefaultPolicy {
|
switch policy.DefaultPolicy {
|
||||||
case "":
|
case "":
|
||||||
policy.DefaultPolicy = policyTwoFactor
|
policy.DefaultPolicy = policyTwoFactor
|
||||||
case policyOneFactor, policyTwoFactor:
|
case policyOneFactor, policyTwoFactor, policyDeny:
|
||||||
break
|
break
|
||||||
default:
|
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 {
|
if len(policy.Rules) == 0 {
|
||||||
|
@ -89,10 +89,10 @@ func validateOIDCPolicies(config *schema.OpenIDConnect, val *schema.StructValida
|
||||||
switch rule.Policy {
|
switch rule.Policy {
|
||||||
case "":
|
case "":
|
||||||
policy.Rules[i].Policy = policyTwoFactor
|
policy.Rules[i].Policy = policyTwoFactor
|
||||||
case policyOneFactor, policyTwoFactor:
|
case policyOneFactor, policyTwoFactor, policyDeny:
|
||||||
break
|
break
|
||||||
default:
|
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 {
|
if len(rule.Subjects) == 0 {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
|
|
||||||
|
@ -30,10 +29,12 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR
|
||||||
|
|
||||||
var handler handlerAuthorizationConsent
|
var handler handlerAuthorizationConsent
|
||||||
|
|
||||||
|
policy := client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()})
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case userSession.IsAnonymous():
|
case userSession.IsAnonymous():
|
||||||
handler = handleOIDCAuthorizationConsentNotAuthenticated
|
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 {
|
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)
|
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
|
return nil, true
|
||||||
}
|
}
|
||||||
default:
|
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 {
|
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)
|
ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authorization"
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
|
@ -88,6 +87,14 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
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 {
|
if bodyJSON.Consent {
|
||||||
consent.Grant()
|
consent.Grant()
|
||||||
|
|
||||||
|
|
|
@ -221,34 +221,37 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
|
||||||
return
|
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.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.GetID())
|
||||||
ctx.ReplyOK()
|
ctx.ReplyOK()
|
||||||
|
|
||||||
return
|
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) {
|
func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, bannedUntil *time.Time, username string, authType string, errAuth error) (err error) {
|
||||||
|
|
|
@ -32,4 +32,6 @@ var (
|
||||||
|
|
||||||
// ErrPAREnforcedClientMissingPAR is sent when a client has EnforcePAR configured but the Authorization Request was not Pushed.
|
// 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.")
|
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.")
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue