Compare commits
4 Commits
master
...
feat-oidc-
Author | SHA1 | Date |
---|---|---|
James Elliott | 33fdacb6e1 | |
James Elliott | f290fd90b1 | |
James Elliott | 2f9da2b7e0 | |
James Elliott | 5e5eead729 |
|
@ -208,6 +208,10 @@ func domainToPrefixSuffix(domain string) (prefix, suffix string) {
|
|||
return parts[0], strings.Join(parts[1:], ".")
|
||||
}
|
||||
|
||||
func NewSubjects(subjectRules [][]string) (subjects []AccessControlSubjects) {
|
||||
return schemaSubjectsToACL(subjectRules)
|
||||
}
|
||||
|
||||
// IsAuthLevelSufficient returns true if the current authenticationLevel is above the authorizationLevel.
|
||||
func IsAuthLevelSufficient(authenticationLevel authentication.Level, authorizationLevel Level) bool {
|
||||
switch authorizationLevel {
|
||||
|
|
|
@ -35,11 +35,25 @@ type OpenIDConnect struct {
|
|||
|
||||
Clients []OpenIDConnectClient `koanf:"clients"`
|
||||
|
||||
Policies map[string]OpenIDConnectPolicy `koanf:"policies"`
|
||||
|
||||
Discovery OpenIDConnectDiscovery // MetaData value. Not configurable by users.
|
||||
}
|
||||
|
||||
type OpenIDConnectPolicy struct {
|
||||
DefaultPolicy string `koanf:"default_policy"`
|
||||
|
||||
Rules []OpenIDConnectPolicyRule `koanf:"rules"`
|
||||
}
|
||||
|
||||
type OpenIDConnectPolicyRule struct {
|
||||
Policy string `koanf:"policy"`
|
||||
Subjects [][]string `koanf:"subject"`
|
||||
}
|
||||
|
||||
// OpenIDConnectDiscovery is information discovered during validation reused for the discovery handlers.
|
||||
type OpenIDConnectDiscovery struct {
|
||||
Policies []string
|
||||
DefaultKeyIDs map[string]string
|
||||
DefaultKeyID string
|
||||
ResponseObjectSigningKeyIDs []string
|
||||
|
|
|
@ -72,6 +72,11 @@ var Keys = []string{
|
|||
"identity_providers.oidc.clients[].public_keys.values[].key",
|
||||
"identity_providers.oidc.clients[].public_keys.values[].certificate_chain",
|
||||
"identity_providers.oidc.clients[]",
|
||||
"identity_providers.oidc.policies",
|
||||
"identity_providers.oidc.policies.*.default_policy",
|
||||
"identity_providers.oidc.policies.*.rules",
|
||||
"identity_providers.oidc.policies.*.rules[].policy",
|
||||
"identity_providers.oidc.policies.*.rules[].subject",
|
||||
"identity_providers.oidc",
|
||||
"authentication_backend.password_reset.disable",
|
||||
"authentication_backend.password_reset.custom_url",
|
||||
|
|
|
@ -124,6 +124,12 @@ notifier:
|
|||
|
||||
identity_providers:
|
||||
oidc:
|
||||
policies:
|
||||
abc:
|
||||
default_policy: 'two_factor'
|
||||
rules:
|
||||
- subject: 'group:admin'
|
||||
policy: 'one_factor'
|
||||
cors:
|
||||
allowed_origins:
|
||||
- 'https://google.com'
|
||||
|
|
|
@ -30,6 +30,7 @@ func validateOIDC(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
|||
setOIDCDefaults(config)
|
||||
|
||||
validateOIDCIssuer(config, val)
|
||||
validateOIDCPolicies(config, val)
|
||||
|
||||
sort.Sort(oidc.SortedSigningAlgs(config.Discovery.ResponseObjectSigningAlgs))
|
||||
|
||||
|
@ -58,6 +59,53 @@ func validateOIDC(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
|||
}
|
||||
}
|
||||
|
||||
func validateOIDCPolicies(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||
config.Discovery.Policies = []string{policyOneFactor, policyTwoFactor}
|
||||
|
||||
for name, policy := range config.Policies {
|
||||
switch name {
|
||||
case "":
|
||||
val.Push(fmt.Errorf("policy must have a name"))
|
||||
case policyOneFactor, policyTwoFactor, policyDeny:
|
||||
val.Push(fmt.Errorf("policy names can't be named any of %s", strJoinAnd([]string{policyOneFactor, policyTwoFactor, policyDeny})))
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
switch policy.DefaultPolicy {
|
||||
case "":
|
||||
policy.DefaultPolicy = policyTwoFactor
|
||||
case policyOneFactor, policyTwoFactor, policyDeny:
|
||||
break
|
||||
default:
|
||||
val.Push(fmt.Errorf("policy must be one of %s", strJoinAnd([]string{policyOneFactor, policyTwoFactor, policyDeny})))
|
||||
}
|
||||
|
||||
if len(policy.Rules) == 0 {
|
||||
val.Push(fmt.Errorf("policy must include at least one rule"))
|
||||
}
|
||||
|
||||
for i, rule := range policy.Rules {
|
||||
switch rule.Policy {
|
||||
case "":
|
||||
policy.Rules[i].Policy = policyTwoFactor
|
||||
case policyOneFactor, policyTwoFactor, policyDeny:
|
||||
break
|
||||
default:
|
||||
val.Push(fmt.Errorf("policy must be one of %s", strJoinAnd([]string{policyOneFactor, policyTwoFactor, policyDeny})))
|
||||
}
|
||||
|
||||
if len(rule.Subjects) == 0 {
|
||||
val.Push(fmt.Errorf("policy must include at least one criteria"))
|
||||
}
|
||||
}
|
||||
|
||||
config.Policies[name] = policy
|
||||
|
||||
config.Discovery.Policies = append(config.Discovery.Policies, name)
|
||||
}
|
||||
}
|
||||
|
||||
func validateOIDCIssuer(config *schema.OpenIDConnect, val *schema.StructValidator) {
|
||||
switch {
|
||||
case config.IssuerPrivateKey != nil:
|
||||
|
@ -337,13 +385,13 @@ func validateOIDCClient(c int, config *schema.OpenIDConnect, val *schema.StructV
|
|||
}
|
||||
}
|
||||
|
||||
switch config.Clients[c].Policy {
|
||||
case "":
|
||||
switch {
|
||||
case config.Clients[c].Policy == "":
|
||||
config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy
|
||||
case policyOneFactor, policyTwoFactor:
|
||||
case utils.IsStringInSlice(config.Clients[c].Policy, config.Discovery.Policies):
|
||||
break
|
||||
default:
|
||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "authorization_policy", strJoinOr([]string{policyOneFactor, policyTwoFactor}), config.Clients[c].Policy))
|
||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "authorization_policy", strJoinOr(config.Discovery.Policies), config.Clients[c].Policy))
|
||||
}
|
||||
|
||||
switch config.Clients[c].PKCEChallengeMethod {
|
||||
|
|
|
@ -67,16 +67,18 @@ KEYS:
|
|||
// NewKeyPattern returns patterns which are required to match key patterns.
|
||||
func NewKeyPattern(key string) (pattern *regexp.Regexp, err error) {
|
||||
switch {
|
||||
case strings.Contains(key, ".*."):
|
||||
case reIsMapKey.MatchString(key):
|
||||
return NewKeyMapPattern(key)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
var reIsMapKey = regexp.MustCompile(`\.\*(\[]|\.)`)
|
||||
|
||||
// NewKeyMapPattern returns a pattern required to match map keys.
|
||||
func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) {
|
||||
parts := strings.Split(key, ".*.")
|
||||
parts := strings.Split(key, ".*")
|
||||
|
||||
buf := &strings.Builder{}
|
||||
|
||||
|
@ -85,11 +87,16 @@ func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) {
|
|||
n := len(parts) - 1
|
||||
|
||||
for i, part := range parts {
|
||||
if i != 0 {
|
||||
if i != 0 && !strings.HasPrefix(part, "[]") {
|
||||
buf.WriteString("\\.")
|
||||
}
|
||||
|
||||
for _, r := range part {
|
||||
for j, r := range part {
|
||||
// Skip prefixed period.
|
||||
if j == 0 && r == '.' {
|
||||
continue
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '[', ']', '.', '{', '}':
|
||||
buf.WriteRune('\\')
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"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"
|
||||
|
@ -117,7 +118,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
|
|||
|
||||
extraClaims := oidcGrantRequests(requester, consent, &userSession)
|
||||
|
||||
if authTime, err = userSession.AuthenticatedTime(client.GetAuthorizationPolicy()); err != nil {
|
||||
if authTime, err = userSession.AuthenticatedTime(client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()})); 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.WriteAuthorizeError(ctx, rw, requester, fosite.ErrServerError.WithHint("Could not obtain the authentication time."))
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"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"
|
||||
|
@ -28,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):
|
||||
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)
|
||||
|
||||
|
@ -55,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)
|
||||
|
||||
|
@ -121,7 +132,7 @@ func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer
|
|||
userSession session.UserSession, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) {
|
||||
var location *url.URL
|
||||
|
||||
if client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
|
||||
if client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel, authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}) {
|
||||
location, _ = url.ParseRequestURI(issuer.String())
|
||||
location.Path = path.Join(location.Path, oidc.EndpointPathConsent)
|
||||
|
||||
|
@ -130,11 +141,11 @@ func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer
|
|||
|
||||
location.RawQuery = query.Encode()
|
||||
|
||||
ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "sufficient", client.GetAuthorizationPolicy())
|
||||
ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "sufficient", client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}))
|
||||
} else {
|
||||
location = handleOIDCAuthorizationConsentGetRedirectionURL(issuer, consent, requester)
|
||||
|
||||
ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "insufficient", client.GetAuthorizationPolicy())
|
||||
ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "insufficient", client.GetAuthorizationPolicy(authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}))
|
||||
}
|
||||
|
||||
ctx.Logger.Debugf(logFmtDbgConsentRedirect, requester.GetID(), client.GetID(), client.GetConsentPolicy(), location)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"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"
|
||||
|
@ -86,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()
|
||||
|
||||
|
@ -206,7 +215,7 @@ func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uui
|
|||
}
|
||||
}
|
||||
|
||||
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
|
||||
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel, authorization.Subject{Username: userSession.Username, Groups: userSession.Groups, IP: ctx.RemoteIP()}) {
|
||||
ctx.Logger.Errorf("Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the user is not sufficiently authenticated", userSession.Username, consent.ClientID)
|
||||
ctx.ReplyForbidden()
|
||||
|
||||
|
|
|
@ -221,34 +221,37 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
|
|||
return
|
||||
}
|
||||
|
||||
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
|
||||
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) {
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
// NewClient creates a new Client.
|
||||
func NewClient(config schema.OpenIDConnectClient) (client Client) {
|
||||
func NewClient(config schema.OpenIDConnectClient, c *schema.OpenIDConnect) (client Client) {
|
||||
base := &BaseClient{
|
||||
ID: config.ID,
|
||||
Description: config.Description,
|
||||
|
@ -39,8 +39,7 @@ func NewClient(config schema.OpenIDConnectClient) (client Client) {
|
|||
UserinfoSigningAlg: config.UserinfoSigningAlg,
|
||||
UserinfoSigningKeyID: config.UserinfoSigningKeyID,
|
||||
|
||||
Policy: authorization.NewLevel(config.Policy),
|
||||
|
||||
Policy: NewClientPolicy(config.Policy, c),
|
||||
Consent: NewClientConsent(config.ConsentMode, config.ConsentPreConfiguredDuration),
|
||||
}
|
||||
|
||||
|
@ -192,9 +191,18 @@ func (c *BaseClient) GetPKCEChallengeMethod() string {
|
|||
return c.PKCEChallengeMethod
|
||||
}
|
||||
|
||||
// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
|
||||
func (c *BaseClient) IsAuthenticationLevelSufficient(level authentication.Level, subject authorization.Subject) bool {
|
||||
if level == authentication.NotAuthenticated {
|
||||
return false
|
||||
}
|
||||
|
||||
return authorization.IsAuthLevelSufficient(level, c.GetAuthorizationPolicy(subject))
|
||||
}
|
||||
|
||||
// GetAuthorizationPolicy returns Policy.
|
||||
func (c *BaseClient) GetAuthorizationPolicy() authorization.Level {
|
||||
return c.Policy
|
||||
func (c *BaseClient) GetAuthorizationPolicy(subject authorization.Subject) authorization.Level {
|
||||
return c.Policy.GetRequiredLevel(subject)
|
||||
}
|
||||
|
||||
// GetConsentPolicy returns Consent.
|
||||
|
@ -223,15 +231,6 @@ func (c *BaseClient) IsPublic() bool {
|
|||
return c.Public
|
||||
}
|
||||
|
||||
// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
|
||||
func (c *BaseClient) IsAuthenticationLevelSufficient(level authentication.Level) bool {
|
||||
if level == authentication.NotAuthenticated {
|
||||
return false
|
||||
}
|
||||
|
||||
return authorization.IsAuthLevelSufficient(level, c.Policy)
|
||||
}
|
||||
|
||||
// ValidatePKCEPolicy is a helper function to validate PKCE policy constraints on a per-client basis.
|
||||
func (c *BaseClient) ValidatePKCEPolicy(r fosite.Requester) (err error) {
|
||||
form := r.GetRequestForm()
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"github.com/authelia/authelia/v4/internal/authorization"
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
)
|
||||
|
||||
func NewClientPolicy(name string, config *schema.OpenIDConnect) (policy ClientPolicy) {
|
||||
switch name {
|
||||
case authorization.OneFactor.String(), authorization.TwoFactor.String():
|
||||
return ClientPolicy{DefaultPolicy: authorization.NewLevel(name)}
|
||||
default:
|
||||
if p, ok := config.Policies[name]; ok {
|
||||
policy.DefaultPolicy = authorization.NewLevel(p.DefaultPolicy)
|
||||
|
||||
for _, r := range p.Rules {
|
||||
policy.Rules = append(policy.Rules, ClientPolicyRule{
|
||||
Policy: authorization.NewLevel(r.Policy),
|
||||
Subjects: authorization.NewSubjects(r.Subjects),
|
||||
})
|
||||
}
|
||||
|
||||
return policy
|
||||
}
|
||||
|
||||
return ClientPolicy{DefaultPolicy: authorization.TwoFactor}
|
||||
}
|
||||
}
|
||||
|
||||
// ClientPolicy controls and represents a client policy.
|
||||
type ClientPolicy struct {
|
||||
DefaultPolicy authorization.Level
|
||||
Rules []ClientPolicyRule
|
||||
}
|
||||
|
||||
func (p *ClientPolicy) GetRequiredLevel(subject authorization.Subject) authorization.Level {
|
||||
for _, rule := range p.Rules {
|
||||
if rule.IsMatch(subject) {
|
||||
return rule.Policy
|
||||
}
|
||||
}
|
||||
|
||||
return p.DefaultPolicy
|
||||
}
|
||||
|
||||
type ClientPolicyRule struct {
|
||||
Subjects []authorization.AccessControlSubjects
|
||||
Policy authorization.Level
|
||||
}
|
||||
|
||||
// MatchesSubjects returns true if the rule matches the subjects.
|
||||
func (p *ClientPolicyRule) MatchesSubjects(subject authorization.Subject) (match bool) {
|
||||
// If there are no subjects in this rule then the subject condition is a match.
|
||||
if len(p.Subjects) == 0 {
|
||||
return true
|
||||
} else if subject.IsAnonymous() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Iterate over the subjects until we find a match (return true) or until we exit the loop (return false).
|
||||
for _, rule := range p.Subjects {
|
||||
if rule.IsMatch(subject) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsMatch returns true if all elements of an AccessControlRule match the object and subject.
|
||||
func (p *ClientPolicyRule) IsMatch(subject authorization.Subject) (match bool) {
|
||||
return p.MatchesSubjects(subject)
|
||||
}
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
func TestNewClient(t *testing.T) {
|
||||
config := schema.OpenIDConnectClient{}
|
||||
client := oidc.NewClient(config)
|
||||
client := oidc.NewClient(config, &schema.OpenIDConnect{})
|
||||
assert.Equal(t, "", client.GetID())
|
||||
assert.Equal(t, "", client.GetDescription())
|
||||
assert.Len(t, client.GetResponseModes(), 0)
|
||||
|
@ -47,17 +47,17 @@ func TestNewClient(t *testing.T) {
|
|||
ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes,
|
||||
}
|
||||
|
||||
client = oidc.NewClient(config)
|
||||
client = oidc.NewClient(config, &schema.OpenIDConnect{})
|
||||
assert.Equal(t, myclient, client.GetID())
|
||||
require.Len(t, client.GetResponseModes(), 1)
|
||||
assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0])
|
||||
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
|
||||
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy(authorization.Subject{}))
|
||||
|
||||
config = schema.OpenIDConnectClient{
|
||||
TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
|
||||
}
|
||||
|
||||
client = oidc.NewClient(config)
|
||||
client = oidc.NewClient(config, &schema.OpenIDConnect{})
|
||||
|
||||
fclient, ok := client.(*oidc.FullClient)
|
||||
|
||||
|
@ -204,25 +204,25 @@ func TestBaseClient_ValidatePARPolicy(t *testing.T) {
|
|||
func TestIsAuthenticationLevelSufficient(t *testing.T) {
|
||||
c := &oidc.FullClient{BaseClient: &oidc.BaseClient{}}
|
||||
|
||||
c.Policy = authorization.Bypass
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
|
||||
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
|
||||
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
|
||||
c.Policy = oidc.ClientPolicy{DefaultPolicy: authorization.Bypass}
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated, authorization.Subject{}))
|
||||
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor, authorization.Subject{}))
|
||||
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor, authorization.Subject{}))
|
||||
|
||||
c.Policy = authorization.OneFactor
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
|
||||
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
|
||||
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
|
||||
c.Policy = oidc.ClientPolicy{DefaultPolicy: authorization.OneFactor}
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated, authorization.Subject{}))
|
||||
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor, authorization.Subject{}))
|
||||
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor, authorization.Subject{}))
|
||||
|
||||
c.Policy = authorization.TwoFactor
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
|
||||
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
|
||||
c.Policy = oidc.ClientPolicy{DefaultPolicy: authorization.TwoFactor}
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated, authorization.Subject{}))
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor, authorization.Subject{}))
|
||||
assert.True(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor, authorization.Subject{}))
|
||||
|
||||
c.Policy = authorization.Denied
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor))
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor))
|
||||
c.Policy = oidc.ClientPolicy{DefaultPolicy: authorization.Denied}
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated, authorization.Subject{}))
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.OneFactor, authorization.Subject{}))
|
||||
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.TwoFactor, authorization.Subject{}))
|
||||
}
|
||||
|
||||
func TestClient_GetConsentResponseBody(t *testing.T) {
|
||||
|
@ -446,7 +446,7 @@ func TestNewClientPKCE(t *testing.T) {
|
|||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client := oidc.NewClient(tc.have)
|
||||
client := oidc.NewClient(tc.have, &schema.OpenIDConnect{})
|
||||
|
||||
assert.Equal(t, tc.expectedEnforcePKCE, client.GetPKCEEnforcement())
|
||||
assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.GetPKCEChallengeMethodEnforcement())
|
||||
|
@ -511,7 +511,7 @@ func TestNewClientPAR(t *testing.T) {
|
|||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client := oidc.NewClient(tc.have)
|
||||
client := oidc.NewClient(tc.have, &schema.OpenIDConnect{})
|
||||
|
||||
assert.Equal(t, tc.expected, client.GetPAREnforcement())
|
||||
|
||||
|
@ -575,7 +575,7 @@ func TestNewClientResponseModes(t *testing.T) {
|
|||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
client := oidc.NewClient(tc.have)
|
||||
client := oidc.NewClient(tc.have, &schema.OpenIDConnect{})
|
||||
|
||||
assert.Equal(t, tc.expected, client.GetResponseModes())
|
||||
|
||||
|
@ -615,7 +615,7 @@ func TestNewClient_JSONWebKeySetURI(t *testing.T) {
|
|||
PublicKeys: schema.OpenIDConnectClientPublicKeys{
|
||||
URI: MustParseRequestURI("https://google.com"),
|
||||
},
|
||||
})
|
||||
}, &schema.OpenIDConnect{})
|
||||
|
||||
require.NotNil(t, client)
|
||||
|
||||
|
@ -630,7 +630,7 @@ func TestNewClient_JSONWebKeySetURI(t *testing.T) {
|
|||
PublicKeys: schema.OpenIDConnectClientPublicKeys{
|
||||
URI: nil,
|
||||
},
|
||||
})
|
||||
}, &schema.OpenIDConnect{})
|
||||
|
||||
require.NotNil(t, client)
|
||||
|
||||
|
|
|
@ -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.")
|
||||
)
|
||||
|
|
|
@ -31,7 +31,7 @@ func NewStore(config *schema.OpenIDConnect, provider storage.Provider) (store *S
|
|||
policy := authorization.NewLevel(client.Policy)
|
||||
logger.Debugf("Registering client %s with policy %s (%v)", client.ID, client.Policy, policy)
|
||||
|
||||
store.clients[client.ID] = NewClient(client)
|
||||
store.clients[client.ID] = NewClient(client, config)
|
||||
}
|
||||
|
||||
return store
|
||||
|
@ -65,16 +65,6 @@ func (s *Store) GetSubject(ctx context.Context, sectorID, username string) (subj
|
|||
return opaqueID.Identifier, nil
|
||||
}
|
||||
|
||||
// GetClientPolicy retrieves the policy from the client with the matching provided id.
|
||||
func (s *Store) GetClientPolicy(id string) (level authorization.Level) {
|
||||
client, err := s.GetFullClient(id)
|
||||
if err != nil {
|
||||
return authorization.TwoFactor
|
||||
}
|
||||
|
||||
return client.GetAuthorizationPolicy()
|
||||
}
|
||||
|
||||
// GetFullClient returns a fosite.Client asserted as an Client matching the provided id.
|
||||
func (s *Store) GetFullClient(id string) (client Client, err error) {
|
||||
client, ok := s.clients[id]
|
||||
|
|
|
@ -23,38 +23,6 @@ import (
|
|||
"github.com/authelia/authelia/v4/internal/storage"
|
||||
)
|
||||
|
||||
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
||||
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClient{
|
||||
{
|
||||
ID: myclient,
|
||||
Description: myclientdesc,
|
||||
Policy: onefactor,
|
||||
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
|
||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||
},
|
||||
{
|
||||
ID: "myotherclient",
|
||||
Description: myclientdesc,
|
||||
Policy: twofactor,
|
||||
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile},
|
||||
Secret: tOpenIDConnectPlainTextClientSecret,
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
policyOne := s.GetClientPolicy(myclient)
|
||||
assert.Equal(t, authorization.OneFactor, policyOne)
|
||||
|
||||
policyTwo := s.GetClientPolicy("myotherclient")
|
||||
assert.Equal(t, authorization.TwoFactor, policyTwo)
|
||||
|
||||
policyInvalid := s.GetClientPolicy("invalidclient")
|
||||
assert.Equal(t, authorization.TwoFactor, policyInvalid)
|
||||
}
|
||||
|
||||
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
||||
s := oidc.NewStore(&schema.OpenIDConnect{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
|
@ -106,7 +74,7 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
|||
assert.Equal(t, fosite.Arguments([]string{oidc.GrantTypeAuthorizationCode}), client.GetGrantTypes())
|
||||
assert.Equal(t, fosite.Arguments([]string{oidc.ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes())
|
||||
assert.Equal(t, []string(nil), client.GetRedirectURIs())
|
||||
assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy())
|
||||
assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy(authorization.Subject{}))
|
||||
assert.Equal(t, "$plaintext$client-secret", client.GetSecret().Encode())
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/ory/fosite/handler/openid"
|
||||
"github.com/ory/fosite/token/jwt"
|
||||
"github.com/ory/herodot"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/authentication"
|
||||
"github.com/authelia/authelia/v4/internal/authorization"
|
||||
|
@ -127,7 +127,7 @@ type BaseClient struct {
|
|||
UserinfoSigningAlg string
|
||||
UserinfoSigningKeyID string
|
||||
|
||||
Policy authorization.Level
|
||||
Policy ClientPolicy
|
||||
|
||||
Consent ClientConsent
|
||||
}
|
||||
|
@ -164,14 +164,14 @@ type Client interface {
|
|||
GetPKCEEnforcement() bool
|
||||
GetPKCEChallengeMethodEnforcement() bool
|
||||
GetPKCEChallengeMethod() string
|
||||
GetAuthorizationPolicy() authorization.Level
|
||||
GetConsentPolicy() ClientConsent
|
||||
|
||||
IsAuthenticationLevelSufficient(level authentication.Level) bool
|
||||
|
||||
ValidatePKCEPolicy(r fosite.Requester) (err error)
|
||||
ValidatePARPolicy(r fosite.Requester, prefix string) (err error)
|
||||
ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error)
|
||||
|
||||
GetConsentPolicy() ClientConsent
|
||||
IsAuthenticationLevelSufficient(level authentication.Level, subject authorization.Subject) bool
|
||||
GetAuthorizationPolicy(subject authorization.Subject) authorization.Level
|
||||
}
|
||||
|
||||
// NewClientConsent converts the schema.OpenIDConnectClientConsentConfig into a oidc.ClientConsent.
|
||||
|
|
Loading…
Reference in New Issue