Compare commits

...

4 Commits

Author SHA1 Message Date
James Elliott 33fdacb6e1
Merge branch 'master' into feat-oidc-policies 2023-06-20 15:46:19 +10:00
James Elliott f290fd90b1
feat: denied
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-06-19 12:02:38 +10:00
James Elliott 2f9da2b7e0
feat(oidc): per-client auth policy applied per-subject
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-06-19 12:02:38 +10:00
James Elliott 5e5eead729
feat(oidc): per-client auth policy applied per-subject
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-06-19 12:02:38 +10:00
17 changed files with 266 additions and 126 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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",

View File

@ -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'

View File

@ -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 {

View File

@ -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('\\')

View File

@ -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."))

View File

@ -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)

View File

@ -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()

View File

@ -221,13 +221,10 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
return
}
if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) {
ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.GetID())
ctx.ReplyOK()
return
}
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
@ -249,6 +246,12 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) {
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
}
}
func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, bannedUntil *time.Time, username string, authType string, errAuth error) (err error) {

View File

@ -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()

View File

@ -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)
}

View File

@ -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)

View File

@ -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.")
)

View File

@ -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]

View File

@ -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())
}

View File

@ -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.