fix(configuration): missing warning about session domain (#4417)

This adds some helpful configuration warnings and fixes a few misconfiguration issues.
pull/4424/head
James Elliott 2022-11-24 10:16:23 +11:00 committed by GitHub
parent b295bf55a9
commit 203cb19c2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 202 additions and 95 deletions

View File

@ -542,14 +542,14 @@ if they have a path of exactly `/api` or if they start with `/api/`. This means
a match for that request. a match for that request.
```yaml ```yaml
- domains: - domain:
- 'example.com' - 'example.com'
- '*.example.com' - '*.example.com'
policy: bypass policy: bypass
resources: resources:
- '^/api$' - '^/api$'
- '^/api/' - '^/api/'
- domains: - domain:
- 'app.example.com' - 'app.example.com'
policy: two_factor policy: two_factor
``` ```

View File

@ -90,9 +90,7 @@ func ValidateRules(config *schema.Configuration, validator *schema.StructValidat
for i, rule := range config.AccessControl.Rules { for i, rule := range config.AccessControl.Rules {
rulePosition := i + 1 rulePosition := i + 1
if len(rule.Domains)+len(rule.DomainsRegex) == 0 { validateDomains(rulePosition, rule, validator)
validator.Push(fmt.Errorf(errFmtAccessControlRuleNoDomains, ruleDescriptor(rulePosition, rule)))
}
if !IsPolicyValid(rule.Policy) { if !IsPolicyValid(rule.Policy) {
validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidPolicy, ruleDescriptor(rulePosition, rule), rule.Policy)) validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidPolicy, ruleDescriptor(rulePosition, rule), rule.Policy))
@ -125,6 +123,18 @@ func validateBypass(rulePosition int, rule schema.ACLRule, validator *schema.Str
} }
} }
func validateDomains(rulePosition int, rule schema.ACLRule, validator *schema.StructValidator) {
if len(rule.Domains)+len(rule.DomainsRegex) == 0 {
validator.Push(fmt.Errorf(errFmtAccessControlRuleNoDomains, ruleDescriptor(rulePosition, rule)))
}
for i, domain := range rule.Domains {
if len(domain) > 1 && domain[0] == '*' && domain[1] != '.' {
validator.PushWarning(fmt.Errorf("access control: rule #%d: domain #%d: domain '%s' is ineffective and should probably be '%s' instead", rulePosition, i+1, domain, fmt.Sprintf("*.%s", domain[1:])))
}
}
}
func validateNetworks(rulePosition int, rule schema.ACLRule, config schema.AccessControlConfiguration, validator *schema.StructValidator) { func validateNetworks(rulePosition int, rule schema.ACLRule, config schema.AccessControlConfiguration, validator *schema.StructValidator) {
for _, network := range rule.Networks { for _, network := range rule.Networks {
if !IsNetworkValid(network) { if !IsNetworkValid(network) {

View File

@ -88,6 +88,22 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidNetworkGroupNetwork() {
suite.Assert().EqualError(suite.validator.Errors()[0], "access control: networks: network group 'internal' is invalid: the network 'abc.def.ghi.jkl' is not a valid IP or CIDR notation") suite.Assert().EqualError(suite.validator.Errors()[0], "access control: networks: network group 'internal' is invalid: the network 'abc.def.ghi.jkl' is not a valid IP or CIDR notation")
} }
func (suite *AccessControl) TestShouldRaiseWarningOnBadDomain() {
suite.config.AccessControl.Rules = []schema.ACLRule{
{
Domains: []string{"*example.com"},
Policy: "one_factor",
},
}
ValidateRules(suite.config, suite.validator)
suite.Assert().Len(suite.validator.Warnings(), 1)
suite.Require().Len(suite.validator.Errors(), 0)
suite.Assert().EqualError(suite.validator.Warnings()[0], "access control: rule #1: domain #1: domain '*example.com' is ineffective and should probably be '*.example.com' instead")
}
func (suite *AccessControl) TestShouldRaiseErrorWithNoRulesDefined() { func (suite *AccessControl) TestShouldRaiseErrorWithNoRulesDefined() {
suite.config.AccessControl.Rules = []schema.ACLRule{} suite.config.AccessControl.Rules = []schema.ACLRule{}

View File

@ -12,11 +12,11 @@ import (
) )
// ValidateIdentityProviders validates and updates the IdentityProviders configuration. // ValidateIdentityProviders validates and updates the IdentityProviders configuration.
func ValidateIdentityProviders(config *schema.IdentityProvidersConfiguration, validator *schema.StructValidator) { func ValidateIdentityProviders(config *schema.IdentityProvidersConfiguration, val *schema.StructValidator) {
validateOIDC(config.OIDC, validator) validateOIDC(config.OIDC, val)
} }
func validateOIDC(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
if config == nil { if config == nil {
return return
} }
@ -25,37 +25,37 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, validator *schema.S
switch { switch {
case config.IssuerPrivateKey == nil: case config.IssuerPrivateKey == nil:
validator.Push(fmt.Errorf(errFmtOIDCNoPrivateKey)) val.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
default: default:
if config.IssuerCertificateChain.HasCertificates() { if config.IssuerCertificateChain.HasCertificates() {
if !config.IssuerCertificateChain.EqualKey(config.IssuerPrivateKey) { if !config.IssuerCertificateChain.EqualKey(config.IssuerPrivateKey) {
validator.Push(fmt.Errorf(errFmtOIDCCertificateMismatch)) val.Push(fmt.Errorf(errFmtOIDCCertificateMismatch))
} }
if err := config.IssuerCertificateChain.Validate(); err != nil { if err := config.IssuerCertificateChain.Validate(); err != nil {
validator.Push(fmt.Errorf(errFmtOIDCCertificateChain, err)) val.Push(fmt.Errorf(errFmtOIDCCertificateChain, err))
} }
} }
if config.IssuerPrivateKey.Size()*8 < 2048 { if config.IssuerPrivateKey.Size()*8 < 2048 {
validator.Push(fmt.Errorf(errFmtOIDCInvalidPrivateKeyBitSize, 2048, config.IssuerPrivateKey.Size()*8)) val.Push(fmt.Errorf(errFmtOIDCInvalidPrivateKeyBitSize, 2048, config.IssuerPrivateKey.Size()*8))
} }
} }
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 { if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
validator.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy)) val.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy))
} }
if config.EnforcePKCE != "never" && config.EnforcePKCE != "public_clients_only" && config.EnforcePKCE != "always" { if config.EnforcePKCE != "never" && config.EnforcePKCE != "public_clients_only" && config.EnforcePKCE != "always" {
validator.Push(fmt.Errorf(errFmtOIDCEnforcePKCEInvalidValue, config.EnforcePKCE)) val.Push(fmt.Errorf(errFmtOIDCEnforcePKCEInvalidValue, config.EnforcePKCE))
} }
validateOIDCOptionsCORS(config, validator) validateOIDCOptionsCORS(config, val)
if len(config.Clients) == 0 { if len(config.Clients) == 0 {
validator.Push(fmt.Errorf(errFmtOIDCNoClientsConfigured)) val.Push(fmt.Errorf(errFmtOIDCNoClientsConfigured))
} else { } else {
validateOIDCClients(config, validator) validateOIDCClients(config, val)
} }
} }
@ -91,26 +91,26 @@ func validateOIDCOptionsCORS(config *schema.OpenIDConnectConfiguration, validato
validateOIDCOptionsCORSEndpoints(config, validator) validateOIDCOptionsCORSEndpoints(config, validator)
} }
func validateOIDCOptionsCORSAllowedOrigins(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { func validateOIDCOptionsCORSAllowedOrigins(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
for _, origin := range config.CORS.AllowedOrigins { for _, origin := range config.CORS.AllowedOrigins {
if origin.String() == "*" { if origin.String() == "*" {
if len(config.CORS.AllowedOrigins) != 1 { if len(config.CORS.AllowedOrigins) != 1 {
validator.Push(fmt.Errorf(errFmtOIDCCORSInvalidOriginWildcard)) val.Push(fmt.Errorf(errFmtOIDCCORSInvalidOriginWildcard))
} }
if config.CORS.AllowedOriginsFromClientRedirectURIs { if config.CORS.AllowedOriginsFromClientRedirectURIs {
validator.Push(fmt.Errorf(errFmtOIDCCORSInvalidOriginWildcardWithClients)) val.Push(fmt.Errorf(errFmtOIDCCORSInvalidOriginWildcardWithClients))
} }
continue continue
} }
if origin.Path != "" { if origin.Path != "" {
validator.Push(fmt.Errorf(errFmtOIDCCORSInvalidOrigin, origin.String(), "path")) val.Push(fmt.Errorf(errFmtOIDCCORSInvalidOrigin, origin.String(), "path"))
} }
if origin.RawQuery != "" { if origin.RawQuery != "" {
validator.Push(fmt.Errorf(errFmtOIDCCORSInvalidOrigin, origin.String(), "query string")) val.Push(fmt.Errorf(errFmtOIDCCORSInvalidOrigin, origin.String(), "query string"))
} }
} }
} }
@ -132,16 +132,15 @@ func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema.
} }
} }
func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
for _, endpoint := range config.CORS.Endpoints { for _, endpoint := range config.CORS.Endpoints {
if !utils.IsStringInSlice(endpoint, validOIDCCORSEndpoints) { if !utils.IsStringInSlice(endpoint, validOIDCCORSEndpoints) {
validator.Push(fmt.Errorf(errFmtOIDCCORSInvalidEndpoint, endpoint, strings.Join(validOIDCCORSEndpoints, "', '"))) val.Push(fmt.Errorf(errFmtOIDCCORSInvalidEndpoint, endpoint, strings.Join(validOIDCCORSEndpoints, "', '")))
} }
} }
} }
//nolint:gocyclo // TODO: Refactor. func validateOIDCClients(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
func validateOIDCClients(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
invalidID, duplicateIDs := false, false invalidID, duplicateIDs := false, false
var ids []string var ids []string
@ -162,176 +161,179 @@ func validateOIDCClients(config *schema.OpenIDConnectConfiguration, validator *s
if client.Public { if client.Public {
if client.Secret != nil { if client.Secret != nil {
validator.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, client.ID)) val.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, client.ID))
} }
} else { } else {
if client.Secret == nil { if client.Secret == nil {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, client.ID)) val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, client.ID))
} }
} }
if client.Policy == "" { if client.Policy == "" {
config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy
} else if client.Policy != policyOneFactor && client.Policy != policyTwoFactor { } else if client.Policy != policyOneFactor && client.Policy != policyTwoFactor {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy)) val.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
} }
switch { validateOIDCClientConsentMode(c, config, val)
case utils.IsStringInSlice(client.ConsentMode, []string{"", "auto"}): validateOIDCClientSectorIdentifier(client, val)
if client.ConsentPreConfiguredDuration != nil { validateOIDCClientScopes(c, config, val)
config.Clients[c].ConsentMode = oidc.ClientConsentModePreConfigured.String() validateOIDCClientGrantTypes(c, config, val)
} else { validateOIDCClientResponseTypes(c, config, val)
config.Clients[c].ConsentMode = oidc.ClientConsentModeExplicit.String() validateOIDCClientResponseModes(c, config, val)
} validateOIDDClientUserinfoAlgorithm(c, config, val)
case utils.IsStringInSlice(client.ConsentMode, validOIDCClientConsentModes): validateOIDCClientRedirectURIs(client, val)
break
default:
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidConsentMode, client.ID, strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), client.ConsentMode))
}
if client.ConsentPreConfiguredDuration == nil {
config.Clients[c].ConsentPreConfiguredDuration = schema.DefaultOpenIDConnectClientConfiguration.ConsentPreConfiguredDuration
}
validateOIDCClientSectorIdentifier(client, validator)
validateOIDCClientScopes(c, config, validator)
validateOIDCClientGrantTypes(c, config, validator)
validateOIDCClientResponseTypes(c, config, validator)
validateOIDCClientResponseModes(c, config, validator)
validateOIDDClientUserinfoAlgorithm(c, config, validator)
validateOIDCClientRedirectURIs(client, validator)
} }
if invalidID { if invalidID {
validator.Push(fmt.Errorf(errFmtOIDCClientsWithEmptyID)) val.Push(fmt.Errorf(errFmtOIDCClientsWithEmptyID))
} }
if duplicateIDs { if duplicateIDs {
validator.Push(fmt.Errorf(errFmtOIDCClientsDuplicateID)) val.Push(fmt.Errorf(errFmtOIDCClientsDuplicateID))
} }
} }
func validateOIDCClientSectorIdentifier(client schema.OpenIDConnectClientConfiguration, validator *schema.StructValidator) { func validateOIDCClientSectorIdentifier(client schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) {
if client.SectorIdentifier.String() != "" { if client.SectorIdentifier.String() != "" {
if utils.IsURLHostComponent(client.SectorIdentifier) || utils.IsURLHostComponentWithPort(client.SectorIdentifier) { if utils.IsURLHostComponent(client.SectorIdentifier) || utils.IsURLHostComponentWithPort(client.SectorIdentifier) {
return return
} }
if client.SectorIdentifier.Scheme != "" { if client.SectorIdentifier.Scheme != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "scheme", client.SectorIdentifier.Scheme)) val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "scheme", client.SectorIdentifier.Scheme))
if client.SectorIdentifier.Path != "" { if client.SectorIdentifier.Path != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "path", client.SectorIdentifier.Path)) val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "path", client.SectorIdentifier.Path))
} }
if client.SectorIdentifier.RawQuery != "" { if client.SectorIdentifier.RawQuery != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "query", client.SectorIdentifier.RawQuery)) val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "query", client.SectorIdentifier.RawQuery))
} }
if client.SectorIdentifier.Fragment != "" { if client.SectorIdentifier.Fragment != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "fragment", client.SectorIdentifier.Fragment)) val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "fragment", client.SectorIdentifier.Fragment))
} }
if client.SectorIdentifier.User != nil { if client.SectorIdentifier.User != nil {
if client.SectorIdentifier.User.Username() != "" { if client.SectorIdentifier.User.Username() != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "username", client.SectorIdentifier.User.Username())) val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "username", client.SectorIdentifier.User.Username()))
} }
if _, set := client.SectorIdentifier.User.Password(); set { if _, set := client.SectorIdentifier.User.Password(); set {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "password")) val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "password"))
} }
} }
} else if client.SectorIdentifier.Host == "" { } else if client.SectorIdentifier.Host == "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, client.ID, client.SectorIdentifier.String())) val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, client.ID, client.SectorIdentifier.String()))
} }
} }
} }
func validateOIDCClientScopes(c int, configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
if len(configuration.Clients[c].Scopes) == 0 { switch {
configuration.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", "auto"}):
if config.Clients[c].ConsentPreConfiguredDuration != nil {
config.Clients[c].ConsentMode = oidc.ClientConsentModePreConfigured.String()
} else {
config.Clients[c].ConsentMode = oidc.ClientConsentModeExplicit.String()
}
case utils.IsStringInSlice(config.Clients[c].ConsentMode, validOIDCClientConsentModes):
break
default:
val.Push(fmt.Errorf(errFmtOIDCClientInvalidConsentMode, config.Clients[c].ID, strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), config.Clients[c].ConsentMode))
}
if config.Clients[c].ConsentMode == oidc.ClientConsentModePreConfigured.String() && config.Clients[c].ConsentPreConfiguredDuration == nil {
config.Clients[c].ConsentPreConfiguredDuration = schema.DefaultOpenIDConnectClientConfiguration.ConsentPreConfiguredDuration
}
}
func validateOIDCClientScopes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
if len(config.Clients[c].Scopes) == 0 {
config.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes
return return
} }
if !utils.IsStringInSlice(oidc.ScopeOpenID, configuration.Clients[c].Scopes) { if !utils.IsStringInSlice(oidc.ScopeOpenID, config.Clients[c].Scopes) {
configuration.Clients[c].Scopes = append(configuration.Clients[c].Scopes, oidc.ScopeOpenID) config.Clients[c].Scopes = append(config.Clients[c].Scopes, oidc.ScopeOpenID)
} }
for _, scope := range configuration.Clients[c].Scopes { for _, scope := range config.Clients[c].Scopes {
if !utils.IsStringInSlice(scope, validOIDCScopes) { if !utils.IsStringInSlice(scope, validOIDCScopes) {
validator.Push(fmt.Errorf( val.Push(fmt.Errorf(
errFmtOIDCClientInvalidEntry, errFmtOIDCClientInvalidEntry,
configuration.Clients[c].ID, "scopes", strings.Join(validOIDCScopes, "', '"), scope)) config.Clients[c].ID, "scopes", strings.Join(validOIDCScopes, "', '"), scope))
} }
} }
} }
func validateOIDCClientGrantTypes(c int, configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { func validateOIDCClientGrantTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
if len(configuration.Clients[c].GrantTypes) == 0 { if len(config.Clients[c].GrantTypes) == 0 {
configuration.Clients[c].GrantTypes = schema.DefaultOpenIDConnectClientConfiguration.GrantTypes config.Clients[c].GrantTypes = schema.DefaultOpenIDConnectClientConfiguration.GrantTypes
return return
} }
for _, grantType := range configuration.Clients[c].GrantTypes { for _, grantType := range config.Clients[c].GrantTypes {
if !utils.IsStringInSlice(grantType, validOIDCGrantTypes) { if !utils.IsStringInSlice(grantType, validOIDCGrantTypes) {
validator.Push(fmt.Errorf( val.Push(fmt.Errorf(
errFmtOIDCClientInvalidEntry, errFmtOIDCClientInvalidEntry,
configuration.Clients[c].ID, "grant_types", strings.Join(validOIDCGrantTypes, "', '"), grantType)) config.Clients[c].ID, "grant_types", strings.Join(validOIDCGrantTypes, "', '"), grantType))
} }
} }
} }
func validateOIDCClientResponseTypes(c int, configuration *schema.OpenIDConnectConfiguration, _ *schema.StructValidator) { func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnectConfiguration, _ *schema.StructValidator) {
if len(configuration.Clients[c].ResponseTypes) == 0 { if len(config.Clients[c].ResponseTypes) == 0 {
configuration.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes config.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes
return return
} }
} }
func validateOIDCClientResponseModes(c int, configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { func validateOIDCClientResponseModes(c int, config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
if len(configuration.Clients[c].ResponseModes) == 0 { if len(config.Clients[c].ResponseModes) == 0 {
configuration.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes config.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes
return return
} }
for _, responseMode := range configuration.Clients[c].ResponseModes { for _, responseMode := range config.Clients[c].ResponseModes {
if !utils.IsStringInSlice(responseMode, validOIDCResponseModes) { if !utils.IsStringInSlice(responseMode, validOIDCResponseModes) {
validator.Push(fmt.Errorf( validator.Push(fmt.Errorf(
errFmtOIDCClientInvalidEntry, errFmtOIDCClientInvalidEntry,
configuration.Clients[c].ID, "response_modes", strings.Join(validOIDCResponseModes, "', '"), responseMode)) config.Clients[c].ID, "response_modes", strings.Join(validOIDCResponseModes, "', '"), responseMode))
} }
} }
} }
func validateOIDDClientUserinfoAlgorithm(c int, configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { func validateOIDDClientUserinfoAlgorithm(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
if configuration.Clients[c].UserinfoSigningAlgorithm == "" { if config.Clients[c].UserinfoSigningAlgorithm == "" {
configuration.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm config.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm
} else if !utils.IsStringInSlice(configuration.Clients[c].UserinfoSigningAlgorithm, validOIDCUserinfoAlgorithms) { } else if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlgorithm, validOIDCUserinfoAlgorithms) {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidUserinfoAlgorithm, val.Push(fmt.Errorf(errFmtOIDCClientInvalidUserinfoAlgorithm,
configuration.Clients[c].ID, strings.Join(validOIDCUserinfoAlgorithms, ", "), configuration.Clients[c].UserinfoSigningAlgorithm)) config.Clients[c].ID, strings.Join(validOIDCUserinfoAlgorithms, ", "), config.Clients[c].UserinfoSigningAlgorithm))
} }
} }
func validateOIDCClientRedirectURIs(client schema.OpenIDConnectClientConfiguration, validator *schema.StructValidator) { func validateOIDCClientRedirectURIs(client schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) {
for _, redirectURI := range client.RedirectURIs { for _, redirectURI := range client.RedirectURIs {
if redirectURI == oauth2InstalledApp { if redirectURI == oauth2InstalledApp {
if client.Public { if client.Public {
continue continue
} }
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, client.ID, oauth2InstalledApp)) val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, client.ID, oauth2InstalledApp))
continue continue
} }
parsedURL, err := url.Parse(redirectURI) parsedURL, err := url.Parse(redirectURI)
if err != nil { if err != nil {
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURICantBeParsed, client.ID, redirectURI, err)) val.Push(fmt.Errorf(errFmtOIDCClientRedirectURICantBeParsed, client.ID, redirectURI, err))
continue continue
} }
if !parsedURL.IsAbs() || (!client.Public && parsedURL.Scheme == "") { if !parsedURL.IsAbs() || (!client.Public && parsedURL.Scheme == "") {
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURIAbsolute, client.ID, redirectURI)) val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIAbsolute, client.ID, redirectURI))
return return
} }
} }

View File

@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"strings"
"testing" "testing"
"time" "time"
@ -313,6 +314,23 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierHost, "client-invalid-sector", "example.com/path?query=abc#fragment"), fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierHost, "client-invalid-sector", "example.com/path?query=abc#fragment"),
}, },
}, },
{
Name: "InvalidConsentMode",
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "client-bad-consent-mode",
Secret: MustDecodeSecret("$plaintext$a-secret"),
Policy: policyTwoFactor,
RedirectURIs: []string{
"https://google.com",
},
ConsentMode: "cap",
},
},
Errors: []string{
fmt.Sprintf(errFmtOIDCClientInvalidConsentMode, "client-bad-consent-mode", strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), "cap"),
},
},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -633,6 +651,8 @@ func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidPublicClients(t *te
} }
func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) { func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
timeDay := time.Hour * 24
validator := schema.NewStructValidator() validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
@ -645,6 +665,7 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
RedirectURIs: []string{ RedirectURIs: []string{
"https://google.com", "https://google.com",
}, },
ConsentPreConfiguredDuration: &timeDay,
}, },
{ {
ID: "b-client", ID: "b-client",
@ -670,6 +691,30 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
"fragment", "fragment",
}, },
}, },
{
ID: "c-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
RedirectURIs: []string{
"https://google.com",
},
ConsentMode: "implicit",
},
{
ID: "d-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
RedirectURIs: []string{
"https://google.com",
},
ConsentMode: "explicit",
},
{
ID: "e-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
RedirectURIs: []string{
"https://google.com",
},
ConsentMode: "pre-configured",
},
}, },
}, },
} }
@ -702,6 +747,15 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
assert.Equal(t, "groups", config.OIDC.Clients[1].Scopes[0]) assert.Equal(t, "groups", config.OIDC.Clients[1].Scopes[0])
assert.Equal(t, "openid", config.OIDC.Clients[1].Scopes[1]) assert.Equal(t, "openid", config.OIDC.Clients[1].Scopes[1])
// Assert Clients[0] ends up configured with the correct consent mode.
require.NotNil(t, config.OIDC.Clients[0].ConsentPreConfiguredDuration)
assert.Equal(t, time.Hour*24, *config.OIDC.Clients[0].ConsentPreConfiguredDuration)
assert.Equal(t, "pre-configured", config.OIDC.Clients[0].ConsentMode)
// Assert Clients[1] ends up configured with the correct consent mode.
assert.Nil(t, config.OIDC.Clients[1].ConsentPreConfiguredDuration)
assert.Equal(t, "explicit", config.OIDC.Clients[1].ConsentMode)
// Assert Clients[0] ends up configured with the default GrantTypes. // Assert Clients[0] ends up configured with the default GrantTypes.
require.Len(t, config.OIDC.Clients[0].GrantTypes, 2) require.Len(t, config.OIDC.Clients[0].GrantTypes, 2)
assert.Equal(t, "refresh_token", config.OIDC.Clients[0].GrantTypes[0]) assert.Equal(t, "refresh_token", config.OIDC.Clients[0].GrantTypes[0])
@ -736,6 +790,15 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
assert.Equal(t, time.Minute, config.OIDC.AuthorizeCodeLifespan) assert.Equal(t, time.Minute, config.OIDC.AuthorizeCodeLifespan)
assert.Equal(t, time.Hour, config.OIDC.IDTokenLifespan) assert.Equal(t, time.Hour, config.OIDC.IDTokenLifespan)
assert.Equal(t, time.Minute*90, config.OIDC.RefreshTokenLifespan) assert.Equal(t, time.Minute*90, config.OIDC.RefreshTokenLifespan)
assert.Equal(t, "implicit", config.OIDC.Clients[2].ConsentMode)
assert.Nil(t, config.OIDC.Clients[2].ConsentPreConfiguredDuration)
assert.Equal(t, "explicit", config.OIDC.Clients[3].ConsentMode)
assert.Nil(t, config.OIDC.Clients[3].ConsentPreConfiguredDuration)
assert.Equal(t, "pre-configured", config.OIDC.Clients[4].ConsentMode)
assert.Equal(t, schema.DefaultOpenIDConnectClientConfiguration.ConsentPreConfiguredDuration, config.OIDC.Clients[4].ConsentPreConfiguredDuration)
} }
// All valid schemes are supported as defined in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 // All valid schemes are supported as defined in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1

View File

@ -41,6 +41,8 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru
if config.Domain == "" { if config.Domain == "" {
validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "domain")) validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "domain"))
} else if strings.HasPrefix(config.Domain, ".") {
validator.PushWarning(fmt.Errorf("session: option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it"))
} }
if strings.HasPrefix(config.Domain, "*.") { if strings.HasPrefix(config.Domain, "*.") {

View File

@ -49,6 +49,20 @@ func TestShouldSetDefaultSessionValuesWhenNegative(t *testing.T) {
assert.Equal(t, schema.DefaultSessionConfiguration.RememberMeDuration, config.RememberMeDuration) assert.Equal(t, schema.DefaultSessionConfiguration.RememberMeDuration, config.RememberMeDuration)
} }
func TestShouldWarnSessionValuesWhenPotentiallyInvalid(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultSessionConfig()
config.Domain = ".example.com"
ValidateSession(&config, validator)
require.Len(t, validator.Warnings(), 1)
assert.Len(t, validator.Errors(), 0)
assert.EqualError(t, validator.Warnings()[0], "session: option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it")
}
func TestShouldHandleRedisConfigSuccessfully(t *testing.T) { func TestShouldHandleRedisConfigSuccessfully(t *testing.T) {
validator := schema.NewStructValidator() validator := schema.NewStructValidator()
config := newDefaultSessionConfig() config := newDefaultSessionConfig()