summaryrefslogtreecommitdiff
path: root/internal/configuration/validator
diff options
context:
space:
mode:
Diffstat (limited to 'internal/configuration/validator')
-rw-r--r--internal/configuration/validator/access_control.go2
-rw-r--r--internal/configuration/validator/const.go56
-rw-r--r--internal/configuration/validator/identity_providers.go158
-rw-r--r--internal/configuration/validator/identity_providers_test.go388
-rw-r--r--internal/configuration/validator/server.go53
-rw-r--r--internal/configuration/validator/server_test.go81
-rw-r--r--internal/configuration/validator/util.go2
7 files changed, 625 insertions, 115 deletions
diff --git a/internal/configuration/validator/access_control.go b/internal/configuration/validator/access_control.go
index 9b92eff80..9b7db2bea 100644
--- a/internal/configuration/validator/access_control.go
+++ b/internal/configuration/validator/access_control.go
@@ -18,7 +18,7 @@ func IsPolicyValid(policy string) (isValid bool) {
// IsSubjectValid check if a subject is valid.
func IsSubjectValid(subject string) (isValid bool) {
- return subject == "" || strings.HasPrefix(subject, "user:") || strings.HasPrefix(subject, "group:")
+ return subject == "" || strings.HasPrefix(subject, "user:") || strings.HasPrefix(subject, "group:") || strings.HasPrefix(subject, "oauth2:client:")
}
// IsNetworkGroupValid check if a network group is valid.
diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go
index a2c636ece..d212ab9ce 100644
--- a/internal/configuration/validator/const.go
+++ b/internal/configuration/validator/const.go
@@ -159,7 +159,7 @@ const (
const (
errFmtOIDCProviderNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
"more clients configured"
- errFmtOIDCProviderNoPrivateKey = "identity_providers: oidc: option `issuer_private_keys` or 'issuer_private_key' is required"
+ errFmtOIDCProviderNoPrivateKey = "identity_providers: oidc: option `issuer_private_keys` is required"
errFmtOIDCProviderEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
"'public_clients_only' or 'always', but it's configured as '%s'"
errFmtOIDCProviderInsecureParameterEntropy = "identity_providers: oidc: option 'minimum_parameter_entropy' is "
@@ -196,6 +196,10 @@ const (
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions %s"
errFmtOIDCClientsDeprecated = "identity_providers: oidc: clients: warnings for clients above indicate deprecated functionality and it's strongly suggested these issues are checked and fixed if they're legitimate issues or reported if they are not as in a future version these warnings will become errors"
+ errFmtMustOnlyHaveValues = "'%s' must only have the values %s "
+ errFmtMustBeConfiguredAs = "'%s' must be configured as %s "
+ errFmtOIDCClientOption = "identity_providers: oidc: clients: client '%s': option "
+ errFmtOIDCWhenScope = "when configured with scope '%s'"
errFmtOIDCClientInvalidSecretIs = errFmtOIDCClientOption + "'secret' is "
errFmtOIDCClientInvalidSecret = errFmtOIDCClientInvalidSecretIs + "required"
errFmtOIDCClientInvalidSecretPlainText = errFmtOIDCClientInvalidSecretIs + "plaintext but for clients not using the 'token_endpoint_auth_method' of 'client_secret_jwt' it should be a hashed value as plaintext values are deprecated with the exception of 'client_secret_jwt' and will be removed when oidc becomes stable"
@@ -204,7 +208,9 @@ const (
"required to be empty when option 'public' is true"
errFmtOIDCClientPublicInvalidSecretClientAuthMethod = errFmtOIDCClientInvalidSecretIs +
"required to be empty when option 'token_endpoint_auth_method' is configured as '%s'"
- errFmtOIDCClientOption = "identity_providers: oidc: clients: client '%s': option "
+ errFmtOIDCClientIDTooLong = errFmtOIDCClientOption + "'id' must not be more than 100 characters but it has %d characters"
+ errFmtOIDCClientIDInvalidCharacters = errFmtOIDCClientOption + "'id' must only contain RFC3986 unreserved characters"
+
errFmtOIDCClientRedirectURIHas = errFmtOIDCClientOption + "'redirect_uris' has "
errFmtOIDCClientRedirectURICantBeParsed = errFmtOIDCClientRedirectURIHas +
"an invalid value: redirect uri '%s' could not be parsed: %v"
@@ -215,10 +221,19 @@ const (
"an invalid value: redirect uri '%s' must have a scheme but it's absent"
errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: clients: client '%s': consent: option 'mode' must be one of " +
"%s but it's configured as '%s'"
- errFmtOIDCClientInvalidEntries = errFmtOIDCClientOption + "'%s' must only have the values " +
- "%s but the values %s are present"
+ errFmtOIDCClientInvalidEntries = errFmtOIDCClientOption + errFmtMustOnlyHaveValues +
+ "but the values %s are present"
+ errFmtOIDCClientUnknownScopeEntries = errFmtOIDCClientOption + "'%s' only expects the values " +
+ "%s but the unknown values %s are present and should generally only be used if a particular client requires a scope outside of our standard scopes"
+ errFmtOIDCClientInvalidEntriesScope = errFmtOIDCClientOption + errFmtMustOnlyHaveValues +
+ errFmtOIDCWhenScope + " but the values %s are present"
+ errFmtOIDCClientEmptyEntriesScope = errFmtOIDCClientOption + errFmtMustOnlyHaveValues +
+ errFmtOIDCWhenScope + " but it's not configured"
+ errFmtOIDCClientOptionRequiredScope = errFmtOIDCClientOption + "'%s' must be configured " + errFmtOIDCWhenScope + " but it's absent"
+ errFmtOIDCClientOptionMustScope = errFmtOIDCClientOption + errFmtMustBeConfiguredAs + errFmtOIDCWhenScope + " but it's configured as '%s'"
+ errFmtOIDCClientOptionMustScopeClientType = errFmtOIDCClientOption + errFmtMustBeConfiguredAs + errFmtOIDCWhenScope + " and the '%s' client type but it's configured as '%s'"
errFmtOIDCClientInvalidEntriesClientCredentials = errFmtOIDCClientOption + "'scopes' has the values " +
- "%s however when exclusively utilizing the 'client_credentials' value for the 'grant_types' the values %s are not allowed"
+ "%s however when utilizing the 'client_credentials' value for the 'grant_types' the values %s are not allowed"
errFmtOIDCClientInvalidEntryDuplicates = errFmtOIDCClientOption + "'%s' must have unique values but the values %s are duplicated"
errFmtOIDCClientInvalidValue = errFmtOIDCClientOption +
"'%s' must be one of %s but it's configured as '%s'"
@@ -367,11 +382,14 @@ const (
errFmtServerPathNotEndForwardSlash = "server: option 'address' must not and with a forward slash but it's configured as '%s'"
errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters"
- errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of %s but it's configured as '%s'"
- errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of %s but it's configured as '%s'"
- errFmtServerEndpointsAuthzStrategyDuplicate = "server: endpoints: authz: %s: authn_strategies: duplicate strategy name detected with name '%s'"
- errFmtServerEndpointsAuthzPrefixDuplicate = "server: endpoints: authz: %s: endpoint starts with the same prefix as the '%s' endpoint with the '%s' implementation which accepts prefixes as part of its implementation"
- errFmtServerEndpointsAuthzInvalidName = "server: endpoints: authz: %s: contains invalid characters"
+ errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of %s but it's configured as '%s'"
+ errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of %s but it's configured as '%s'"
+ errFmtServerEndpointsAuthzSchemes = "server: endpoints: authz: %s: authn_strategies: strategy #%d (%s): option 'schemes' must only include the values %s but has '%s'"
+ errFmtServerEndpointsAuthzSchemesInvalidForStrategy = "server: endpoints: authz: %s: authn_strategies: strategy #%d (%s): option 'schemes' is not valid for the strategy"
+ errFmtServerEndpointsAuthzStrategyNoName = "server: endpoints: authz: %s: authn_strategies: strategy #%d: option 'name' must be configured"
+ errFmtServerEndpointsAuthzStrategyDuplicate = "server: endpoints: authz: %s: authn_strategies: duplicate strategy name detected with name '%s'"
+ errFmtServerEndpointsAuthzPrefixDuplicate = "server: endpoints: authz: %s: endpoint starts with the same prefix as the '%s' endpoint with the '%s' implementation which accepts prefixes as part of its implementation"
+ errFmtServerEndpointsAuthzInvalidName = "server: endpoints: authz: %s: contains invalid characters"
errFmtServerEndpointsAuthzLegacyInvalidImplementation = "server: endpoints: authz: %s: option 'implementation' is invalid: the endpoint with the name 'legacy' must use the 'Legacy' implementation"
)
@@ -421,9 +439,7 @@ const (
)
const (
- legacy = "legacy"
- authzImplementationLegacy = "Legacy"
- authzImplementationExtAuthz = "ExtAuthz"
+ legacy = "legacy"
)
const (
@@ -431,8 +447,10 @@ const (
)
var (
- validAuthzImplementations = []string{"AuthRequest", "ForwardAuth", authzImplementationExtAuthz, authzImplementationLegacy}
- validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"}
+ validAuthzImplementations = []string{schema.AuthzImplementationAuthRequest, schema.AuthzImplementationForwardAuth, schema.AuthzImplementationExtAuthz, schema.AuthzImplementationLegacy}
+ validAuthzAuthnStrategies = []string{schema.AuthzStrategyHeaderCookieSession, schema.AuthzStrategyHeaderAuthorization, schema.AuthzStrategyHeaderProxyAuthorization, schema.AuthzStrategyHeaderAuthRequestProxyAuthorization, schema.AuthzStrategyHeaderLegacy}
+ validAuthzAuthnHeaderStrategies = []string{schema.AuthzStrategyHeaderAuthorization, schema.AuthzStrategyHeaderProxyAuthorization, schema.AuthzStrategyHeaderAuthRequestProxyAuthorization}
+ validAuthzAuthnStrategySchemes = []string{schema.SchemeBasic, schema.SchemeBearer}
)
var (
@@ -514,7 +532,7 @@ var (
var (
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
- validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
+ validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess, oidc.ScopeOffline, oidc.ScopeAutheliaBearerAuthz}
validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment, oidc.ResponseModeJWT, oidc.ResponseModeFormPostJWT, oidc.ResponseModeQueryJWT, oidc.ResponseModeFragmentJWT}
validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
@@ -527,6 +545,11 @@ var (
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodPrivateKeyJWT}
validOIDCClientTokenEndpointAuthSigAlgsClientSecretJWT = []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}
validOIDCIssuerJWKSigningAlgs = []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP521AndSHA512}
+
+ validOIDCClientScopesBearerAuthz = []string{oidc.ScopeOfflineAccess, oidc.ScopeOffline, oidc.ScopeAutheliaBearerAuthz}
+ validOIDCClientResponseModesBearerAuthz = []string{oidc.ResponseModeFormPost, oidc.ResponseModeFormPostJWT}
+ validOIDCClientResponseTypesBearerAuthz = []string{oidc.ResponseTypeAuthorizationCodeFlow}
+ validOIDCClientGrantTypesBearerAuthz = []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken, oidc.GrantTypeClientCredentials}
)
var (
@@ -534,6 +557,7 @@ var (
reDomainCharacters = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)+[a-z0-9]$`)
reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/._-]*)([a-zA-Z]))?$`)
reOpenIDConnectKID = regexp.MustCompile(`^([a-zA-Z0-9](([a-zA-Z0-9._~-]*)([a-zA-Z0-9]))?)?$`)
+ reRFC3986Unreserved = regexp.MustCompile(`^[a-zA-Z0-9._~-]+$`)
)
var replacedKeys = map[string]string{
diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go
index 7b86d1b86..c28bbce8d 100644
--- a/internal/configuration/validator/identity_providers.go
+++ b/internal/configuration/validator/identity_providers.go
@@ -7,7 +7,6 @@ import (
"net/url"
"sort"
"strconv"
- "strings"
"github.com/ory/fosite"
@@ -120,6 +119,8 @@ func validateOIDCLifespans(config *schema.IdentityProvidersOpenIDConnect, _ *sch
func validateOIDCIssuer(config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator) {
switch {
+ case len(config.IssuerPrivateKeys) != 0 && (config.IssuerPrivateKey != nil || config.IssuerCertificateChain.HasCertificates()):
+ validator.Push(fmt.Errorf("identity_providers: oidc: option `issuer_private_keys` must not be configured at the same time as 'issuer_private_key' or 'issuer_certificate_chain'"))
case config.IssuerPrivateKey != nil:
validateOIDCIssuerPrivateKey(config)
@@ -347,19 +348,26 @@ func validateOIDCClients(config *schema.IdentityProvidersOpenIDConnect, validato
errDeprecatedFunc := func() { errDeprecated = true }
for c, client := range config.Clients {
- if client.ID == "" {
+ n := len(client.ID)
+
+ switch {
+ case n == 0:
blankClientIDs = append(blankClientIDs, "#"+strconv.Itoa(c+1))
- } else {
+ case n > 100:
+ validator.Push(fmt.Errorf(errFmtOIDCClientIDTooLong, client.ID, n))
+ case !reRFC3986Unreserved.MatchString(client.ID):
+ validator.Push(fmt.Errorf(errFmtOIDCClientIDInvalidCharacters, client.ID))
+ default:
if client.Description == "" {
config.Clients[c].Description = client.ID
}
- if id := strings.ToLower(client.ID); utils.IsStringInSlice(id, clientIDs) {
- if !utils.IsStringInSlice(id, duplicateClientIDs) {
- duplicateClientIDs = append(duplicateClientIDs, id)
+ if utils.IsStringInSlice(client.ID, clientIDs) {
+ if !utils.IsStringInSlice(client.ID, duplicateClientIDs) {
+ duplicateClientIDs = append(duplicateClientIDs, client.ID)
}
} else {
- clientIDs = append(clientIDs, id)
+ clientIDs = append(clientIDs, client.ID)
}
}
@@ -380,7 +388,15 @@ func validateOIDCClients(config *schema.IdentityProvidersOpenIDConnect, validato
}
func validateOIDCClient(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, errDeprecatedFunc func()) {
+ ccg := utils.IsStringInSlice(oidc.GrantTypeClientCredentials, config.Clients[c].GrantTypes)
+
switch {
+ case ccg:
+ if config.Clients[c].AuthorizationPolicy == "" {
+ config.Clients[c].AuthorizationPolicy = policyOneFactor
+ } else if config.Clients[c].AuthorizationPolicy != policyOneFactor {
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "authorization_policy", strJoinOr([]string{policyOneFactor}), config.Clients[c].AuthorizationPolicy))
+ }
case config.Clients[c].AuthorizationPolicy == "":
config.Clients[c].AuthorizationPolicy = schema.DefaultOpenIDConnectClientConfiguration.AuthorizationPolicy
case utils.IsStringInSlice(config.Clients[c].AuthorizationPolicy, config.Discovery.AuthorizationPolicies):
@@ -416,12 +432,14 @@ func validateOIDCClient(c int, config *schema.IdentityProvidersOpenIDConnect, va
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, attrOIDCRequestedAudienceMode, strJoinOr([]string{oidc.ClientRequestedAudienceModeExplicit.String(), oidc.ClientRequestedAudienceModeImplicit.String()}), config.Clients[c].RequestedAudienceMode))
}
- validateOIDCClientConsentMode(c, config, validator)
+ setDefaults := validateOIDCClientScopesSpecialBearerAuthz(c, config, ccg, validator)
- validateOIDCClientScopes(c, config, validator, errDeprecatedFunc)
- validateOIDCClientResponseTypes(c, config, validator, errDeprecatedFunc)
- validateOIDCClientResponseModes(c, config, validator, errDeprecatedFunc)
- validateOIDCClientGrantTypes(c, config, validator, errDeprecatedFunc)
+ validateOIDCClientConsentMode(c, config, validator, setDefaults)
+
+ validateOIDCClientScopes(c, config, validator, ccg, errDeprecatedFunc)
+ validateOIDCClientResponseTypes(c, config, validator, setDefaults, errDeprecatedFunc)
+ validateOIDCClientResponseModes(c, config, validator, setDefaults, errDeprecatedFunc)
+ validateOIDCClientGrantTypes(c, config, validator, setDefaults, errDeprecatedFunc)
validateOIDCClientRedirectURIs(c, config, validator, errDeprecatedFunc)
validateOIDDClientSigningAlgs(c, config, validator)
@@ -569,9 +587,13 @@ func validateOIDCClientSectorIdentifier(c int, config *schema.IdentityProvidersO
}
}
-func validateOIDCClientConsentMode(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator) {
+func validateOIDCClientConsentMode(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, setDefaults bool) {
switch {
case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", auto}):
+ if !setDefaults {
+ break
+ }
+
if config.Clients[c].ConsentPreConfiguredDuration != nil {
config.Clients[c].ConsentMode = oidc.ClientConsentModePreConfigured.String()
} else {
@@ -588,8 +610,8 @@ func validateOIDCClientConsentMode(c int, config *schema.IdentityProvidersOpenID
}
}
-func validateOIDCClientScopes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, errDeprecatedFunc func()) {
- if len(config.Clients[c].Scopes) == 0 {
+func validateOIDCClientScopes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, ccg bool, errDeprecatedFunc func()) {
+ if len(config.Clients[c].Scopes) == 0 && !ccg {
config.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes
}
@@ -601,16 +623,10 @@ func validateOIDCClientScopes(c int, config *schema.IdentityProvidersOpenIDConne
validator.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCScopes, strJoinAnd(duplicates)))
}
- if utils.IsStringInSlice(oidc.GrantTypeClientCredentials, config.Clients[c].GrantTypes) {
+ if ccg {
validateOIDCClientScopesClientCredentialsGrant(c, config, validator)
- } else {
- if !utils.IsStringInSlice(oidc.ScopeOpenID, config.Clients[c].Scopes) {
- config.Clients[c].Scopes = append([]string{oidc.ScopeOpenID}, config.Clients[c].Scopes...)
- }
-
- if len(invalid) != 0 {
- validator.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCScopes, strJoinOr(validOIDCClientScopes), strJoinAnd(invalid)))
- }
+ } else if len(invalid) != 0 {
+ validator.PushWarning(fmt.Errorf(errFmtOIDCClientUnknownScopeEntries, config.Clients[c].ID, attrOIDCScopes, strJoinOr(validOIDCClientScopes), strJoinAnd(invalid)))
}
if utils.IsStringSliceContainsAny([]string{oidc.ScopeOfflineAccess, oidc.ScopeOffline}, config.Clients[c].Scopes) &&
@@ -625,11 +641,81 @@ func validateOIDCClientScopes(c int, config *schema.IdentityProvidersOpenIDConne
}
}
-func validateOIDCClientScopesClientCredentialsGrant(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator) {
- if len(config.Clients[c].GrantTypes) != 1 {
- return
+//nolint:gocyclo
+func validateOIDCClientScopesSpecialBearerAuthz(c int, config *schema.IdentityProvidersOpenIDConnect, ccg bool, validator *schema.StructValidator) bool {
+ if !utils.IsStringInSlice(oidc.ScopeAutheliaBearerAuthz, config.Clients[c].Scopes) {
+ return true
+ }
+
+ if !config.Discovery.BearerAuthorization {
+ config.Discovery.BearerAuthorization = true
+ }
+
+ if !utils.IsStringSliceContainsAll(config.Clients[c].Scopes, validOIDCClientScopesBearerAuthz) {
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidEntriesScope, config.Clients[c].ID, attrOIDCScopes, strJoinAnd(validOIDCClientScopesBearerAuthz), oidc.ScopeAutheliaBearerAuthz, strJoinAnd(config.Clients[c].Scopes)))
}
+ if len(config.Clients[c].GrantTypes) == 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientEmptyEntriesScope, config.Clients[c].ID, attrOIDCGrantTypes, strJoinAnd(validOIDCClientGrantTypesBearerAuthz), oidc.ScopeAutheliaBearerAuthz))
+ } else {
+ invalid, _ := validateList(config.Clients[c].GrantTypes, validOIDCClientGrantTypesBearerAuthz, false)
+
+ if len(invalid) != 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidEntriesScope, config.Clients[c].ID, attrOIDCGrantTypes, strJoinAnd(validOIDCClientGrantTypesBearerAuthz), oidc.ScopeAutheliaBearerAuthz, strJoinAnd(invalid)))
+ }
+ }
+
+ if len(config.Clients[c].Audience) == 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionRequiredScope, config.Clients[c].ID, "audience", oidc.ScopeAutheliaBearerAuthz))
+ }
+
+ if !ccg {
+ if !config.Clients[c].EnforcePAR {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScope, config.Clients[c].ID, "enforce_par", "'true'", oidc.ScopeAutheliaBearerAuthz, "false"))
+ }
+
+ if !config.Clients[c].EnforcePKCE {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScope, config.Clients[c].ID, "enforce_pkce", "'true'", oidc.ScopeAutheliaBearerAuthz, "false"))
+ } else if config.Clients[c].PKCEChallengeMethod != oidc.PKCEChallengeMethodSHA256 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScope, config.Clients[c].ID, attrOIDCPKCEChallengeMethod, "'"+oidc.PKCEChallengeMethodSHA256+"'", oidc.ScopeAutheliaBearerAuthz, config.Clients[c].PKCEChallengeMethod))
+ }
+
+ if config.Clients[c].ConsentMode != oidc.ClientConsentModeExplicit.String() {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScope, config.Clients[c].ID, "consent_mode", "'"+oidc.ClientConsentModeExplicit.String()+"'", oidc.ScopeAutheliaBearerAuthz, config.Clients[c].ConsentMode))
+ }
+
+ if len(config.Clients[c].ResponseTypes) == 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientEmptyEntriesScope, config.Clients[c].ID, attrOIDCResponseTypes, strJoinAnd(validOIDCClientResponseTypesBearerAuthz), oidc.ScopeAutheliaBearerAuthz))
+ } else if !utils.IsStringSliceContainsAll(config.Clients[c].ResponseTypes, validOIDCClientResponseTypesBearerAuthz) ||
+ !utils.IsStringSliceContainsAny(config.Clients[c].ResponseTypes, validOIDCClientResponseTypesBearerAuthz) {
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidEntriesScope, config.Clients[c].ID, attrOIDCResponseTypes, strJoinAnd(validOIDCClientResponseTypesBearerAuthz), oidc.ScopeAutheliaBearerAuthz, strJoinAnd(config.Clients[c].ResponseTypes)))
+ }
+
+ if len(config.Clients[c].ResponseModes) == 0 {
+ validator.Push(fmt.Errorf(errFmtOIDCClientEmptyEntriesScope, config.Clients[c].ID, attrOIDCResponseModes, strJoinAnd(validOIDCClientResponseModesBearerAuthz), oidc.ScopeAutheliaBearerAuthz))
+ } else if !utils.IsStringSliceContainsAll(config.Clients[c].ResponseModes, validOIDCClientResponseModesBearerAuthz) ||
+ !utils.IsStringSliceContainsAny(config.Clients[c].ResponseModes, validOIDCClientResponseModesBearerAuthz) {
+ validator.Push(fmt.Errorf(errFmtOIDCClientInvalidEntriesScope, config.Clients[c].ID, attrOIDCResponseModes, strJoinAnd(validOIDCClientResponseModesBearerAuthz), oidc.ScopeAutheliaBearerAuthz, strJoinAnd(config.Clients[c].ResponseModes)))
+ }
+ }
+
+ if config.Clients[c].Public {
+ if config.Clients[c].TokenEndpointAuthMethod != oidc.ClientAuthMethodNone {
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScopeClientType, config.Clients[c].ID, attrOIDCTokenAuthMethod, "'"+oidc.ClientAuthMethodNone+"'", oidc.ScopeAutheliaBearerAuthz, "public", config.Clients[c].TokenEndpointAuthMethod))
+ }
+ } else {
+ switch config.Clients[c].TokenEndpointAuthMethod {
+ case oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretJWT, oidc.ClientAuthMethodPrivateKeyJWT:
+ break
+ default:
+ validator.Push(fmt.Errorf(errFmtOIDCClientOptionMustScopeClientType, config.Clients[c].ID, attrOIDCTokenAuthMethod, strJoinOr([]string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretJWT, oidc.ClientAuthMethodPrivateKeyJWT}), oidc.ScopeAutheliaBearerAuthz, "confidential", config.Clients[c].TokenEndpointAuthMethod))
+ }
+ }
+
+ return false
+}
+
+func validateOIDCClientScopesClientCredentialsGrant(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator) {
invalid := validateListNotAllowed(config.Clients[c].Scopes, []string{oidc.ScopeOpenID, oidc.ScopeOffline, oidc.ScopeOfflineAccess})
if len(invalid) > 0 {
@@ -637,8 +723,12 @@ func validateOIDCClientScopesClientCredentialsGrant(c int, config *schema.Identi
}
}
-func validateOIDCClientResponseTypes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, errDeprecatedFunc func()) {
+func validateOIDCClientResponseTypes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, setDefaults bool, errDeprecatedFunc func()) {
if len(config.Clients[c].ResponseTypes) == 0 {
+ if !setDefaults {
+ return
+ }
+
config.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes
}
@@ -655,8 +745,12 @@ func validateOIDCClientResponseTypes(c int, config *schema.IdentityProvidersOpen
}
}
-func validateOIDCClientResponseModes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, errDeprecatedFunc func()) {
+func validateOIDCClientResponseModes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, setDefaults bool, errDeprecatedFunc func()) {
if len(config.Clients[c].ResponseModes) == 0 {
+ if !setDefaults {
+ return
+ }
+
config.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes
for _, responseType := range config.Clients[c].ResponseTypes {
@@ -687,8 +781,12 @@ func validateOIDCClientResponseModes(c int, config *schema.IdentityProvidersOpen
}
}
-func validateOIDCClientGrantTypes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, errDeprecatedFunc func()) {
+func validateOIDCClientGrantTypes(c int, config *schema.IdentityProvidersOpenIDConnect, validator *schema.StructValidator, setDefaults bool, errDeprecatedFunc func()) {
if len(config.Clients[c].GrantTypes) == 0 {
+ if !setDefaults {
+ return
+ }
+
validateOIDCClientGrantTypesSetDefaults(c, config)
}
diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go
index b3e276488..a5af2a01d 100644
--- a/internal/configuration/validator/identity_providers_test.go
+++ b/internal/configuration/validator/identity_providers_test.go
@@ -34,7 +34,31 @@ func TestShouldRaiseErrorWhenInvalidOIDCServerConfiguration(t *testing.T) {
require.Len(t, validator.Errors(), 2)
- assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option `issuer_private_keys` or 'issuer_private_key' is required")
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option `issuer_private_keys` is required")
+ assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured")
+}
+
+func TestShouldRaiseErrorWhenInvalidOIDCServerConfigurationBothKeyTypesSpecified(t *testing.T) {
+ validator := schema.NewStructValidator()
+ config := &schema.IdentityProviders{
+ OIDC: &schema.IdentityProvidersOpenIDConnect{
+ HMACSecret: "abc",
+ IssuerPrivateKey: keyRSA2048,
+ IssuerPrivateKeys: []schema.JWK{
+ {
+ Use: "sig",
+ Algorithm: "RS256",
+ Key: keyRSA4096,
+ },
+ },
+ },
+ }
+
+ ValidateIdentityProviders(config, validator)
+
+ require.Len(t, validator.Errors(), 2)
+
+ assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option `issuer_private_keys` must not be configured at the same time as 'issuer_private_key' or 'issuer_certificate_chain'")
assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured")
}
@@ -188,6 +212,30 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
{
+ name: "BadIDTooLong",
+ clients: []schema.IdentityProvidersOpenIDConnectClient{
+ {
+ ID: "abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123",
+ Secret: tOpenIDConnectPlainTextClientSecret,
+ },
+ },
+ errors: []string{
+ "identity_providers: oidc: clients: client 'abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123': option 'id' must not be more than 100 characters but it has 108 characters",
+ },
+ },
+ {
+ name: "BadIDInvalidCharacters",
+ clients: []schema.IdentityProvidersOpenIDConnectClient{
+ {
+ ID: "@!#!@$!@#*()!&@%*(!^@#*()!&@^%!(_@#&",
+ Secret: tOpenIDConnectPlainTextClientSecret,
+ },
+ },
+ errors: []string{
+ "identity_providers: oidc: clients: client '@!#!@$!@#*()!&@%*(!^@#*()!&@^%!(_@#&': option 'id' must only contain RFC3986 unreserved characters",
+ },
+ },
+ {
name: "InvalidPolicy",
clients: []schema.IdentityProvidersOpenIDConnectClient{
{
@@ -204,6 +252,25 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
},
},
{
+ name: "InvalidPolicyCCG",
+ clients: []schema.IdentityProvidersOpenIDConnectClient{
+ {
+ ID: "client-1",
+ Secret: tOpenIDConnectPlainTextClientSecret,
+ AuthorizationPolicy: "a-policy",
+ RedirectURIs: []string{
+ "https://google.com",
+ },
+ GrantTypes: []string{
+ oidc.GrantTypeClientCredentials,
+ },
+ },
+ },
+ errors: []string{
+ "identity_providers: oidc: clients: client 'client-1': option 'authorization_policy' must be one of 'one_factor' but it's configured as 'a-policy'",
+ },
+ },
+ {
name: "ClientIDDuplicated",
clients: []schema.IdentityProvidersOpenIDConnectClient{
{
@@ -453,32 +520,6 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
}
}
-func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) {
- validator := schema.NewStructValidator()
- config := &schema.IdentityProviders{
- OIDC: &schema.IdentityProvidersOpenIDConnect{
- HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
- IssuerPrivateKey: keyRSA2048,
- Clients: []schema.IdentityProvidersOpenIDConnectClient{
- {
- ID: "good_id",
- Secret: tOpenIDConnectPlainTextClientSecret,
- AuthorizationPolicy: "two_factor",
- Scopes: []string{"openid", "bad_scope"},
- RedirectURIs: []string{
- "https://google.com/callback",
- },
- },
- },
- },
- }
-
- ValidateIdentityProviders(config, validator)
-
- require.Len(t, validator.Errors(), 1)
- assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: clients: client 'good_id': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'bad_scope' are present")
-}
-
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProviders{
@@ -858,7 +899,7 @@ func TestValidateOIDCClients(t *testing.T) {
nil,
},
{
- "ShouldIncludeMinimalScope",
+ "ShouldNotIncludeOldMinimalScope",
nil,
nil,
tcv{
@@ -868,7 +909,7 @@ func TestValidateOIDCClients(t *testing.T) {
nil,
},
tcv{
- []string{oidc.ScopeOpenID, oidc.ScopeEmail},
+ []string{oidc.ScopeEmail},
[]string{oidc.ResponseTypeAuthorizationCodeFlow},
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
[]string{oidc.GrantTypeAuthorizationCode},
@@ -1031,7 +1072,7 @@ func TestValidateOIDCClients(t *testing.T) {
nil,
},
{
- "ShouldRaiseErrorOnInvalidScopes",
+ "ShouldRaiseWarningOnInvalidScopes",
nil,
nil,
tcv{
@@ -1046,10 +1087,10 @@ func TestValidateOIDCClients(t *testing.T) {
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
[]string{oidc.GrantTypeAuthorizationCode},
},
- nil,
[]string{
- "identity_providers: oidc: clients: client 'test': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'group' are present",
+ "identity_providers: oidc: clients: client 'test': option 'scopes' only expects the values 'openid', 'email', 'profile', 'groups', 'offline_access', 'offline', or 'authelia.bearer.authz' but the unknown values 'group' are present and should generally only be used if a particular client requires a scope outside of our standard scopes",
},
+ nil,
},
{
"ShouldRaiseErrorOnMissingAuthorizationCodeFlowResponseTypeWithRefreshTokenValues",
@@ -1290,33 +1331,8 @@ func TestValidateOIDCClients(t *testing.T) {
"identity_providers: oidc: clients: client 'test': option 'scopes' should only have the values 'offline_access' or 'offline' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes",
},
[]string{
- "identity_providers: oidc: clients: client 'test': option 'scopes' has the values 'openid', 'offline', and 'offline_access' however when exclusively utilizing the 'client_credentials' value for the 'grant_types' the values 'openid', 'offline', or 'offline_access' are not allowed",
- },
- },
- {
- "ShouldNotRestrictRefreshOpenIDScopesWithMultipleGrantTypesAndAllowCustomClientCredentials",
- func(have *schema.IdentityProvidersOpenIDConnect) {
- have.Clients[0].Public = false
- have.Clients[0].Scopes = []string{oidc.ScopeOpenID, oidc.ScopeOffline, oidc.ScopeOfflineAccess, "custom"}
- },
- nil,
- tcv{
- nil,
- nil,
- nil,
- []string{oidc.GrantTypeClientCredentials, oidc.GrantTypeImplicit},
- },
- tcv{
- []string{oidc.ScopeOpenID, oidc.ScopeOffline, oidc.ScopeOfflineAccess, "custom"},
- []string{oidc.ResponseTypeAuthorizationCodeFlow},
- []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
- []string{oidc.GrantTypeClientCredentials, oidc.GrantTypeImplicit},
- },
- []string{
- "identity_providers: oidc: clients: client 'test': option 'scopes' should only have the values 'offline_access' or 'offline' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes",
- "identity_providers: oidc: clients: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'implicit' expects a response type for either the implicit or hybrid flow such as 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the response types are 'code'",
+ "identity_providers: oidc: clients: client 'test': option 'scopes' has the values 'openid', 'offline', and 'offline_access' however when utilizing the 'client_credentials' value for the 'grant_types' the values 'openid', 'offline', or 'offline_access' are not allowed",
},
- nil,
},
{
"ShouldRaiseErrorOnGrantTypeRefreshTokenWithoutScopeOfflineAccess",
@@ -2062,6 +2078,262 @@ func TestValidateOIDCClients(t *testing.T) {
},
},
{
+ "ShouldHandleBearerErrorsMisconfiguredPublicClientType",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: nil,
+ Public: true,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: nil,
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ GrantTypes: []string{oidc.GrantTypeImplicit},
+ ResponseTypes: []string{oidc.ResponseTypeImplicitFlowBoth},
+ ResponseModes: []string{oidc.ResponseModeQuery},
+ AuthorizationPolicy: "",
+ RequestedAudienceMode: "",
+ ConsentMode: oidc.ClientConsentModeImplicit.String(),
+ EnforcePAR: false,
+ EnforcePKCE: false,
+ PKCEChallengeMethod: "",
+ TokenEndpointAuthMethod: "",
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ []string{oidc.ResponseTypeImplicitFlowBoth},
+ []string{oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeImplicit},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: clients: client 'abc': option 'scopes' must only have the values 'offline_access', 'offline', and 'authelia.bearer.authz' when configured with scope 'authelia.bearer.authz' but the values 'authelia.bearer.authz' and 'openid' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'grant_types' must only have the values 'authorization_code', 'refresh_token', and 'client_credentials' when configured with scope 'authelia.bearer.authz' but the values 'implicit' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'audience' must be configured when configured with scope 'authelia.bearer.authz' but it's absent",
+ "identity_providers: oidc: clients: client 'abc': option 'enforce_par' must be configured as 'true' when configured with scope 'authelia.bearer.authz' but it's configured as 'false'",
+ "identity_providers: oidc: clients: client 'abc': option 'enforce_pkce' must be configured as 'true' when configured with scope 'authelia.bearer.authz' but it's configured as 'false'",
+ "identity_providers: oidc: clients: client 'abc': option 'consent_mode' must be configured as 'explicit' when configured with scope 'authelia.bearer.authz' but it's configured as 'implicit'",
+ "identity_providers: oidc: clients: client 'abc': option 'response_types' must only have the values 'code' when configured with scope 'authelia.bearer.authz' but the values 'id_token token' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'response_modes' must only have the values 'form_post' and 'form_post.jwt' when configured with scope 'authelia.bearer.authz' but the values 'query' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'token_endpoint_auth_method' must be configured as 'none' when configured with scope 'authelia.bearer.authz' and the 'public' client type but it's configured as ''",
+ },
+ },
+ {
+ "ShouldHandleBearerErrorsMisconfiguredConfidentialClientType",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: tOpenIDConnectPBKDF2ClientSecret,
+ Public: false,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: nil,
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ GrantTypes: []string{oidc.GrantTypeImplicit},
+ ResponseTypes: []string{oidc.ResponseTypeImplicitFlowBoth},
+ ResponseModes: []string{oidc.ResponseModeQuery},
+ AuthorizationPolicy: "",
+ RequestedAudienceMode: "",
+ ConsentMode: oidc.ClientConsentModeImplicit.String(),
+ EnforcePAR: false,
+ EnforcePKCE: true,
+ PKCEChallengeMethod: "",
+ TokenEndpointAuthMethod: "",
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ []string{oidc.ResponseTypeImplicitFlowBoth},
+ []string{oidc.ResponseModeQuery},
+ []string{oidc.GrantTypeImplicit},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: clients: client 'abc': option 'scopes' must only have the values 'offline_access', 'offline', and 'authelia.bearer.authz' when configured with scope 'authelia.bearer.authz' but the values 'authelia.bearer.authz' and 'openid' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'grant_types' must only have the values 'authorization_code', 'refresh_token', and 'client_credentials' when configured with scope 'authelia.bearer.authz' but the values 'implicit' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'audience' must be configured when configured with scope 'authelia.bearer.authz' but it's absent",
+ "identity_providers: oidc: clients: client 'abc': option 'enforce_par' must be configured as 'true' when configured with scope 'authelia.bearer.authz' but it's configured as 'false'",
+ "identity_providers: oidc: clients: client 'abc': option 'pkce_challenge_method' must be configured as 'S256' when configured with scope 'authelia.bearer.authz' but it's configured as ''",
+ "identity_providers: oidc: clients: client 'abc': option 'consent_mode' must be configured as 'explicit' when configured with scope 'authelia.bearer.authz' but it's configured as 'implicit'",
+ "identity_providers: oidc: clients: client 'abc': option 'response_types' must only have the values 'code' when configured with scope 'authelia.bearer.authz' but the values 'id_token token' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'response_modes' must only have the values 'form_post' and 'form_post.jwt' when configured with scope 'authelia.bearer.authz' but the values 'query' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'token_endpoint_auth_method' must be configured as 'client_secret_post', 'client_secret_jwt', or 'private_key_jwt' when configured with scope 'authelia.bearer.authz' and the 'confidential' client type but it's configured as ''",
+ },
+ },
+ {
+ "ShouldHandleBearerErrorsMisconfiguredConfidentialClientTypeClientCredentials",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: tOpenIDConnectPBKDF2ClientSecret,
+ Public: false,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: nil,
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ GrantTypes: []string{oidc.GrantTypeClientCredentials},
+ ResponseTypes: nil,
+ ResponseModes: nil,
+ AuthorizationPolicy: "",
+ RequestedAudienceMode: "",
+ ConsentMode: oidc.ClientConsentModeImplicit.String(),
+ EnforcePAR: false,
+ EnforcePKCE: true,
+ PKCEChallengeMethod: "",
+ TokenEndpointAuthMethod: "",
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOpenID},
+ []string(nil),
+ []string(nil),
+ []string{oidc.GrantTypeClientCredentials},
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: clients: client 'abc': option 'scopes' must only have the values 'offline_access', 'offline', and 'authelia.bearer.authz' when configured with scope 'authelia.bearer.authz' but the values 'authelia.bearer.authz' and 'openid' are present",
+ "identity_providers: oidc: clients: client 'abc': option 'audience' must be configured when configured with scope 'authelia.bearer.authz' but it's absent",
+ "identity_providers: oidc: clients: client 'abc': option 'token_endpoint_auth_method' must be configured as 'client_secret_post', 'client_secret_jwt', or 'private_key_jwt' when configured with scope 'authelia.bearer.authz' and the 'confidential' client type but it's configured as ''",
+ "identity_providers: oidc: clients: client 'abc': option 'scopes' has the values 'authelia.bearer.authz' and 'openid' however when utilizing the 'client_credentials' value for the 'grant_types' the values 'openid' are not allowed",
+ },
+ },
+ {
+ "ShouldHandleBearerErrorsNotExplicit",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: nil,
+ Public: true,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: nil,
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz},
+ EnforcePAR: false,
+ EnforcePKCE: false,
+ PKCEChallengeMethod: "",
+ TokenEndpointAuthMethod: "",
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz},
+ nil,
+ nil,
+ nil,
+ },
+ nil,
+ []string{
+ "identity_providers: oidc: clients: client 'abc': option 'grant_types' must only have the values 'authorization_code', 'refresh_token', and 'client_credentials' when configured with scope 'authelia.bearer.authz' but it's not configured",
+ "identity_providers: oidc: clients: client 'abc': option 'audience' must be configured when configured with scope 'authelia.bearer.authz' but it's absent",
+ "identity_providers: oidc: clients: client 'abc': option 'enforce_par' must be configured as 'true' when configured with scope 'authelia.bearer.authz' but it's configured as 'false'",
+ "identity_providers: oidc: clients: client 'abc': option 'enforce_pkce' must be configured as 'true' when configured with scope 'authelia.bearer.authz' but it's configured as 'false'",
+ "identity_providers: oidc: clients: client 'abc': option 'consent_mode' must be configured as 'explicit' when configured with scope 'authelia.bearer.authz' but it's configured as ''",
+ "identity_providers: oidc: clients: client 'abc': option 'response_types' must only have the values 'code' when configured with scope 'authelia.bearer.authz' but it's not configured",
+ "identity_providers: oidc: clients: client 'abc': option 'response_modes' must only have the values 'form_post' and 'form_post.jwt' when configured with scope 'authelia.bearer.authz' but it's not configured",
+ "identity_providers: oidc: clients: client 'abc': option 'token_endpoint_auth_method' must be configured as 'none' when configured with scope 'authelia.bearer.authz' and the 'public' client type but it's configured as ''",
+ },
+ },
+ {
+ "ShouldHandleBearerValidConfidentialClientType",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: tOpenIDConnectPBKDF2ClientSecret,
+ Public: false,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: []string{"https://app.example.com"},
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOfflineAccess},
+ GrantTypes: []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken},
+ ResponseTypes: []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ ResponseModes: []string{oidc.ResponseModeFormPost, oidc.ResponseModeFormPostJWT},
+ AuthorizationPolicy: "",
+ RequestedAudienceMode: "",
+ ConsentMode: oidc.ClientConsentModeExplicit.String(),
+ EnforcePAR: true,
+ EnforcePKCE: true,
+ PKCEChallengeMethod: oidc.PKCEChallengeMethodSHA256,
+ TokenEndpointAuthMethod: oidc.ClientAuthMethodClientSecretPost,
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOfflineAccess},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost, oidc.ResponseModeFormPostJWT},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken},
+ },
+ nil,
+ nil,
+ },
+ {
+ "ShouldHandleBearerValidPublicClientType",
+ func(have *schema.IdentityProvidersOpenIDConnect) {
+ have.Clients[0] = schema.IdentityProvidersOpenIDConnectClient{
+ ID: "abc",
+ Secret: nil,
+ Public: true,
+ RedirectURIs: []string{"http://localhost"},
+ Audience: []string{"https://app.example.com"},
+ Scopes: []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOfflineAccess},
+ GrantTypes: []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken},
+ ResponseTypes: []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ ResponseModes: []string{oidc.ResponseModeFormPost},
+ AuthorizationPolicy: "",
+ RequestedAudienceMode: "",
+ ConsentMode: oidc.ClientConsentModeExplicit.String(),
+ EnforcePAR: true,
+ EnforcePKCE: true,
+ PKCEChallengeMethod: oidc.PKCEChallengeMethodSHA256,
+ TokenEndpointAuthMethod: oidc.ClientAuthMethodNone,
+ }
+ },
+ nil,
+ tcv{
+ nil,
+ nil,
+ nil,
+ nil,
+ },
+ tcv{
+ []string{oidc.ScopeAutheliaBearerAuthz, oidc.ScopeOfflineAccess},
+ []string{oidc.ResponseTypeAuthorizationCodeFlow},
+ []string{oidc.ResponseModeFormPost},
+ []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken},
+ },
+ nil,
+ nil,
+ },
+ {
"ShouldSetDefaultConsentMode",
nil,
func(t *testing.T, have *schema.IdentityProvidersOpenIDConnect) {
diff --git a/internal/configuration/validator/server.go b/internal/configuration/validator/server.go
index 90c4ab744..89b96d6ab 100644
--- a/internal/configuration/validator/server.go
+++ b/internal/configuration/validator/server.go
@@ -174,7 +174,7 @@ func ValidateServerEndpoints(config *schema.Configuration, validator *schema.Str
}
switch oEndpoint.Implementation {
- case authzImplementationLegacy, authzImplementationExtAuthz:
+ case schema.AuthzImplementationLegacy, schema.AuthzImplementationExtAuthz:
if strings.HasPrefix(name, oName+"/") {
validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzPrefixDuplicate, name, oName, oEndpoint.Implementation))
}
@@ -183,17 +183,17 @@ func ValidateServerEndpoints(config *schema.Configuration, validator *schema.Str
}
}
- validateServerEndpointsAuthzStrategies(name, endpoint.AuthnStrategies, validator)
+ validateServerEndpointsAuthzStrategies(name, endpoint.Implementation, endpoint.AuthnStrategies, validator)
}
}
func validateServerEndpointsAuthzEndpoint(config *schema.Configuration, name string, endpoint schema.ServerEndpointsAuthz, validator *schema.StructValidator) {
if name == legacy {
switch endpoint.Implementation {
- case authzImplementationLegacy:
+ case schema.AuthzImplementationLegacy:
break
case "":
- endpoint.Implementation = authzImplementationLegacy
+ endpoint.Implementation = schema.AuthzImplementationLegacy
config.Server.Endpoints.Authz[name] = endpoint
default:
@@ -212,18 +212,55 @@ func validateServerEndpointsAuthzEndpoint(config *schema.Configuration, name str
}
}
-func validateServerEndpointsAuthzStrategies(name string, strategies []schema.ServerEndpointsAuthzAuthnStrategy, validator *schema.StructValidator) {
+//nolint:gocyclo
+func validateServerEndpointsAuthzStrategies(name, implementation string, strategies []schema.ServerEndpointsAuthzAuthnStrategy, validator *schema.StructValidator) {
+ var defaults []schema.ServerEndpointsAuthzAuthnStrategy
+
+ switch implementation {
+ case schema.AuthzImplementationLegacy:
+ defaults = schema.DefaultServerConfiguration.Endpoints.Authz[schema.AuthzEndpointNameLegacy].AuthnStrategies
+ case schema.AuthzImplementationAuthRequest:
+ defaults = schema.DefaultServerConfiguration.Endpoints.Authz[schema.AuthzEndpointNameAuthRequest].AuthnStrategies
+ case schema.AuthzImplementationExtAuthz:
+ defaults = schema.DefaultServerConfiguration.Endpoints.Authz[schema.AuthzEndpointNameExtAuthz].AuthnStrategies
+ case schema.AuthzImplementationForwardAuth:
+ defaults = schema.DefaultServerConfiguration.Endpoints.Authz[schema.AuthzEndpointNameForwardAuth].AuthnStrategies
+ }
+
+ if len(strategies) == 0 {
+ copy(strategies, defaults)
+
+ return
+ }
+
names := make([]string, len(strategies))
- for _, strategy := range strategies {
- if utils.IsStringInSlice(strategy.Name, names) {
+ for i, strategy := range strategies {
+ if strategy.Name != "" && utils.IsStringInSlice(strategy.Name, names) {
validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategyDuplicate, name, strategy.Name))
}
names = append(names, strategy.Name)
- if !utils.IsStringInSlice(strategy.Name, validAuthzAuthnStrategies) {
+ switch {
+ case strategy.Name == "":
+ validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategyNoName, name, i+1))
+ case !utils.IsStringInSlice(strategy.Name, validAuthzAuthnStrategies):
validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategy, name, strJoinOr(validAuthzAuthnStrategies), strategy.Name))
+ default:
+ if utils.IsStringInSlice(strategy.Name, validAuthzAuthnHeaderStrategies) {
+ if len(strategy.Schemes) == 0 {
+ strategies[i].Schemes = defaults[0].Schemes
+ } else {
+ for _, scheme := range strategy.Schemes {
+ if !utils.IsStringInSliceFold(scheme, validAuthzAuthnStrategySchemes) {
+ validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzSchemes, name, i+1, strategy.Name, strJoinOr(validAuthzAuthnStrategySchemes), scheme))
+ }
+ }
+ }
+ } else if len(strategy.Schemes) != 0 {
+ validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzSchemesInvalidForStrategy, name, i+1, strategy.Name))
+ }
}
}
}
diff --git a/internal/configuration/validator/server_test.go b/internal/configuration/validator/server_test.go
index c425bb420..231efe9c2 100644
--- a/internal/configuration/validator/server_test.go
+++ b/internal/configuration/validator/server_test.go
@@ -489,6 +489,38 @@ func TestServerAuthzEndpointErrors(t *testing.T) {
[]string{"server: endpoints: authz: example: authn_strategies: duplicate strategy name detected with name 'CookieSession'"},
},
{
+ "ShouldErrorOnSchemesForInvalidStrategy",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{{Name: "CookieSession", Schemes: []string{"basic"}}}},
+ },
+ []string{"server: endpoints: authz: example: authn_strategies: strategy #1 (CookieSession): option 'schemes' is not valid for the strategy"},
+ },
+ {
+ "ShouldNotErrorOnSchemeCase",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{{Name: "HeaderAuthorization", Schemes: []string{"basIc"}}}},
+ },
+ nil,
+ },
+ {
+ "ShouldErrorOnInvalidStrategySchemesAndUnnamedStrategy",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{{Name: "HeaderAuthorization", Schemes: []string{"basic", "bearer", "abc"}}}},
+ },
+ []string{
+ "server: endpoints: authz: example: authn_strategies: strategy #1 (HeaderAuthorization): option 'schemes' must only include the values 'basic' or 'bearer' but has 'abc'",
+ },
+ },
+ {
+ "ShouldErrorOnUnnamedStrategy",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{{Name: "", Schemes: []string{"basic", "bearer", "abc"}}}},
+ },
+ []string{
+ "server: endpoints: authz: example: authn_strategies: strategy #1: option 'name' must be configured",
+ },
+ },
+ {
"ShouldErrorOnInvalidChars",
map[string]schema.ServerEndpointsAuthz{
"/abc": {Implementation: "ForwardAuth"},
@@ -567,6 +599,53 @@ func TestServerAuthzEndpointErrors(t *testing.T) {
}
}
+func TestServerAuthzEndpointDefaults(t *testing.T) {
+ testCases := []struct {
+ name string
+ have map[string]schema.ServerEndpointsAuthz
+ expected map[string]schema.ServerEndpointsAuthz
+ }{
+ {
+ "ShouldSetDefaultSchemes",
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{
+ {
+ Name: "HeaderAuthorization",
+ Schemes: []string{},
+ },
+ }},
+ },
+ map[string]schema.ServerEndpointsAuthz{
+ "example": {Implementation: "ForwardAuth", AuthnStrategies: []schema.ServerEndpointsAuthzAuthnStrategy{
+ {
+ Name: "HeaderAuthorization",
+ Schemes: []string{"basic"},
+ },
+ }},
+ },
+ },
+ }
+
+ validator := schema.NewStructValidator()
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ validator.Clear()
+
+ config := newDefaultConfig()
+
+ config.Server.Endpoints.Authz = tc.have
+
+ ValidateServerEndpoints(&config, validator)
+
+ assert.Len(t, validator.Warnings(), 0)
+ assert.Len(t, validator.Errors(), 0)
+
+ assert.Equal(t, tc.expected, config.Server.Endpoints.Authz)
+ })
+ }
+}
+
func TestServerAuthzEndpointLegacyAsImplementationLegacyWhenBlank(t *testing.T) {
have := map[string]schema.ServerEndpointsAuthz{
"legacy": {},
@@ -583,7 +662,7 @@ func TestServerAuthzEndpointLegacyAsImplementationLegacyWhenBlank(t *testing.T)
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
- assert.Equal(t, authzImplementationLegacy, config.Server.Endpoints.Authz[legacy].Implementation)
+ assert.Equal(t, schema.AuthzImplementationLegacy, config.Server.Endpoints.Authz[legacy].Implementation)
}
func TestValidateTLSPathStatInvalidArgument(t *testing.T) {
diff --git a/internal/configuration/validator/util.go b/internal/configuration/validator/util.go
index 67403dd90..0d6955570 100644
--- a/internal/configuration/validator/util.go
+++ b/internal/configuration/validator/util.go
@@ -92,7 +92,7 @@ func validateListNotAllowed(values, filter []string) (invalid []string) {
return invalid
}
-func validateList(values, valid []string, chkDuplicate bool) (invalid, duplicates []string) { //nolint:unparam
+func validateList(values, valid []string, chkDuplicate bool) (invalid, duplicates []string) {
chkValid := len(valid) != 0
for i, value := range values {