authelia/internal/oidc/config.go

612 lines
19 KiB
Go

package oidc
import (
"context"
"crypto/sha512"
"hash"
"html/template"
"net/url"
"time"
"github.com/hashicorp/go-retryablehttp"
"github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/handler/par"
"github.com/ory/fosite/handler/pkce"
"github.com/ory/fosite/i18n"
"github.com/ory/fosite/token/hmac"
"github.com/ory/fosite/token/jwt"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/templates"
"github.com/authelia/authelia/v4/internal/utils"
)
func NewConfig(config *schema.IdentityProvidersOpenIDConnect, templates *templates.Provider) (c *Config) {
c = &Config{
GlobalSecret: []byte(utils.HashSHA256FromString(config.HMACSecret)),
SendDebugMessagesToClients: config.EnableClientDebugMessages,
MinParameterEntropy: config.MinimumParameterEntropy,
Lifespans: LifespanConfig{
AccessToken: config.AccessTokenLifespan,
AuthorizeCode: config.AuthorizeCodeLifespan,
IDToken: config.IDTokenLifespan,
RefreshToken: config.RefreshTokenLifespan,
},
ProofKeyCodeExchange: ProofKeyCodeExchangeConfig{
Enforce: config.EnforcePKCE == "always",
EnforcePublicClients: config.EnforcePKCE != "never",
AllowPlainChallengeMethod: config.EnablePKCEPlainChallenge,
},
PAR: PARConfig{
Enforced: config.PAR.Enforce,
ContextLifespan: config.PAR.ContextLifespan,
URIPrefix: RedirectURIPrefixPushedAuthorizationRequestURN,
},
Templates: templates,
}
c.Strategy.Core = &HMACCoreStrategy{
Enigma: &hmac.HMACStrategy{Config: c},
Config: c,
}
return c
}
// Config is an implementation of the fosite.Configurator.
type Config struct {
// GlobalSecret is the global secret used to sign and verify signatures.
GlobalSecret []byte
// RotatedGlobalSecrets is a list of global secrets that are used to verify signatures.
RotatedGlobalSecrets [][]byte
Issuers IssuersConfig
SendDebugMessagesToClients bool
DisableRefreshTokenValidation bool
OmitRedirectScopeParameter bool
JWTScopeField jwt.JWTScopeFieldEnum
JWTMaxDuration time.Duration
Hasher *Hasher
Hash HashConfig
Strategy StrategyConfig
PAR PARConfig
Handlers HandlersConfig
Lifespans LifespanConfig
ProofKeyCodeExchange ProofKeyCodeExchangeConfig
GrantTypeJWTBearer GrantTypeJWTBearerConfig
TokenURL string
TokenEntropy int
MinParameterEntropy int
SanitationWhiteList []string
AllowedPrompts []string
RefreshTokenScopes []string
HTTPClient *retryablehttp.Client
MessageCatalog i18n.MessageCatalog
Templates *templates.Provider
}
// HashConfig holds specific fosite.Configurator information for hashing.
type HashConfig struct {
ClientSecrets fosite.Hasher
HMAC func() (h hash.Hash)
}
// StrategyConfig holds specific fosite.Configurator information for various strategies.
type StrategyConfig struct {
Core oauth2.CoreStrategy
OpenID openid.OpenIDConnectTokenStrategy
Audience fosite.AudienceMatchingStrategy
Scope fosite.ScopeStrategy
JWKSFetcher fosite.JWKSFetcherStrategy
ClientAuthentication fosite.ClientAuthenticationStrategy
}
// PARConfig holds specific fosite.Configurator information for Pushed Authorization Requests.
type PARConfig struct {
Enforced bool
URIPrefix string
ContextLifespan time.Duration
}
// IssuersConfig holds specific fosite.Configurator information for the issuer.
type IssuersConfig struct {
IDToken string
AccessToken string
}
// HandlersConfig holds specific fosite.Configurator handlers configuration information.
type HandlersConfig struct {
// ResponseMode provides an extension handler for custom response modes.
ResponseMode fosite.ResponseModeHandler
// AuthorizeEndpoint is a list of handlers that are called before the authorization endpoint is served.
AuthorizeEndpoint fosite.AuthorizeEndpointHandlers
// TokenEndpoint is a list of handlers that are called before the token endpoint is served.
TokenEndpoint fosite.TokenEndpointHandlers
// TokenIntrospection is a list of handlers that are called before the token introspection endpoint is served.
TokenIntrospection fosite.TokenIntrospectionHandlers
// Revocation is a list of handlers that are called before the revocation endpoint is served.
Revocation fosite.RevocationHandlers
// PushedAuthorizeEndpoint is a list of handlers that are called before the PAR endpoint is served.
PushedAuthorizeEndpoint fosite.PushedAuthorizeEndpointHandlers
}
// GrantTypeJWTBearerConfig holds specific fosite.Configurator information for the JWT Bearer Grant Type.
type GrantTypeJWTBearerConfig struct {
OptionalClientAuth bool
OptionalJTIClaim bool
OptionalIssuedDate bool
}
// ProofKeyCodeExchangeConfig holds specific fosite.Configurator information for PKCE.
type ProofKeyCodeExchangeConfig struct {
Enforce bool
EnforcePublicClients bool
AllowPlainChallengeMethod bool
}
// LifespanConfig holds specific fosite.Configurator information for various lifespans.
type LifespanConfig struct {
AccessToken time.Duration
AuthorizeCode time.Duration
IDToken time.Duration
RefreshToken time.Duration
}
// LoadHandlers reloads the handlers based on the current configuration.
func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) {
validator := openid.NewOpenIDConnectRequestValidator(strategy, c)
handlers := []any{
&oauth2.AuthorizeExplicitGrantHandler{
AccessTokenStrategy: c.Strategy.Core,
RefreshTokenStrategy: c.Strategy.Core,
AuthorizeCodeStrategy: c.Strategy.Core,
CoreStorage: store,
TokenRevocationStorage: store,
Config: c,
},
&oauth2.AuthorizeImplicitGrantTypeHandler{
AccessTokenStrategy: c.Strategy.Core,
AccessTokenStorage: store,
Config: c,
},
&oauth2.ClientCredentialsGrantHandler{
HandleHelper: &oauth2.HandleHelper{
AccessTokenStrategy: c.Strategy.Core,
AccessTokenStorage: store,
Config: c,
},
Config: c,
},
&oauth2.RefreshTokenGrantHandler{
AccessTokenStrategy: c.Strategy.Core,
RefreshTokenStrategy: c.Strategy.Core,
TokenRevocationStorage: store,
Config: c,
},
&openid.OpenIDConnectExplicitHandler{
IDTokenHandleHelper: &openid.IDTokenHandleHelper{
IDTokenStrategy: c.Strategy.OpenID,
},
OpenIDConnectRequestValidator: validator,
OpenIDConnectRequestStorage: store,
Config: c,
},
&openid.OpenIDConnectImplicitHandler{
AuthorizeImplicitGrantTypeHandler: &oauth2.AuthorizeImplicitGrantTypeHandler{
AccessTokenStrategy: c.Strategy.Core,
AccessTokenStorage: store,
Config: c,
},
IDTokenHandleHelper: &openid.IDTokenHandleHelper{
IDTokenStrategy: c.Strategy.OpenID,
},
OpenIDConnectRequestValidator: validator,
Config: c,
},
&openid.OpenIDConnectHybridHandler{
AuthorizeExplicitGrantHandler: &oauth2.AuthorizeExplicitGrantHandler{
AccessTokenStrategy: c.Strategy.Core,
RefreshTokenStrategy: c.Strategy.Core,
AuthorizeCodeStrategy: c.Strategy.Core,
CoreStorage: store,
Config: c,
},
Config: c,
AuthorizeImplicitGrantTypeHandler: &oauth2.AuthorizeImplicitGrantTypeHandler{
AccessTokenStrategy: c.Strategy.Core,
AccessTokenStorage: store,
Config: c,
},
IDTokenHandleHelper: &openid.IDTokenHandleHelper{
IDTokenStrategy: c.Strategy.OpenID,
},
OpenIDConnectRequestValidator: validator,
OpenIDConnectRequestStorage: store,
},
&openid.OpenIDConnectRefreshHandler{
IDTokenHandleHelper: &openid.IDTokenHandleHelper{
IDTokenStrategy: c.Strategy.OpenID,
},
Config: c,
},
&oauth2.CoreValidator{
CoreStrategy: c.Strategy.Core,
CoreStorage: store,
Config: c,
},
&oauth2.TokenRevocationHandler{
AccessTokenStrategy: c.Strategy.Core,
RefreshTokenStrategy: c.Strategy.Core,
TokenRevocationStorage: store,
},
&pkce.Handler{
AuthorizeCodeStrategy: c.Strategy.Core,
Storage: store,
Config: c,
},
&par.PushedAuthorizeHandler{
Storage: store,
Config: c,
},
}
x := HandlersConfig{}
for _, handler := range handlers {
if h, ok := handler.(fosite.AuthorizeEndpointHandler); ok {
x.AuthorizeEndpoint.Append(h)
}
if h, ok := handler.(fosite.TokenEndpointHandler); ok {
x.TokenEndpoint.Append(h)
}
if h, ok := handler.(fosite.TokenIntrospector); ok {
x.TokenIntrospection.Append(h)
}
if h, ok := handler.(fosite.RevocationHandler); ok {
x.Revocation.Append(h)
}
if h, ok := handler.(fosite.PushedAuthorizeEndpointHandler); ok {
x.PushedAuthorizeEndpoint.Append(h)
}
}
c.Handlers = x
}
// GetAllowedPrompts returns the allowed prompts.
func (c *Config) GetAllowedPrompts(ctx context.Context) (prompts []string) {
if len(c.AllowedPrompts) == 0 {
c.AllowedPrompts = []string{PromptNone, PromptLogin, PromptConsent}
}
return c.AllowedPrompts
}
// GetEnforcePKCE returns the enforcement of PKCE.
func (c *Config) GetEnforcePKCE(ctx context.Context) (enforce bool) {
return c.ProofKeyCodeExchange.Enforce
}
// GetEnforcePKCEForPublicClients returns the enforcement of PKCE for public clients.
func (c *Config) GetEnforcePKCEForPublicClients(ctx context.Context) (enforce bool) {
return c.GetEnforcePKCE(ctx) || c.ProofKeyCodeExchange.EnforcePublicClients
}
// GetEnablePKCEPlainChallengeMethod returns the enable PKCE plain challenge method.
func (c *Config) GetEnablePKCEPlainChallengeMethod(ctx context.Context) (enable bool) {
return c.ProofKeyCodeExchange.AllowPlainChallengeMethod
}
// GetGrantTypeJWTBearerCanSkipClientAuth returns the grant type JWT bearer can skip client auth.
func (c *Config) GetGrantTypeJWTBearerCanSkipClientAuth(ctx context.Context) (skip bool) {
return c.GrantTypeJWTBearer.OptionalClientAuth
}
// GetGrantTypeJWTBearerIDOptional returns the grant type JWT bearer ID optional.
func (c *Config) GetGrantTypeJWTBearerIDOptional(ctx context.Context) (optional bool) {
return c.GrantTypeJWTBearer.OptionalJTIClaim
}
// GetGrantTypeJWTBearerIssuedDateOptional returns the grant type JWT bearer issued date optional.
func (c *Config) GetGrantTypeJWTBearerIssuedDateOptional(ctx context.Context) (optional bool) {
return c.GrantTypeJWTBearer.OptionalIssuedDate
}
// GetJWTMaxDuration returns the JWT max duration.
func (c *Config) GetJWTMaxDuration(ctx context.Context) (duration time.Duration) {
if c.JWTMaxDuration == 0 {
c.JWTMaxDuration = time.Hour * 24
}
return c.JWTMaxDuration
}
// GetRedirectSecureChecker returns the redirect URL security validator.
func (c *Config) GetRedirectSecureChecker(ctx context.Context) func(context.Context, *url.URL) (secure bool) {
return fosite.IsRedirectURISecure
}
// GetOmitRedirectScopeParam must be set to true if the scope query param is to be omitted
// in the authorization's redirect URI.
func (c *Config) GetOmitRedirectScopeParam(ctx context.Context) (omit bool) {
return c.OmitRedirectScopeParameter
}
// GetSanitationWhiteList is a whitelist of form values that are required by the token endpoint. These values
// are safe for storage in a database (cleartext).
func (c *Config) GetSanitationWhiteList(ctx context.Context) (whitelist []string) {
return c.SanitationWhiteList
}
// GetJWTScopeField returns the JWT scope field.
func (c *Config) GetJWTScopeField(ctx context.Context) (field jwt.JWTScopeFieldEnum) {
if c.JWTScopeField == jwt.JWTScopeFieldUnset {
c.JWTScopeField = jwt.JWTScopeFieldList
}
return c.JWTScopeField
}
// GetIDTokenIssuer returns the ID token issuer.
func (c *Config) GetIDTokenIssuer(ctx context.Context) (issuer string) {
return c.Issuers.IDToken
}
// GetAccessTokenIssuer returns the access token issuer.
func (c *Config) GetAccessTokenIssuer(ctx context.Context) (issuer string) {
return c.Issuers.AccessToken
}
// GetDisableRefreshTokenValidation returns the disable refresh token validation flag.
func (c *Config) GetDisableRefreshTokenValidation(ctx context.Context) (disable bool) {
return c.DisableRefreshTokenValidation
}
// GetAuthorizeCodeLifespan returns the authorization code lifespan.
func (c *Config) GetAuthorizeCodeLifespan(ctx context.Context) (lifespan time.Duration) {
if c.Lifespans.AuthorizeCode.Seconds() <= 0 {
c.Lifespans.AuthorizeCode = lifespanAuthorizeCodeDefault
}
return c.Lifespans.AuthorizeCode
}
// GetRefreshTokenLifespan returns the refresh token lifespan.
func (c *Config) GetRefreshTokenLifespan(ctx context.Context) (lifespan time.Duration) {
if c.Lifespans.RefreshToken.Seconds() <= 0 {
c.Lifespans.RefreshToken = lifespanRefreshTokenDefault
}
return c.Lifespans.RefreshToken
}
// GetIDTokenLifespan returns the ID token lifespan.
func (c *Config) GetIDTokenLifespan(ctx context.Context) (lifespan time.Duration) {
if c.Lifespans.IDToken.Seconds() <= 0 {
c.Lifespans.IDToken = lifespanTokenDefault
}
return c.Lifespans.IDToken
}
// GetAccessTokenLifespan returns the access token lifespan.
func (c *Config) GetAccessTokenLifespan(ctx context.Context) (lifespan time.Duration) {
if c.Lifespans.AccessToken.Seconds() <= 0 {
c.Lifespans.AccessToken = lifespanTokenDefault
}
return c.Lifespans.AccessToken
}
// GetTokenEntropy returns the token entropy.
func (c *Config) GetTokenEntropy(ctx context.Context) (entropy int) {
if c.TokenEntropy == 0 {
c.TokenEntropy = 32
}
return c.TokenEntropy
}
// GetGlobalSecret returns the global secret.
func (c *Config) GetGlobalSecret(ctx context.Context) (secret []byte, err error) {
return c.GlobalSecret, nil
}
// GetRotatedGlobalSecrets returns the rotated global secrets.
func (c *Config) GetRotatedGlobalSecrets(ctx context.Context) (secrets [][]byte, err error) {
return c.RotatedGlobalSecrets, nil
}
// GetHTTPClient returns the HTTP client provider.
func (c *Config) GetHTTPClient(ctx context.Context) (client *retryablehttp.Client) {
if c.HTTPClient == nil {
c.HTTPClient = retryablehttp.NewClient()
}
return c.HTTPClient
}
// GetRefreshTokenScopes returns the refresh token scopes.
func (c *Config) GetRefreshTokenScopes(ctx context.Context) (scopes []string) {
if c.RefreshTokenScopes == nil {
c.RefreshTokenScopes = []string{ScopeOffline, ScopeOfflineAccess}
}
return c.RefreshTokenScopes
}
// GetScopeStrategy returns the scope strategy.
func (c *Config) GetScopeStrategy(ctx context.Context) (strategy fosite.ScopeStrategy) {
if c.Strategy.Scope == nil {
c.Strategy.Scope = fosite.ExactScopeStrategy
}
return c.Strategy.Scope
}
// GetAudienceStrategy returns the audience strategy.
func (c *Config) GetAudienceStrategy(ctx context.Context) (strategy fosite.AudienceMatchingStrategy) {
if c.Strategy.Audience == nil {
c.Strategy.Audience = fosite.DefaultAudienceMatchingStrategy
}
return c.Strategy.Audience
}
// GetMinParameterEntropy returns the minimum parameter entropy.
func (c *Config) GetMinParameterEntropy(_ context.Context) (entropy int) {
if c.MinParameterEntropy == 0 {
c.MinParameterEntropy = fosite.MinParameterEntropy
}
return c.MinParameterEntropy
}
// GetHMACHasher returns the hash function.
func (c *Config) GetHMACHasher(ctx context.Context) func() (h hash.Hash) {
if c.Hash.HMAC == nil {
c.Hash.HMAC = sha512.New512_256
}
return c.Hash.HMAC
}
// GetSendDebugMessagesToClients returns the send debug messages to clients.
func (c *Config) GetSendDebugMessagesToClients(ctx context.Context) (send bool) {
return c.SendDebugMessagesToClients
}
// GetJWKSFetcherStrategy returns the JWKS fetcher strategy.
func (c *Config) GetJWKSFetcherStrategy(ctx context.Context) (strategy fosite.JWKSFetcherStrategy) {
if c.Strategy.JWKSFetcher == nil {
c.Strategy.JWKSFetcher = fosite.NewDefaultJWKSFetcherStrategy()
}
return c.Strategy.JWKSFetcher
}
// GetClientAuthenticationStrategy returns the client authentication strategy.
func (c *Config) GetClientAuthenticationStrategy(ctx context.Context) (strategy fosite.ClientAuthenticationStrategy) {
return c.Strategy.ClientAuthentication
}
// GetMessageCatalog returns the message catalog.
func (c *Config) GetMessageCatalog(ctx context.Context) (catalog i18n.MessageCatalog) {
return c.MessageCatalog
}
// GetFormPostHTMLTemplate returns the form post HTML template.
func (c *Config) GetFormPostHTMLTemplate(ctx context.Context) (tmpl *template.Template) {
if c.Templates == nil {
return nil
}
return c.Templates.GetOpenIDConnectAuthorizeResponseFormPostTemplate()
}
// GetTokenURL returns the token URL.
func (c *Config) GetTokenURL(ctx context.Context) (tokenURL string) {
if octx, ok := ctx.(OpenIDConnectContext); ok {
switch issuerURL, err := octx.IssuerURL(); err {
case nil:
return issuerURL.JoinPath(EndpointPathToken).String()
default:
return c.TokenURL
}
}
return c.TokenURL
}
// GetSecretsHasher returns the client secrets hashing function.
func (c *Config) GetSecretsHasher(ctx context.Context) (hasher fosite.Hasher) {
if c.Hash.ClientSecrets == nil {
c.Hash.ClientSecrets, _ = NewHasher()
}
return c.Hash.ClientSecrets
}
// GetUseLegacyErrorFormat returns whether to use the legacy error format.
//
// Deprecated: Do not use this flag anymore.
func (c *Config) GetUseLegacyErrorFormat(ctx context.Context) (use bool) {
return false
}
// GetAuthorizeEndpointHandlers returns the authorize endpoint handlers.
func (c *Config) GetAuthorizeEndpointHandlers(ctx context.Context) (handlers fosite.AuthorizeEndpointHandlers) {
return c.Handlers.AuthorizeEndpoint
}
// GetTokenEndpointHandlers returns the token endpoint handlers.
func (c *Config) GetTokenEndpointHandlers(ctx context.Context) (handlers fosite.TokenEndpointHandlers) {
return c.Handlers.TokenEndpoint
}
// GetTokenIntrospectionHandlers returns the token introspection handlers.
func (c *Config) GetTokenIntrospectionHandlers(ctx context.Context) (handlers fosite.TokenIntrospectionHandlers) {
return c.Handlers.TokenIntrospection
}
// GetRevocationHandlers returns the revocation handlers.
func (c *Config) GetRevocationHandlers(ctx context.Context) (handlers fosite.RevocationHandlers) {
return c.Handlers.Revocation
}
// GetPushedAuthorizeEndpointHandlers returns the handlers.
func (c *Config) GetPushedAuthorizeEndpointHandlers(ctx context.Context) fosite.PushedAuthorizeEndpointHandlers {
return c.Handlers.PushedAuthorizeEndpoint
}
// GetResponseModeHandlerExtension returns the response mode handler extension.
func (c *Config) GetResponseModeHandlerExtension(ctx context.Context) (handler fosite.ResponseModeHandler) {
return c.Handlers.ResponseMode
}
// GetPushedAuthorizeRequestURIPrefix is the request URI prefix. This is
// usually 'urn:ietf:params:oauth:request_uri:'.
func (c *Config) GetPushedAuthorizeRequestURIPrefix(ctx context.Context) string {
if c.PAR.URIPrefix == "" {
c.PAR.URIPrefix = RedirectURIPrefixPushedAuthorizationRequestURN
}
return c.PAR.URIPrefix
}
// EnforcePushedAuthorize indicates if PAR is enforced. In this mode, a client
// cannot pass authorize parameters at the 'authorize' endpoint. The 'authorize' endpoint
// must contain the PAR request_uri.
func (c *Config) EnforcePushedAuthorize(ctx context.Context) bool {
return c.PAR.Enforced
}
// GetPushedAuthorizeContextLifespan is the lifespan of the short-lived PAR context.
func (c *Config) GetPushedAuthorizeContextLifespan(ctx context.Context) (lifespan time.Duration) {
if c.PAR.ContextLifespan.Seconds() <= 0 {
c.PAR.ContextLifespan = lifespanPARContextDefault
}
return c.PAR.ContextLifespan
}