authelia/internal/configuration/validator/identity_providers.go
Clément Michaud a7a2bc63fe
feat(configuration): support private-use redirect uris in oidc (#2796)
Private-use redirect URIs are used to redirect the user to native apps initiating the authentication flow on a device as described in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1

Fix #2742
2022-01-21 22:05:53 +11:00

198 lines
6.8 KiB
Go

package validator
import (
"fmt"
"net/url"
"strings"
"time"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
// ValidateIdentityProviders validates and update IdentityProviders configuration.
func ValidateIdentityProviders(configuration *schema.IdentityProvidersConfiguration, validator *schema.StructValidator) {
validateOIDC(configuration.OIDC, validator)
}
func validateOIDC(configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
if configuration != nil {
if configuration.IssuerPrivateKey == "" {
validator.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
}
if configuration.AccessTokenLifespan == time.Duration(0) {
configuration.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
}
if configuration.AuthorizeCodeLifespan == time.Duration(0) {
configuration.AuthorizeCodeLifespan = schema.DefaultOpenIDConnectConfiguration.AuthorizeCodeLifespan
}
if configuration.IDTokenLifespan == time.Duration(0) {
configuration.IDTokenLifespan = schema.DefaultOpenIDConnectConfiguration.IDTokenLifespan
}
if configuration.RefreshTokenLifespan == time.Duration(0) {
configuration.RefreshTokenLifespan = schema.DefaultOpenIDConnectConfiguration.RefreshTokenLifespan
}
if configuration.MinimumParameterEntropy != 0 && configuration.MinimumParameterEntropy < 8 {
validator.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, configuration.MinimumParameterEntropy))
}
validateOIDCClients(configuration, validator)
if len(configuration.Clients) == 0 {
validator.Push(fmt.Errorf(errFmtOIDCNoClientsConfigured))
}
}
}
func validateOIDCClients(configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
invalidID, duplicateIDs := false, false
var ids []string
for c, client := range configuration.Clients {
if client.ID == "" {
invalidID = true
} else {
if client.Description == "" {
configuration.Clients[c].Description = client.ID
}
if utils.IsStringInSliceFold(client.ID, ids) {
duplicateIDs = true
}
ids = append(ids, client.ID)
}
if client.Public {
if client.Secret != "" {
validator.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, client.ID))
}
} else {
if client.Secret == "" {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, client.ID))
}
}
if client.Policy == "" {
configuration.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy
} else if client.Policy != policyOneFactor && client.Policy != policyTwoFactor {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy))
}
validateOIDCClientScopes(c, configuration, validator)
validateOIDCClientGrantTypes(c, configuration, validator)
validateOIDCClientResponseTypes(c, configuration, validator)
validateOIDCClientResponseModes(c, configuration, validator)
validateOIDDClientUserinfoAlgorithm(c, configuration, validator)
validateOIDCClientRedirectURIs(client, validator)
}
if invalidID {
validator.Push(fmt.Errorf(errFmtOIDCClientsWithEmptyID))
}
if duplicateIDs {
validator.Push(fmt.Errorf(errFmtOIDCClientsDuplicateID))
}
}
func validateOIDCClientScopes(c int, configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
if len(configuration.Clients[c].Scopes) == 0 {
configuration.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes
return
}
if !utils.IsStringInSlice("openid", configuration.Clients[c].Scopes) {
configuration.Clients[c].Scopes = append(configuration.Clients[c].Scopes, "openid")
}
for _, scope := range configuration.Clients[c].Scopes {
if !utils.IsStringInSlice(scope, validOIDCScopes) {
validator.Push(fmt.Errorf(
errFmtOIDCClientInvalidScope,
configuration.Clients[c].ID, scope, strings.Join(validOIDCScopes, "', '")))
}
}
}
func validateOIDCClientGrantTypes(c int, configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
if len(configuration.Clients[c].GrantTypes) == 0 {
configuration.Clients[c].GrantTypes = schema.DefaultOpenIDConnectClientConfiguration.GrantTypes
return
}
for _, grantType := range configuration.Clients[c].GrantTypes {
if !utils.IsStringInSlice(grantType, validOIDCGrantTypes) {
validator.Push(fmt.Errorf(
errFmtOIDCClientInvalidGrantType,
configuration.Clients[c].ID, grantType, strings.Join(validOIDCGrantTypes, "', '")))
}
}
}
func validateOIDCClientResponseTypes(c int, configuration *schema.OpenIDConnectConfiguration, _ *schema.StructValidator) {
if len(configuration.Clients[c].ResponseTypes) == 0 {
configuration.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes
return
}
}
func validateOIDCClientResponseModes(c int, configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
if len(configuration.Clients[c].ResponseModes) == 0 {
configuration.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes
return
}
for _, responseMode := range configuration.Clients[c].ResponseModes {
if !utils.IsStringInSlice(responseMode, validOIDCResponseModes) {
validator.Push(fmt.Errorf(
errFmtOIDCClientInvalidResponseMode,
configuration.Clients[c].ID, responseMode, strings.Join(validOIDCResponseModes, "', '")))
}
}
}
func validateOIDDClientUserinfoAlgorithm(c int, configuration *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
if configuration.Clients[c].UserinfoSigningAlgorithm == "" {
configuration.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm
} else if !utils.IsStringInSlice(configuration.Clients[c].UserinfoSigningAlgorithm, validOIDCUserinfoAlgorithms) {
validator.Push(fmt.Errorf(errFmtOIDCClientInvalidUserinfoAlgorithm,
configuration.Clients[c].ID, configuration.Clients[c].UserinfoSigningAlgorithm, strings.Join(validOIDCUserinfoAlgorithms, ", ")))
}
}
func validateOIDCClientRedirectURIs(client schema.OpenIDConnectClientConfiguration, validator *schema.StructValidator) {
for _, redirectURI := range client.RedirectURIs {
if redirectURI == oauth2InstalledApp {
if client.Public {
continue
}
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, client.ID, redirectURI))
continue
}
parsedURL, err := url.Parse(redirectURI)
if err != nil {
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURICantBeParsed, client.ID, redirectURI, err))
continue
}
if !parsedURL.IsAbs() {
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURIAbsolute, client.ID, redirectURI))
return
}
if !client.Public && parsedURL.Scheme != schemeHTTPS && parsedURL.Scheme != schemeHTTP {
validator.Push(fmt.Errorf(errFmtOIDCClientRedirectURI, client.ID, redirectURI, parsedURL.Scheme))
}
}
}