ory-am/hydra

View on GitHub
driver/config/provider.go

Summary

Maintainability
D
1 day
Test Coverage
// Copyright © 2022 Ory Corp
// SPDX-License-Identifier: Apache-2.0

package config

import (
    "context"
    "fmt"
    "net/http"
    "net/url"
    "strings"
    "time"

    "github.com/pkg/errors"

    "github.com/ory/x/hasherx"

    "github.com/gofrs/uuid"

    "github.com/ory/x/otelx"

    "github.com/ory/hydra/v2/spec"
    "github.com/ory/x/dbal"

    "github.com/ory/x/configx"

    "github.com/ory/x/logrusx"

    "github.com/ory/hydra/v2/x"
    "github.com/ory/x/contextx"
    "github.com/ory/x/stringslice"
    "github.com/ory/x/urlx"
)

const (
    KeyRoot                                      = ""
    HSMEnabled                                   = "hsm.enabled"
    HSMLibraryPath                               = "hsm.library"
    HSMPin                                       = "hsm.pin"
    HSMSlotNumber                                = "hsm.slot"
    HSMKeySetPrefix                              = "hsm.key_set_prefix"
    HSMTokenLabel                                = "hsm.token_label" // #nosec G101
    KeyWellKnownKeys                             = "webfinger.jwks.broadcast_keys"
    KeyOAuth2ClientRegistrationURL               = "webfinger.oidc_discovery.client_registration_url"
    KeyOAuth2TokenURL                            = "webfinger.oidc_discovery.token_url" // #nosec G101
    KeyOAuth2AuthURL                             = "webfinger.oidc_discovery.auth_url"
    KeyVerifiableCredentialsURL                  = "webfinger.oidc_discovery.verifiable_credentials_url" // #nosec G101
    KeyJWKSURL                                   = "webfinger.oidc_discovery.jwks_url"
    KeyOIDCDiscoverySupportedClaims              = "webfinger.oidc_discovery.supported_claims"
    KeyOIDCDiscoverySupportedScope               = "webfinger.oidc_discovery.supported_scope"
    KeyOIDCDiscoveryUserinfoEndpoint             = "webfinger.oidc_discovery.userinfo_url"
    KeySubjectTypesSupported                     = "oidc.subject_identifiers.supported_types"
    KeyDefaultClientScope                        = "oidc.dynamic_client_registration.default_scope"
    KeyDSN                                       = "dsn"
    KeyClientHTTPNoPrivateIPRanges               = "clients.http.disallow_private_ip_ranges"
    KeyClientHTTPPrivateIPExceptionURLs          = "clients.http.private_ip_exception_urls"
    KeyHasherAlgorithm                           = "oauth2.hashers.algorithm"
    KeyBCryptCost                                = "oauth2.hashers.bcrypt.cost"
    KeyPBKDF2Iterations                          = "oauth2.hashers.pbkdf2.iterations"
    KeyEncryptSessionData                        = "oauth2.session.encrypt_at_rest"
    KeyCookieSameSiteMode                        = "serve.cookies.same_site_mode"
    KeyCookieSameSiteLegacyWorkaround            = "serve.cookies.same_site_legacy_workaround"
    KeyCookieDomain                              = "serve.cookies.domain"
    KeyCookieSecure                              = "serve.cookies.secure"
    KeyCookieLoginCSRFName                       = "serve.cookies.names.login_csrf"
    KeyCookieConsentCSRFName                     = "serve.cookies.names.consent_csrf"
    KeyCookieSessionName                         = "serve.cookies.names.session"
    KeyCookieSessionPath                         = "serve.cookies.paths.session"
    KeyConsentRequestMaxAge                      = "ttl.login_consent_request"
    KeyAccessTokenLifespan                       = "ttl.access_token"  // #nosec G101
    KeyRefreshTokenLifespan                      = "ttl.refresh_token" // #nosec G101
    KeyVerifiableCredentialsNonceLifespan        = "ttl.vc_nonce"      // #nosec G101
    KeyIDTokenLifespan                           = "ttl.id_token"      // #nosec G101
    KeyAuthCodeLifespan                          = "ttl.auth_code"
    KeyScopeStrategy                             = "strategies.scope"
    KeyGetCookieSecrets                          = "secrets.cookie"
    KeyGetSystemSecret                           = "secrets.system"
    KeyLogoutRedirectURL                         = "urls.post_logout_redirect"
    KeyLoginURL                                  = "urls.login"
    KeyRegistrationURL                           = "urls.registration"
    KeyLogoutURL                                 = "urls.logout"
    KeyConsentURL                                = "urls.consent"
    KeyErrorURL                                  = "urls.error"
    KeyPublicURL                                 = "urls.self.public"
    KeyAdminURL                                  = "urls.self.admin"
    KeyIssuerURL                                 = "urls.self.issuer"
    KeyIdentityProviderAdminURL                  = "urls.identity_provider.url"
    KeyIdentityProviderPublicURL                 = "urls.identity_provider.publicUrl"
    KeyIdentityProviderHeaders                   = "urls.identity_provider.headers"
    KeyAccessTokenStrategy                       = "strategies.access_token"
    KeyJWTScopeClaimStrategy                     = "strategies.jwt.scope_claim"
    KeyDBIgnoreUnknownTableColumns               = "db.ignore_unknown_table_columns"
    KeySubjectIdentifierAlgorithmSalt            = "oidc.subject_identifiers.pairwise.salt"
    KeyPublicAllowDynamicRegistration            = "oidc.dynamic_client_registration.enabled"
    KeyPKCEEnforced                              = "oauth2.pkce.enforced"
    KeyPKCEEnforcedForPublicClients              = "oauth2.pkce.enforced_for_public_clients"
    KeyLogLevel                                  = "log.level"
    KeyCGroupsV1AutoMaxProcsEnabled              = "cgroups.v1.auto_max_procs_enabled"
    KeyGrantAllClientCredentialsScopesPerDefault = "oauth2.client_credentials.default_grant_allowed_scope" // #nosec G101
    KeyExposeOAuth2Debug                         = "oauth2.expose_internal_errors"
    KeyExcludeNotBeforeClaim                     = "oauth2.exclude_not_before_claim"
    KeyAllowedTopLevelClaims                     = "oauth2.allowed_top_level_claims"
    KeyMirrorTopLevelClaims                      = "oauth2.mirror_top_level_claims"
    KeyOAuth2GrantJWTIDOptional                  = "oauth2.grant.jwt.jti_optional"
    KeyOAuth2GrantJWTIssuedDateOptional          = "oauth2.grant.jwt.iat_optional"
    KeyOAuth2GrantJWTMaxDuration                 = "oauth2.grant.jwt.max_ttl"
    KeyRefreshTokenHook                          = "oauth2.refresh_token_hook" // #nosec G101
    KeyTokenHook                                 = "oauth2.token_hook"         // #nosec G101
    KeyDevelopmentMode                           = "dev"
)

const DSNMemory = "memory"

var (
    _ hasherx.PBKDF2Configurator = (*DefaultProvider)(nil)
    _ hasherx.BCryptConfigurator = (*DefaultProvider)(nil)
)

type DefaultProvider struct {
    l *logrusx.Logger
    p *configx.Provider
    c contextx.Contextualizer
}

func (p *DefaultProvider) GetHasherAlgorithm(ctx context.Context) x.HashAlgorithm {
    switch strings.ToLower(p.getProvider(ctx).String(KeyHasherAlgorithm)) {
    case x.HashAlgorithmBCrypt.String():
        return x.HashAlgorithmBCrypt
    case x.HashAlgorithmPBKDF2.String():
        fallthrough
    default:
        return x.HashAlgorithmPBKDF2
    }
}

func (p *DefaultProvider) HasherBcryptConfig(ctx context.Context) *hasherx.BCryptConfig {
    return &hasherx.BCryptConfig{
        Cost: uint32(p.GetBCryptCost(ctx)),
    }
}

func (p *DefaultProvider) HasherPBKDF2Config(ctx context.Context) *hasherx.PBKDF2Config {
    return &hasherx.PBKDF2Config{
        Algorithm:  "sha256",
        Iterations: uint32(p.getProvider(ctx).Int(KeyPBKDF2Iterations)),
        SaltLength: 16,
        KeyLength:  32,
    }
}

func MustNew(ctx context.Context, l *logrusx.Logger, opts ...configx.OptionModifier) *DefaultProvider {
    p, err := New(ctx, l, opts...)
    if err != nil {
        l.WithError(err).Fatalf("Unable to load config.")
    }
    return p
}

func (p *DefaultProvider) getProvider(ctx context.Context) *configx.Provider {
    return p.c.Config(ctx, p.p)
}

func New(ctx context.Context, l *logrusx.Logger, opts ...configx.OptionModifier) (*DefaultProvider, error) {
    opts = append(
        []configx.OptionModifier{
            configx.WithStderrValidationReporter(),
            configx.OmitKeysFromTracing("dsn", "secrets.system", "secrets.cookie"),
            configx.WithImmutables("log", "serve", "dsn", "profiling"),
            configx.WithLogrusWatcher(l),
        }, opts...,
    )

    p, err := configx.New(ctx, spec.ConfigValidationSchema, opts...)
    if err != nil {
        return nil, err
    }
    return NewCustom(l, p, &contextx.Default{}), nil
}

func NewCustom(l *logrusx.Logger, p *configx.Provider, ctxt contextx.Contextualizer) *DefaultProvider {
    l.UseConfig(p)
    return &DefaultProvider{l: l, p: p, c: ctxt}
}

func (p *DefaultProvider) Set(ctx context.Context, key string, value interface{}) error {
    return p.getProvider(ctx).Set(key, value)
}

func (p *DefaultProvider) MustSet(ctx context.Context, key string, value interface{}) {
    if err := p.Set(ctx, key, value); err != nil {
        p.l.WithError(err).Fatalf("Unable to set \"%s\" to \"%s\".", key, value)
    }
}

func (p *DefaultProvider) Source(ctx context.Context) *configx.Provider {
    return p.getProvider(ctx)
}

func (p *DefaultProvider) IsDevelopmentMode(ctx context.Context) bool {
    return p.getProvider(ctx).Bool(KeyDevelopmentMode)
}

func (p *DefaultProvider) WellKnownKeys(ctx context.Context, include ...string) []string {
    include = append(include, x.OAuth2JWTKeyName, x.OpenIDConnectKeyName)
    return stringslice.Unique(append(p.getProvider(ctx).Strings(KeyWellKnownKeys), include...))
}

func (p *DefaultProvider) ClientHTTPNoPrivateIPRanges() bool {
    return p.getProvider(contextx.RootContext).Bool(KeyClientHTTPNoPrivateIPRanges)
}

func (p *DefaultProvider) ClientHTTPPrivateIPExceptionURLs() []string {
    return p.getProvider(contextx.RootContext).Strings(KeyClientHTTPPrivateIPExceptionURLs)
}

func (p *DefaultProvider) AllowedTopLevelClaims(ctx context.Context) []string {
    return stringslice.Unique(p.getProvider(ctx).Strings(KeyAllowedTopLevelClaims))
}

func (p *DefaultProvider) MirrorTopLevelClaims(ctx context.Context) bool {
    return p.getProvider(ctx).BoolF(KeyMirrorTopLevelClaims, true)
}

func (p *DefaultProvider) SubjectTypesSupported(ctx context.Context, additionalSources ...AccessTokenStrategySource) []string {
    types := stringslice.Filter(
        p.getProvider(ctx).StringsF(KeySubjectTypesSupported, []string{"public"}),
        func(s string) bool {
            return !(s == "public" || s == "pairwise")
        },
    )

    if len(types) == 0 {
        types = []string{"public"}
    }

    if stringslice.Has(types, "pairwise") {
        if p.AccessTokenStrategy(ctx, additionalSources...) == AccessTokenJWTStrategy {
            p.l.Warn(`The pairwise subject identifier algorithm is not supported by the JWT OAuth 2.0 Access Token Strategy and is thus being disabled. Please remove "pairwise" from oidc.subject_identifiers.supported_types" (e.g. oidc.subject_identifiers.supported_types=public) or set strategies.access_token to "opaque".`)
            types = stringslice.Filter(
                types,
                func(s string) bool {
                    return !(s == "public")
                },
            )
        } else if len(p.SubjectIdentifierAlgorithmSalt(ctx)) < 8 {
            p.l.Fatalf(
                `The pairwise subject identifier algorithm was set but length of oidc.subject_identifier.salt is too small (%d < 8), please set oidc.subject_identifiers.pairwise.salt to a random string with 8 characters or more.`,
                len(p.SubjectIdentifierAlgorithmSalt(ctx)),
            )
        }
    }

    return types
}

func (p *DefaultProvider) DefaultClientScope(ctx context.Context) []string {
    return p.getProvider(ctx).StringsF(
        KeyDefaultClientScope,
        []string{"offline_access", "offline", "openid"},
    )
}

func (p *DefaultProvider) DSN() string {
    dsn := p.getProvider(contextx.RootContext).String(KeyDSN)

    if dsn == DSNMemory {
        return dbal.NewSQLiteInMemoryDatabase(uuid.Must(uuid.NewV4()).String())
    }

    if len(dsn) > 0 {
        return dsn
    }

    p.l.Fatal("dsn must be set")
    return ""
}

func (p *DefaultProvider) EncryptSessionData(ctx context.Context) bool {
    return p.getProvider(ctx).BoolF(KeyEncryptSessionData, true)
}

func (p *DefaultProvider) ExcludeNotBeforeClaim(ctx context.Context) bool {
    return p.getProvider(ctx).BoolF(KeyExcludeNotBeforeClaim, false)
}

func (p *DefaultProvider) CookieSecure(ctx context.Context) bool {
    if !p.IsDevelopmentMode(ctx) {
        return true
    }
    return p.getProvider(ctx).BoolF(KeyCookieSecure, false)
}

func (p *DefaultProvider) CookieSameSiteMode(ctx context.Context) http.SameSite {
    sameSiteModeStr := p.getProvider(ctx).String(KeyCookieSameSiteMode)
    switch strings.ToLower(sameSiteModeStr) {
    case "lax":
        return http.SameSiteLaxMode
    case "strict":
        return http.SameSiteStrictMode
    case "none":
        if p.IssuerURL(ctx).Scheme != "https" {
            // SameSite=None can only be set for HTTPS issuers.
            return http.SameSiteLaxMode
        }
        return http.SameSiteNoneMode
    default:
        if p.IsDevelopmentMode(ctx) {
            return http.SameSiteLaxMode
        }
        return http.SameSiteDefaultMode
    }
}

func (p *DefaultProvider) PublicAllowDynamicRegistration(ctx context.Context) bool {
    return p.getProvider(ctx).Bool(KeyPublicAllowDynamicRegistration)
}

func (p *DefaultProvider) CookieSameSiteLegacyWorkaround(ctx context.Context) bool {
    return p.getProvider(ctx).Bool(KeyCookieSameSiteLegacyWorkaround)
}

func (p *DefaultProvider) ConsentRequestMaxAge(ctx context.Context) time.Duration {
    return p.getProvider(ctx).DurationF(KeyConsentRequestMaxAge, time.Minute*30)
}

func (p *DefaultProvider) Tracing() *otelx.Config {
    return p.getProvider(contextx.RootContext).TracingConfig("Ory Hydra")
}

func (p *DefaultProvider) GetCookieSecrets(ctx context.Context) ([][]byte, error) {
    secrets := p.getProvider(ctx).Strings(KeyGetCookieSecrets)
    if len(secrets) == 0 {
        secret, err := p.GetGlobalSecret(ctx)
        if err != nil {
            return nil, err
        }
        return [][]byte{secret}, nil
    }

    bs := make([][]byte, len(secrets))
    for k := range secrets {
        bs[k] = []byte(secrets[k])
    }
    return bs, nil
}

func (p *DefaultProvider) LogoutRedirectURL(ctx context.Context) *url.URL {
    return urlRoot(
        p.getProvider(ctx).RequestURIF(
            KeyLogoutRedirectURL, p.publicFallbackURL(ctx, "oauth2/fallbacks/logout/callback"),
        ),
    )
}

func (p *DefaultProvider) publicFallbackURL(ctx context.Context, path string) *url.URL {
    if len(p.PublicURL(ctx).String()) > 0 {
        return urlx.AppendPaths(p.PublicURL(ctx), path)
    }
    return p.fallbackURL(ctx, path, p.host(PublicInterface), p.port(PublicInterface))
}

func (p *DefaultProvider) fallbackURL(ctx context.Context, path string, host string, port int) *url.URL {
    var u url.URL
    u.Scheme = "http"
    if tls := p.TLS(ctx, PublicInterface); tls.Enabled() || !p.IsDevelopmentMode(ctx) {
        u.Scheme = "https"
    }
    if host == "" {
        u.Host = fmt.Sprintf("%s:%d", "localhost", port)
    }
    u.Path = path
    return &u
}

func (p *DefaultProvider) LoginURL(ctx context.Context) *url.URL {
    return urlRoot(p.getProvider(ctx).URIF(KeyLoginURL, p.publicFallbackURL(ctx, "oauth2/fallbacks/login")))
}

func (p *DefaultProvider) RegistrationURL(ctx context.Context) *url.URL {
    return urlRoot(p.getProvider(ctx).URIF(KeyRegistrationURL, p.LoginURL(ctx)))
}

func (p *DefaultProvider) LogoutURL(ctx context.Context) *url.URL {
    return urlRoot(p.getProvider(ctx).RequestURIF(KeyLogoutURL, p.publicFallbackURL(ctx, "oauth2/fallbacks/logout")))
}

func (p *DefaultProvider) ConsentURL(ctx context.Context) *url.URL {
    return urlRoot(p.getProvider(ctx).URIF(KeyConsentURL, p.publicFallbackURL(ctx, "oauth2/fallbacks/consent")))
}

func (p *DefaultProvider) ErrorURL(ctx context.Context) *url.URL {
    return urlRoot(p.getProvider(ctx).RequestURIF(KeyErrorURL, p.publicFallbackURL(ctx, "oauth2/fallbacks/error")))
}

func (p *DefaultProvider) PublicURL(ctx context.Context) *url.URL {
    return urlRoot(p.getProvider(ctx).RequestURIF(KeyPublicURL, p.IssuerURL(ctx)))
}

func (p *DefaultProvider) AdminURL(ctx context.Context) *url.URL {
    return urlRoot(
        p.getProvider(ctx).RequestURIF(
            KeyAdminURL, p.fallbackURL(ctx, "/", p.host(AdminInterface), p.port(AdminInterface)),
        ),
    )
}

func (p *DefaultProvider) IssuerURL(ctx context.Context) *url.URL {
    return p.getProvider(ctx).RequestURIF(
        KeyIssuerURL, p.fallbackURL(ctx, "/", p.host(PublicInterface), p.port(PublicInterface)),
    )
}

func (p *DefaultProvider) KratosAdminURL(ctx context.Context) (*url.URL, bool) {
    u := p.getProvider(ctx).RequestURIF(KeyIdentityProviderAdminURL, nil)

    return u, u != nil
}
func (p *DefaultProvider) KratosPublicURL(ctx context.Context) (*url.URL, bool) {
    u := p.getProvider(ctx).RequestURIF(KeyIdentityProviderPublicURL, nil)

    return u, u != nil
}

func (p *DefaultProvider) KratosRequestHeader(ctx context.Context) http.Header {
    hh := map[string]string{}
    if err := p.getProvider(ctx).Unmarshal(KeyIdentityProviderHeaders, &hh); err != nil {
        p.l.WithError(errors.WithStack(err)).
            Errorf("Configuration value from key %s could not be decoded.", KeyIdentityProviderHeaders)
        return nil
    }

    h := make(http.Header)
    for k, v := range hh {
        h.Set(k, v)
    }

    return h
}

func (p *DefaultProvider) OAuth2ClientRegistrationURL(ctx context.Context) *url.URL {
    return p.getProvider(ctx).RequestURIF(KeyOAuth2ClientRegistrationURL, new(url.URL))
}

func (p *DefaultProvider) OAuth2TokenURL(ctx context.Context) *url.URL {
    return p.getProvider(ctx).RequestURIF(KeyOAuth2TokenURL, urlx.AppendPaths(p.PublicURL(ctx), "/oauth2/token"))
}

func (p *DefaultProvider) OAuth2AuthURL(ctx context.Context) *url.URL {
    return p.getProvider(ctx).RequestURIF(KeyOAuth2AuthURL, urlx.AppendPaths(p.PublicURL(ctx), "/oauth2/auth"))
}

func (p *DefaultProvider) JWKSURL(ctx context.Context) *url.URL {
    return p.getProvider(ctx).RequestURIF(KeyJWKSURL, urlx.AppendPaths(p.IssuerURL(ctx), "/.well-known/jwks.json"))
}

func (p *DefaultProvider) CredentialsEndpointURL(ctx context.Context) *url.URL {
    return p.getProvider(ctx).RequestURIF(KeyVerifiableCredentialsURL, urlx.AppendPaths(p.PublicURL(ctx), "/credentials"))
}

type AccessTokenStrategySource interface {
    GetAccessTokenStrategy() AccessTokenStrategyType
}

func (p *DefaultProvider) AccessTokenStrategy(ctx context.Context, additionalSources ...AccessTokenStrategySource) AccessTokenStrategyType {
    for _, src := range additionalSources {
        if src == nil {
            continue
        }
        if strategy := src.GetAccessTokenStrategy(); strategy != "" {
            return strategy
        }
    }
    s, err := ToAccessTokenStrategyType(p.getProvider(ctx).String(KeyAccessTokenStrategy))
    if err != nil {
        p.l.WithError(err).Warn("Key `strategies.access_token` contains an invalid value, falling back to `opaque` strategy.")
        return AccessTokenDefaultStrategy
    }

    return s
}

type (
    Auth struct {
        Type   string     `json:"type"`
        Config AuthConfig `json:"config"`
    }
    AuthConfig struct {
        In    string `json:"in"`
        Name  string `json:"name"`
        Value string `json:"value"`
    }
    HookConfig struct {
        URL  string `json:"url"`
        Auth *Auth  `json:"auth"`
    }
)

func (p *DefaultProvider) getHookConfig(ctx context.Context, key string) *HookConfig {
    if hookURL := p.getProvider(ctx).RequestURIF(key, nil); hookURL != nil {
        return &HookConfig{
            URL: hookURL.String(),
        }
    }

    var hookConfig *HookConfig
    if err := p.getProvider(ctx).Unmarshal(key, &hookConfig); err != nil {
        p.l.WithError(errors.WithStack(err)).
            Errorf("Configuration value from key %s could not be decoded.", key)
        return nil
    }
    if hookConfig == nil {
        return nil
    }

    // validate URL by parsing it
    u, err := url.ParseRequestURI(hookConfig.URL)
    if err != nil {
        p.l.WithError(errors.WithStack(err)).
            Errorf("Configuration value from key %s could not be decoded.", key)
        return nil
    }
    hookConfig.URL = u.String()

    return hookConfig
}

func (p *DefaultProvider) TokenHookConfig(ctx context.Context) *HookConfig {
    return p.getHookConfig(ctx, KeyTokenHook)
}

func (p *DefaultProvider) TokenRefreshHookConfig(ctx context.Context) *HookConfig {
    return p.getHookConfig(ctx, KeyRefreshTokenHook)
}

func (p *DefaultProvider) DbIgnoreUnknownTableColumns() bool {
    return p.p.Bool(KeyDBIgnoreUnknownTableColumns)
}

func (p *DefaultProvider) SubjectIdentifierAlgorithmSalt(ctx context.Context) string {
    return p.getProvider(ctx).String(KeySubjectIdentifierAlgorithmSalt)
}

func (p *DefaultProvider) OIDCDiscoverySupportedClaims(ctx context.Context) []string {
    return stringslice.Unique(
        append(
            []string{"sub"},
            p.getProvider(ctx).Strings(KeyOIDCDiscoverySupportedClaims)...,
        ),
    )
}

func (p *DefaultProvider) OIDCDiscoverySupportedScope(ctx context.Context) []string {
    return stringslice.Unique(
        append(
            []string{"offline_access", "offline", "openid"},
            p.getProvider(ctx).Strings(KeyOIDCDiscoverySupportedScope)...,
        ),
    )
}

func (p *DefaultProvider) OIDCDiscoveryUserinfoEndpoint(ctx context.Context) *url.URL {
    return p.getProvider(ctx).RequestURIF(
        KeyOIDCDiscoveryUserinfoEndpoint, urlx.AppendPaths(p.PublicURL(ctx), "/userinfo"),
    )
}

func (p *DefaultProvider) GetSendDebugMessagesToClients(ctx context.Context) bool {
    return p.getProvider(ctx).Bool(KeyExposeOAuth2Debug)
}

func (p *DefaultProvider) GetEnforcePKCE(ctx context.Context) bool {
    return p.getProvider(ctx).Bool(KeyPKCEEnforced)
}

func (p *DefaultProvider) GetEnforcePKCEForPublicClients(ctx context.Context) bool {
    return p.getProvider(ctx).Bool(KeyPKCEEnforcedForPublicClients)
}

func (p *DefaultProvider) CGroupsV1AutoMaxProcsEnabled() bool {
    return p.getProvider(contextx.RootContext).Bool(KeyCGroupsV1AutoMaxProcsEnabled)
}

func (p *DefaultProvider) GrantAllClientCredentialsScopesPerDefault(ctx context.Context) bool {
    return p.getProvider(ctx).Bool(KeyGrantAllClientCredentialsScopesPerDefault)
}

func (p *DefaultProvider) HSMEnabled() bool {
    return p.getProvider(contextx.RootContext).Bool(HSMEnabled)
}

func (p *DefaultProvider) HSMLibraryPath() string {
    return p.getProvider(contextx.RootContext).String(HSMLibraryPath)
}

func (p *DefaultProvider) HSMSlotNumber() *int {
    n := p.getProvider(contextx.RootContext).Int(HSMSlotNumber)
    return &n
}

func (p *DefaultProvider) HSMPin() string {
    return p.getProvider(contextx.RootContext).String(HSMPin)
}

func (p *DefaultProvider) HSMTokenLabel() string {
    return p.getProvider(contextx.RootContext).String(HSMTokenLabel)
}

func (p *DefaultProvider) GetGrantTypeJWTBearerIDOptional(ctx context.Context) bool {
    return p.getProvider(ctx).Bool(KeyOAuth2GrantJWTIDOptional)
}

func (p *DefaultProvider) HSMKeySetPrefix() string {
    return p.getProvider(contextx.RootContext).String(HSMKeySetPrefix)
}

func (p *DefaultProvider) GetGrantTypeJWTBearerIssuedDateOptional(ctx context.Context) bool {
    return p.getProvider(ctx).Bool(KeyOAuth2GrantJWTIssuedDateOptional)
}

func (p *DefaultProvider) GetJWTMaxDuration(ctx context.Context) time.Duration {
    return p.getProvider(ctx).DurationF(KeyOAuth2GrantJWTMaxDuration, time.Hour*24*30)
}

func (p *DefaultProvider) CookieDomain(ctx context.Context) string {
    return p.getProvider(ctx).String(KeyCookieDomain)
}

func (p *DefaultProvider) SessionCookiePath(ctx context.Context) string {
    return p.getProvider(ctx).StringF(KeyCookieSessionPath, "/")
}

func (p *DefaultProvider) CookieNameLoginCSRF(ctx context.Context) string {
    return p.cookieSuffix(ctx, KeyCookieLoginCSRFName)
}

func (p *DefaultProvider) CookieNameConsentCSRF(ctx context.Context) string {
    return p.cookieSuffix(ctx, KeyCookieConsentCSRFName)
}

func (p *DefaultProvider) SessionCookieName(ctx context.Context) string {
    return p.cookieSuffix(ctx, KeyCookieSessionName)
}

func (p *DefaultProvider) cookieSuffix(ctx context.Context, key string) string {
    var suffix string
    if p.IsDevelopmentMode(ctx) {
        suffix = "_dev"
    }

    return p.getProvider(ctx).String(key) + suffix
}