summaryrefslogtreecommitdiff
path: root/internal/oidc
diff options
context:
space:
mode:
Diffstat (limited to 'internal/oidc')
-rw-r--r--internal/oidc/client.go272
-rw-r--r--internal/oidc/client_test.go221
-rw-r--r--internal/oidc/config.go6
-rw-r--r--internal/oidc/const.go10
-rw-r--r--internal/oidc/const_test.go12
-rw-r--r--internal/oidc/discovery.go237
-rw-r--r--internal/oidc/discovery_test.go24
-rw-r--r--internal/oidc/provider.go15
-rw-r--r--internal/oidc/provider_test.go52
-rw-r--r--internal/oidc/store.go6
-rw-r--r--internal/oidc/store_test.go63
-rw-r--r--internal/oidc/types.go347
-rw-r--r--internal/oidc/types_test.go4
13 files changed, 936 insertions, 333 deletions
diff --git a/internal/oidc/client.go b/internal/oidc/client.go
index 41d302d03..71b43d867 100644
--- a/internal/oidc/client.go
+++ b/internal/oidc/client.go
@@ -1,8 +1,10 @@
package oidc
import (
+ "github.com/go-crypt/crypt/algorithm"
"github.com/ory/fosite"
"github.com/ory/x/errorsx"
+ "gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
@@ -11,8 +13,8 @@ import (
)
// NewClient creates a new Client.
-func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) {
- client = &Client{
+func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
+ base := &BaseClient{
ID: config.ID,
Description: config.Description,
Secret: config.Secret,
@@ -40,14 +42,165 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client)
}
for _, mode := range config.ResponseModes {
- client.ResponseModes = append(client.ResponseModes, fosite.ResponseModeType(mode))
+ base.ResponseModes = append(base.ResponseModes, fosite.ResponseModeType(mode))
+ }
+
+ if config.TokenEndpointAuthMethod != "" && config.TokenEndpointAuthMethod != "auto" {
+ client = &FullClient{
+ BaseClient: base,
+ TokenEndpointAuthMethod: config.TokenEndpointAuthMethod,
+ }
+ } else {
+ client = base
}
return client
}
+// GetID returns the ID.
+func (c *BaseClient) GetID() string {
+ return c.ID
+}
+
+// GetDescription returns the Description.
+func (c *BaseClient) GetDescription() string {
+ if c.Description == "" {
+ c.Description = c.GetID()
+ }
+
+ return c.Description
+}
+
+// GetSecret returns the Secret.
+func (c *BaseClient) GetSecret() algorithm.Digest {
+ return c.Secret
+}
+
+// GetSectorIdentifier returns the SectorIdentifier for this client.
+func (c *BaseClient) GetSectorIdentifier() string {
+ return c.SectorIdentifier
+}
+
+// GetHashedSecret returns the Secret.
+func (c *BaseClient) GetHashedSecret() (secret []byte) {
+ if c.Secret == nil {
+ return []byte(nil)
+ }
+
+ return []byte(c.Secret.Encode())
+}
+
+// GetRedirectURIs returns the RedirectURIs.
+func (c *BaseClient) GetRedirectURIs() (redirectURIs []string) {
+ return c.RedirectURIs
+}
+
+// GetGrantTypes returns the GrantTypes.
+func (c *BaseClient) GetGrantTypes() fosite.Arguments {
+ if len(c.GrantTypes) == 0 {
+ return fosite.Arguments{"authorization_code"}
+ }
+
+ return c.GrantTypes
+}
+
+// GetResponseTypes returns the ResponseTypes.
+func (c *BaseClient) GetResponseTypes() fosite.Arguments {
+ if len(c.ResponseTypes) == 0 {
+ return fosite.Arguments{"code"}
+ }
+
+ return c.ResponseTypes
+}
+
+// GetScopes returns the Scopes.
+func (c *BaseClient) GetScopes() fosite.Arguments {
+ return c.Scopes
+}
+
+// GetAudience returns the Audience.
+func (c *BaseClient) GetAudience() fosite.Arguments {
+ return c.Audience
+}
+
+// GetResponseModes returns the valid response modes for this client.
+//
+// Implements the fosite.ResponseModeClient.
+func (c *BaseClient) GetResponseModes() []fosite.ResponseModeType {
+ return c.ResponseModes
+}
+
+// GetUserinfoSigningAlgorithm returns the UserinfoSigningAlgorithm.
+func (c *BaseClient) GetUserinfoSigningAlgorithm() string {
+ if c.UserinfoSigningAlgorithm == "" {
+ c.UserinfoSigningAlgorithm = SigningAlgorithmNone
+ }
+
+ return c.UserinfoSigningAlgorithm
+}
+
+// GetPAREnforcement returns EnforcePAR.
+func (c *BaseClient) GetPAREnforcement() bool {
+ return c.EnforcePAR
+}
+
+// GetPKCEEnforcement returns EnforcePKCE.
+func (c *BaseClient) GetPKCEEnforcement() bool {
+ return c.EnforcePKCE
+}
+
+// GetPKCEChallengeMethodEnforcement returns EnforcePKCEChallengeMethod.
+func (c *BaseClient) GetPKCEChallengeMethodEnforcement() bool {
+ return c.EnforcePKCEChallengeMethod
+}
+
+// GetPKCEChallengeMethod returns PKCEChallengeMethod.
+func (c *BaseClient) GetPKCEChallengeMethod() string {
+ return c.PKCEChallengeMethod
+}
+
+// GetAuthorizationPolicy returns Policy.
+func (c *BaseClient) GetAuthorizationPolicy() authorization.Level {
+ return c.Policy
+}
+
+// GetConsentPolicy returns Consent.
+func (c *BaseClient) GetConsentPolicy() ClientConsent {
+ return c.Consent
+}
+
+// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession.
+func (c *BaseClient) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody {
+ body := ConsentGetResponseBody{
+ ClientID: c.ID,
+ ClientDescription: c.Description,
+ PreConfiguration: c.Consent.Mode == ClientConsentModePreConfigured,
+ }
+
+ if consent != nil {
+ body.Scopes = consent.RequestedScopes
+ body.Audience = consent.RequestedAudience
+ }
+
+ return body
+}
+
+// IsPublic returns the value of the Public property.
+func (c *BaseClient) IsPublic() bool {
+ return c.Public
+}
+
+// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
+func (c *BaseClient) IsAuthenticationLevelSufficient(level authentication.Level) bool {
+ if level == authentication.NotAuthenticated {
+ return false
+ }
+
+ return authorization.IsAuthLevelSufficient(level, c.Policy)
+}
+
// ValidatePKCEPolicy is a helper function to validate PKCE policy constraints on a per-client basis.
-func (c *Client) ValidatePKCEPolicy(r fosite.Requester) (err error) {
+func (c *BaseClient) ValidatePKCEPolicy(r fosite.Requester) (err error) {
form := r.GetRequestForm()
if c.EnforcePKCE {
@@ -70,7 +223,7 @@ func (c *Client) ValidatePKCEPolicy(r fosite.Requester) (err error) {
}
// ValidatePARPolicy is a helper function to validate additional policy constraints on a per-client basis.
-func (c *Client) ValidatePARPolicy(r fosite.Requester, prefix string) (err error) {
+func (c *BaseClient) ValidatePARPolicy(r fosite.Requester, prefix string) (err error) {
if c.EnforcePAR {
if !IsPushedAuthorizedRequest(r, prefix) {
switch requestURI := r.GetRequestForm().Get(FormParameterRequestURI); requestURI {
@@ -87,7 +240,7 @@ func (c *Client) ValidatePARPolicy(r fosite.Requester, prefix string) (err error
// ValidateResponseModePolicy is an additional check to the response mode parameter to ensure if it's omitted that the
// default response mode for the fosite.AuthorizeRequester is permitted.
-func (c *Client) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error) {
+func (c *BaseClient) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error) {
if r.GetResponseMode() != fosite.ResponseModeDefault {
return nil
}
@@ -109,91 +262,52 @@ func (c *Client) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err er
return errorsx.WithStack(fosite.ErrUnsupportedResponseMode.WithHintf(`The request omitted the response_mode making the default response_mode "%s" based on the other authorization request parameters but registered OAuth 2.0 client doesn't support this response_mode`, m))
}
-// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient.
-func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool {
- if level == authentication.NotAuthenticated {
- return false
- }
-
- return authorization.IsAuthLevelSufficient(level, c.Policy)
-}
-
-// GetSectorIdentifier returns the SectorIdentifier for this client.
-func (c *Client) GetSectorIdentifier() string {
- return c.SectorIdentifier
-}
-
-// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession.
-func (c *Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody {
- body := ConsentGetResponseBody{
- ClientID: c.ID,
- ClientDescription: c.Description,
- PreConfiguration: c.Consent.Mode == ClientConsentModePreConfigured,
- }
-
- if consent != nil {
- body.Scopes = consent.RequestedScopes
- body.Audience = consent.RequestedAudience
- }
-
- return body
+// GetRequestURIs is an array of request_uri values that are pre-registered by the RP for use at the OP. Servers MAY
+// cache the contents of the files referenced by these URIs and not retrieve them at the time they are used in a request.
+// OPs can require that request_uri values used be pre-registered with the require_request_uri_registration
+// discovery parameter.
+func (c *FullClient) GetRequestURIs() []string {
+ return c.RequestURIs
}
-// GetID returns the ID.
-func (c *Client) GetID() string {
- return c.ID
+// GetJSONWebKeys returns the JSON Web Key Set containing the public key used by the client to authenticate.
+func (c *FullClient) GetJSONWebKeys() *jose.JSONWebKeySet {
+ return c.JSONWebKeys
}
-// GetHashedSecret returns the Secret.
-func (c *Client) GetHashedSecret() (secret []byte) {
- if c.Secret == nil {
- return []byte(nil)
- }
-
- return []byte(c.Secret.Encode())
+// GetJSONWebKeysURI returns the URL for lookup of JSON Web Key Set containing the
+// public key used by the client to authenticate.
+func (c *FullClient) GetJSONWebKeysURI() string {
+ return c.JSONWebKeysURI
}
-// GetRedirectURIs returns the RedirectURIs.
-func (c *Client) GetRedirectURIs() (redirectURIs []string) {
- return c.RedirectURIs
+// GetRequestObjectSigningAlgorithm returns the JWS [JWS] alg algorithm [JWA] that MUST be used for signing Request
+// Objects sent to the OP. All Request Objects from this Client MUST be rejected, if not signed with this algorithm.
+func (c *FullClient) GetRequestObjectSigningAlgorithm() string {
+ return c.RequestObjectSigningAlgorithm
}
-// GetGrantTypes returns the GrantTypes.
-func (c *Client) GetGrantTypes() fosite.Arguments {
- if len(c.GrantTypes) == 0 {
- return fosite.Arguments{"authorization_code"}
+// GetTokenEndpointAuthMethod returns the requested Client Authentication Method for the Token Endpoint. The options are
+// client_secret_post, client_secret_basic, client_secret_jwt, private_key_jwt, and none.
+func (c *FullClient) GetTokenEndpointAuthMethod() string {
+ if c.TokenEndpointAuthMethod == "" {
+ if c.Public {
+ c.TokenEndpointAuthMethod = ClientAuthMethodNone
+ } else {
+ c.TokenEndpointAuthMethod = ClientAuthMethodClientSecretPost
+ }
}
- return c.GrantTypes
+ return c.TokenEndpointAuthMethod
}
-// GetResponseTypes returns the ResponseTypes.
-func (c *Client) GetResponseTypes() fosite.Arguments {
- if len(c.ResponseTypes) == 0 {
- return fosite.Arguments{"code"}
+// GetTokenEndpointAuthSigningAlgorithm returns the JWS [JWS] alg algorithm [JWA] that MUST be used for signing the JWT
+// [JWT] used to authenticate the Client at the Token Endpoint for the private_key_jwt and client_secret_jwt
+// authentication methods.
+func (c *FullClient) GetTokenEndpointAuthSigningAlgorithm() string {
+ if c.TokenEndpointAuthSigningAlgorithm == "" {
+ c.TokenEndpointAuthSigningAlgorithm = SigningAlgorithmRSAWithSHA256
}
- return c.ResponseTypes
-}
-
-// GetScopes returns the Scopes.
-func (c *Client) GetScopes() fosite.Arguments {
- return c.Scopes
-}
-
-// IsPublic returns the value of the Public property.
-func (c *Client) IsPublic() bool {
- return c.Public
-}
-
-// GetAudience returns the Audience.
-func (c *Client) GetAudience() fosite.Arguments {
- return c.Audience
-}
-
-// GetResponseModes returns the valid response modes for this client.
-//
-// Implements the fosite.ResponseModeClient.
-func (c *Client) GetResponseModes() []fosite.ResponseModeType {
- return c.ResponseModes
+ return c.TokenEndpointAuthSigningAlgorithm
}
diff --git a/internal/oidc/client_test.go b/internal/oidc/client_test.go
index 1546644c2..a4be63346 100644
--- a/internal/oidc/client_test.go
+++ b/internal/oidc/client_test.go
@@ -7,6 +7,7 @@ import (
"github.com/ory/fosite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
@@ -15,36 +16,136 @@ import (
)
func TestNewClient(t *testing.T) {
- blankConfig := schema.OpenIDConnectClientConfiguration{}
- blankClient := NewClient(blankConfig)
- assert.Equal(t, "", blankClient.ID)
- assert.Equal(t, "", blankClient.Description)
- assert.Equal(t, "", blankClient.Description)
- assert.Len(t, blankClient.ResponseModes, 0)
-
- exampleConfig := schema.OpenIDConnectClientConfiguration{
- ID: "myapp",
- Description: "My App",
- Policy: "two_factor",
- Secret: MustDecodeSecret("$plaintext$abcdef"),
- RedirectURIs: []string{"https://google.com/callback"},
+ config := schema.OpenIDConnectClientConfiguration{}
+ client := NewClient(config)
+ assert.Equal(t, "", client.GetID())
+ assert.Equal(t, "", client.GetDescription())
+ assert.Len(t, client.GetResponseModes(), 0)
+ assert.Len(t, client.GetResponseTypes(), 1)
+ assert.Equal(t, "", client.GetSectorIdentifier())
+
+ bclient, ok := client.(*BaseClient)
+ require.True(t, ok)
+ assert.Equal(t, "", bclient.UserinfoSigningAlgorithm)
+ assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm())
+
+ _, ok = client.(*FullClient)
+ assert.False(t, ok)
+
+ config = schema.OpenIDConnectClientConfiguration{
+ ID: myclient,
+ Description: myclientdesc,
+ Policy: twofactor,
+ Secret: MustDecodeSecret(badsecret),
+ RedirectURIs: []string{examplecom},
Scopes: schema.DefaultOpenIDConnectClientConfiguration.Scopes,
ResponseTypes: schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes,
GrantTypes: schema.DefaultOpenIDConnectClientConfiguration.GrantTypes,
ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes,
}
- exampleClient := NewClient(exampleConfig)
- assert.Equal(t, "myapp", exampleClient.ID)
- require.Len(t, exampleClient.ResponseModes, 3)
- assert.Equal(t, fosite.ResponseModeFormPost, exampleClient.ResponseModes[0])
- assert.Equal(t, fosite.ResponseModeQuery, exampleClient.ResponseModes[1])
- assert.Equal(t, fosite.ResponseModeFragment, exampleClient.ResponseModes[2])
- assert.Equal(t, authorization.TwoFactor, exampleClient.Policy)
+ client = NewClient(config)
+ assert.Equal(t, myclient, client.GetID())
+ require.Len(t, client.GetResponseModes(), 1)
+ assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0])
+ assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
+
+ config = schema.OpenIDConnectClientConfiguration{
+ TokenEndpointAuthMethod: ClientAuthMethodClientSecretBasic,
+ }
+
+ client = NewClient(config)
+
+ fclient, ok := client.(*FullClient)
+
+ var niljwks *jose.JSONWebKeySet
+
+ require.True(t, ok)
+ assert.Equal(t, "", fclient.UserinfoSigningAlgorithm)
+ assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
+ assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
+ assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm())
+ assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm)
+ assert.Equal(t, SigningAlgorithmRSAWithSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
+ assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm)
+ assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
+ assert.Equal(t, "", fclient.JSONWebKeysURI)
+ assert.Equal(t, "", fclient.GetJSONWebKeysURI())
+ assert.Equal(t, niljwks, fclient.JSONWebKeys)
+ assert.Equal(t, niljwks, fclient.GetJSONWebKeys())
+ assert.Equal(t, []string(nil), fclient.RequestURIs)
+ assert.Equal(t, []string(nil), fclient.GetRequestURIs())
+}
+
+func TestBaseClient_ValidatePARPolicy(t *testing.T) {
+ testCases := []struct {
+ name string
+ client *BaseClient
+ have *fosite.Request
+ expected string
+ }{
+ {
+ "ShouldNotEnforcePAR",
+ &BaseClient{
+ EnforcePAR: false,
+ },
+ &fosite.Request{},
+ "",
+ },
+ {
+ "ShouldEnforcePARAndErrorWithoutCorrectRequestURI",
+ &BaseClient{
+ EnforcePAR: true,
+ },
+ &fosite.Request{
+ Form: map[string][]string{
+ FormParameterRequestURI: {"https://google.com"},
+ },
+ },
+ "invalid_request",
+ },
+ {
+ "ShouldEnforcePARAndErrorWithEmptyRequestURI",
+ &BaseClient{
+ EnforcePAR: true,
+ },
+ &fosite.Request{
+ Form: map[string][]string{
+ FormParameterRequestURI: {""},
+ },
+ },
+ "invalid_request",
+ },
+ {
+ "ShouldEnforcePARAndNotErrorWithCorrectRequestURI",
+ &BaseClient{
+ EnforcePAR: true,
+ },
+ &fosite.Request{
+ Form: map[string][]string{
+ FormParameterRequestURI: {urnPARPrefix + "abc"},
+ },
+ },
+ "",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ err := tc.client.ValidatePARPolicy(tc.have, urnPARPrefix)
+
+ switch tc.expected {
+ case "":
+ assert.NoError(t, err)
+ default:
+ assert.EqualError(t, err, tc.expected)
+ }
+ })
+ }
}
func TestIsAuthenticationLevelSufficient(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
c.Policy = authorization.Bypass
assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated))
@@ -68,7 +169,7 @@ func TestIsAuthenticationLevelSufficient(t *testing.T) {
}
func TestClient_GetConsentResponseBody(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
consentRequestBody := c.GetConsentResponseBody(nil)
assert.Equal(t, "", consentRequestBody.ClientID)
@@ -76,56 +177,56 @@ func TestClient_GetConsentResponseBody(t *testing.T) {
assert.Equal(t, []string(nil), consentRequestBody.Scopes)
assert.Equal(t, []string(nil), consentRequestBody.Audience)
- c.ID = "myclient"
- c.Description = "My Client"
+ c.ID = myclient
+ c.Description = myclientdesc
consent := &model.OAuth2ConsentSession{
- RequestedAudience: []string{"https://example.com"},
- RequestedScopes: []string{"openid", "groups"},
+ RequestedAudience: []string{examplecom},
+ RequestedScopes: []string{ScopeOpenID, ScopeGroups},
}
- expectedScopes := []string{"openid", "groups"}
- expectedAudiences := []string{"https://example.com"}
+ expectedScopes := []string{ScopeOpenID, ScopeGroups}
+ expectedAudiences := []string{examplecom}
consentRequestBody = c.GetConsentResponseBody(consent)
- assert.Equal(t, "myclient", consentRequestBody.ClientID)
- assert.Equal(t, "My Client", consentRequestBody.ClientDescription)
+ assert.Equal(t, myclient, consentRequestBody.ClientID)
+ assert.Equal(t, myclientdesc, consentRequestBody.ClientDescription)
assert.Equal(t, expectedScopes, consentRequestBody.Scopes)
assert.Equal(t, expectedAudiences, consentRequestBody.Audience)
}
func TestClient_GetAudience(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
audience := c.GetAudience()
assert.Len(t, audience, 0)
- c.Audience = []string{"https://example.com"}
+ c.Audience = []string{examplecom}
audience = c.GetAudience()
require.Len(t, audience, 1)
- assert.Equal(t, "https://example.com", audience[0])
+ assert.Equal(t, examplecom, audience[0])
}
func TestClient_GetScopes(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
scopes := c.GetScopes()
assert.Len(t, scopes, 0)
- c.Scopes = []string{"openid"}
+ c.Scopes = []string{ScopeOpenID}
scopes = c.GetScopes()
require.Len(t, scopes, 1)
- assert.Equal(t, "openid", scopes[0])
+ assert.Equal(t, ScopeOpenID, scopes[0])
}
func TestClient_GetGrantTypes(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
grantTypes := c.GetGrantTypes()
require.Len(t, grantTypes, 1)
- assert.Equal(t, "authorization_code", grantTypes[0])
+ assert.Equal(t, GrantTypeAuthorizationCode, grantTypes[0])
c.GrantTypes = []string{"device_code"}
@@ -135,55 +236,55 @@ func TestClient_GetGrantTypes(t *testing.T) {
}
func TestClient_Hashing(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
hashedSecret := c.GetHashedSecret()
assert.Equal(t, []byte(nil), hashedSecret)
- c.Secret = MustDecodeSecret("$plaintext$a_bad_secret")
+ c.Secret = MustDecodeSecret(badsecret)
assert.True(t, c.Secret.MatchBytes([]byte("a_bad_secret")))
}
func TestClient_GetHashedSecret(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
hashedSecret := c.GetHashedSecret()
assert.Equal(t, []byte(nil), hashedSecret)
- c.Secret = MustDecodeSecret("$plaintext$a_bad_secret")
+ c.Secret = MustDecodeSecret(badsecret)
hashedSecret = c.GetHashedSecret()
- assert.Equal(t, []byte("$plaintext$a_bad_secret"), hashedSecret)
+ assert.Equal(t, []byte(badsecret), hashedSecret)
}
func TestClient_GetID(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
id := c.GetID()
assert.Equal(t, "", id)
- c.ID = "myid"
+ c.ID = myclient
id = c.GetID()
- assert.Equal(t, "myid", id)
+ assert.Equal(t, myclient, id)
}
func TestClient_GetRedirectURIs(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
redirectURIs := c.GetRedirectURIs()
require.Len(t, redirectURIs, 0)
- c.RedirectURIs = []string{"https://example.com/oauth2/callback"}
+ c.RedirectURIs = []string{examplecom}
redirectURIs = c.GetRedirectURIs()
require.Len(t, redirectURIs, 1)
- assert.Equal(t, "https://example.com/oauth2/callback", redirectURIs[0])
+ assert.Equal(t, examplecom, redirectURIs[0])
}
func TestClient_GetResponseModes(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
responseModes := c.GetResponseModes()
require.Len(t, responseModes, 0)
@@ -202,18 +303,18 @@ func TestClient_GetResponseModes(t *testing.T) {
}
func TestClient_GetResponseTypes(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
responseTypes := c.GetResponseTypes()
require.Len(t, responseTypes, 1)
- assert.Equal(t, "code", responseTypes[0])
+ assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0])
- c.ResponseTypes = []string{"code", "id_token"}
+ c.ResponseTypes = []string{ResponseTypeAuthorizationCodeFlow, ResponseTypeImplicitFlowIDToken}
responseTypes = c.GetResponseTypes()
require.Len(t, responseTypes, 2)
- assert.Equal(t, "code", responseTypes[0])
- assert.Equal(t, "id_token", responseTypes[1])
+ assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0])
+ assert.Equal(t, ResponseTypeImplicitFlowIDToken, responseTypes[1])
}
func TestNewClientPKCE(t *testing.T) {
@@ -290,9 +391,9 @@ func TestNewClientPKCE(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
client := NewClient(tc.have)
- assert.Equal(t, tc.expectedEnforcePKCE, client.EnforcePKCE)
- assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.EnforcePKCEChallengeMethod)
- assert.Equal(t, tc.expected, client.PKCEChallengeMethod)
+ assert.Equal(t, tc.expectedEnforcePKCE, client.GetPKCEEnforcement())
+ assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.GetPKCEChallengeMethodEnforcement())
+ assert.Equal(t, tc.expected, client.GetPKCEChallengeMethod())
if tc.r != nil {
err := client.ValidatePKCEPolicy(tc.r)
@@ -355,7 +456,7 @@ func TestNewClientPAR(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
client := NewClient(tc.have)
- assert.Equal(t, tc.expected, client.EnforcePAR)
+ assert.Equal(t, tc.expected, client.GetPAREnforcement())
if tc.r != nil {
err := client.ValidatePARPolicy(tc.r, urnPARPrefix)
@@ -437,7 +538,7 @@ func TestNewClientResponseModes(t *testing.T) {
}
func TestClient_IsPublic(t *testing.T) {
- c := Client{}
+ c := &FullClient{BaseClient: &BaseClient{}}
assert.False(t, c.IsPublic())
diff --git a/internal/oidc/config.go b/internal/oidc/config.go
index 1cc7bf098..1db4e757a 100644
--- a/internal/oidc/config.go
+++ b/internal/oidc/config.go
@@ -169,12 +169,6 @@ type LifespanConfig struct {
RefreshToken time.Duration
}
-const (
- PromptNone = none
- PromptLogin = "login"
- PromptConsent = "consent"
-)
-
// LoadHandlers reloads the handlers based on the current configuration.
func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) {
validator := openid.NewOpenIDConnectRequestValidator(strategy, c)
diff --git a/internal/oidc/const.go b/internal/oidc/const.go
index db8c3a23d..01670e317 100644
--- a/internal/oidc/const.go
+++ b/internal/oidc/const.go
@@ -69,15 +69,12 @@ const (
GrantTypeImplicit = implicit
GrantTypeRefreshToken = "refresh_token"
GrantTypeAuthorizationCode = "authorization_code"
- GrantTypePassword = "password"
- GrantTypeClientCredentials = "client_credentials"
)
// Client Auth Method strings.
const (
ClientAuthMethodClientSecretBasic = "client_secret_basic"
ClientAuthMethodClientSecretPost = "client_secret_post"
- ClientAuthMethodClientSecretJWT = "client_secret_jwt"
ClientAuthMethodNone = "none"
)
@@ -117,6 +114,13 @@ const (
FormParameterCodeChallengeMethod = "code_challenge_method"
)
+const (
+ PromptNone = none
+ PromptLogin = "login"
+ PromptConsent = "consent"
+ // PromptCreate = "create" // This prompt value is currently unused.
+)
+
// Endpoints.
const (
EndpointAuthorization = "authorization"
diff --git a/internal/oidc/const_test.go b/internal/oidc/const_test.go
new file mode 100644
index 000000000..b5e3d915b
--- /dev/null
+++ b/internal/oidc/const_test.go
@@ -0,0 +1,12 @@
+package oidc
+
+const (
+ myclient = "myclient"
+ myclientdesc = "My Client"
+ onefactor = "one_factor"
+ twofactor = "two_factor"
+ examplecom = "https://example.com"
+ examplecomsid = "example.com"
+ badsecret = "$plaintext$a_bad_secret"
+ badhmac = "asbdhaaskmdlkamdklasmdlkams"
+)
diff --git a/internal/oidc/discovery.go b/internal/oidc/discovery.go
index 996e5e3f5..e57bad146 100644
--- a/internal/oidc/discovery.go
+++ b/internal/oidc/discovery.go
@@ -5,70 +5,76 @@ import (
)
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
-func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration, clients map[string]*Client) (config OpenIDConnectWellKnownConfiguration) {
+func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration) (config OpenIDConnectWellKnownConfiguration) {
config = OpenIDConnectWellKnownConfiguration{
- CommonDiscoveryOptions: CommonDiscoveryOptions{
- SubjectTypesSupported: []string{
- SubjectTypePublic,
+ OAuth2WellKnownConfiguration: OAuth2WellKnownConfiguration{
+ CommonDiscoveryOptions: CommonDiscoveryOptions{
+ SubjectTypesSupported: []string{
+ SubjectTypePublic,
+ SubjectTypePairwise,
+ },
+ ResponseTypesSupported: []string{
+ ResponseTypeAuthorizationCodeFlow,
+ ResponseTypeImplicitFlowIDToken,
+ ResponseTypeImplicitFlowToken,
+ ResponseTypeImplicitFlowBoth,
+ ResponseTypeHybridFlowIDToken,
+ ResponseTypeHybridFlowToken,
+ ResponseTypeHybridFlowBoth,
+ },
+ GrantTypesSupported: []string{
+ GrantTypeAuthorizationCode,
+ GrantTypeImplicit,
+ GrantTypeRefreshToken,
+ },
+ ResponseModesSupported: []string{
+ ResponseModeFormPost,
+ ResponseModeQuery,
+ ResponseModeFragment,
+ },
+ ScopesSupported: []string{
+ ScopeOfflineAccess,
+ ScopeOpenID,
+ ScopeProfile,
+ ScopeGroups,
+ ScopeEmail,
+ },
+ ClaimsSupported: []string{
+ ClaimAuthenticationMethodsReference,
+ ClaimAudience,
+ ClaimAuthorizedParty,
+ ClaimClientIdentifier,
+ ClaimExpirationTime,
+ ClaimIssuedAt,
+ ClaimIssuer,
+ ClaimJWTID,
+ ClaimRequestedAt,
+ ClaimSubject,
+ ClaimAuthenticationTime,
+ ClaimNonce,
+ ClaimPreferredEmail,
+ ClaimEmailVerified,
+ ClaimEmailAlts,
+ ClaimGroups,
+ ClaimPreferredUsername,
+ ClaimFullName,
+ },
+ TokenEndpointAuthMethodsSupported: []string{
+ ClientAuthMethodClientSecretBasic,
+ ClientAuthMethodClientSecretPost,
+ ClientAuthMethodNone,
+ },
},
- ResponseTypesSupported: []string{
- ResponseTypeAuthorizationCodeFlow,
- ResponseTypeImplicitFlowIDToken,
- ResponseTypeImplicitFlowToken,
- ResponseTypeImplicitFlowBoth,
- ResponseTypeHybridFlowIDToken,
- ResponseTypeHybridFlowToken,
- ResponseTypeHybridFlowBoth,
+ OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
+ CodeChallengeMethodsSupported: []string{
+ PKCEChallengeMethodSHA256,
+ },
},
- GrantTypesSupported: []string{
- GrantTypeAuthorizationCode,
- GrantTypeImplicit,
- GrantTypeRefreshToken,
- },
- ResponseModesSupported: []string{
- ResponseModeFormPost,
- ResponseModeQuery,
- ResponseModeFragment,
- },
- ScopesSupported: []string{
- ScopeOfflineAccess,
- ScopeOpenID,
- ScopeProfile,
- ScopeGroups,
- ScopeEmail,
- },
- ClaimsSupported: []string{
- ClaimAuthenticationMethodsReference,
- ClaimAudience,
- ClaimAuthorizedParty,
- ClaimClientIdentifier,
- ClaimExpirationTime,
- ClaimIssuedAt,
- ClaimIssuer,
- ClaimJWTID,
- ClaimRequestedAt,
- ClaimSubject,
- ClaimAuthenticationTime,
- ClaimNonce,
- ClaimPreferredEmail,
- ClaimEmailVerified,
- ClaimEmailAlts,
- ClaimGroups,
- ClaimPreferredUsername,
- ClaimFullName,
- },
- TokenEndpointAuthMethodsSupported: []string{
- ClientAuthMethodClientSecretBasic,
- ClientAuthMethodClientSecretPost,
- ClientAuthMethodClientSecretJWT,
- ClientAuthMethodNone,
- },
- },
- OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
- CodeChallengeMethodsSupported: []string{
- PKCEChallengeMethodSHA256,
+ OAuth2PushedAuthorizationDiscoveryOptions: &OAuth2PushedAuthorizationDiscoveryOptions{
+ RequirePushedAuthorizationRequests: c.PAR.Enforce,
},
},
+
OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
IDTokenSigningAlgValuesSupported: []string{
SigningAlgorithmRSAWithSHA256,
@@ -77,35 +83,110 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
SigningAlgorithmNone,
SigningAlgorithmRSAWithSHA256,
},
- RequestObjectSigningAlgValuesSupported: []string{
- SigningAlgorithmNone,
- SigningAlgorithmRSAWithSHA256,
- },
},
- PushedAuthorizationDiscoveryOptions: PushedAuthorizationDiscoveryOptions{
- RequirePushedAuthorizationRequests: c.PAR.Enforce,
+ OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
+ OpenIDConnectBackChannelLogoutDiscoveryOptions: &OpenIDConnectBackChannelLogoutDiscoveryOptions{},
+ OpenIDConnectPromptCreateDiscoveryOptions: &OpenIDConnectPromptCreateDiscoveryOptions{
+ PromptValuesSupported: []string{
+ PromptNone,
+ PromptConsent,
+ },
},
}
- var pairwise, public bool
+ if c.EnablePKCEPlainChallenge {
+ config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
+ }
+
+ return config
+}
+
+// Copy the values of the OAuth2WellKnownConfiguration and return it as a new struct.
+func (opts OAuth2WellKnownConfiguration) Copy() (optsCopy OAuth2WellKnownConfiguration) {
+ optsCopy = OAuth2WellKnownConfiguration{
+ CommonDiscoveryOptions: opts.CommonDiscoveryOptions,
+ OAuth2DiscoveryOptions: opts.OAuth2DiscoveryOptions,
+ }
- for _, client := range clients {
- if pairwise && public {
- break
- }
+ if opts.OAuth2DeviceAuthorizationGrantDiscoveryOptions != nil {
+ optsCopy.OAuth2DeviceAuthorizationGrantDiscoveryOptions = &OAuth2DeviceAuthorizationGrantDiscoveryOptions{}
+ *optsCopy.OAuth2DeviceAuthorizationGrantDiscoveryOptions = *opts.OAuth2DeviceAuthorizationGrantDiscoveryOptions
+ }
- if client.SectorIdentifier != "" {
- pairwise = true
- }
+ if opts.OAuth2MutualTLSClientAuthenticationDiscoveryOptions != nil {
+ optsCopy.OAuth2MutualTLSClientAuthenticationDiscoveryOptions = &OAuth2MutualTLSClientAuthenticationDiscoveryOptions{}
+ *optsCopy.OAuth2MutualTLSClientAuthenticationDiscoveryOptions = *opts.OAuth2MutualTLSClientAuthenticationDiscoveryOptions
}
- if pairwise {
- config.SubjectTypesSupported = append(config.SubjectTypesSupported, SubjectTypePairwise)
+ if opts.OAuth2IssuerIdentificationDiscoveryOptions != nil {
+ optsCopy.OAuth2IssuerIdentificationDiscoveryOptions = &OAuth2IssuerIdentificationDiscoveryOptions{}
+ *optsCopy.OAuth2IssuerIdentificationDiscoveryOptions = *opts.OAuth2IssuerIdentificationDiscoveryOptions
}
- if c.EnablePKCEPlainChallenge {
- config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
+ if opts.OAuth2JWTIntrospectionResponseDiscoveryOptions != nil {
+ optsCopy.OAuth2JWTIntrospectionResponseDiscoveryOptions = &OAuth2JWTIntrospectionResponseDiscoveryOptions{}
+ *optsCopy.OAuth2JWTIntrospectionResponseDiscoveryOptions = *opts.OAuth2JWTIntrospectionResponseDiscoveryOptions
}
- return config
+ if opts.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions != nil {
+ optsCopy.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions = &OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions{}
+ *optsCopy.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions = *opts.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions
+ }
+
+ if opts.OAuth2PushedAuthorizationDiscoveryOptions != nil {
+ optsCopy.OAuth2PushedAuthorizationDiscoveryOptions = &OAuth2PushedAuthorizationDiscoveryOptions{}
+ *optsCopy.OAuth2PushedAuthorizationDiscoveryOptions = *opts.OAuth2PushedAuthorizationDiscoveryOptions
+ }
+
+ return optsCopy
+}
+
+// Copy the values of the OpenIDConnectWellKnownConfiguration and return it as a new struct.
+func (opts OpenIDConnectWellKnownConfiguration) Copy() (optsCopy OpenIDConnectWellKnownConfiguration) {
+ optsCopy = OpenIDConnectWellKnownConfiguration{
+ OAuth2WellKnownConfiguration: opts.OAuth2WellKnownConfiguration.Copy(),
+ OpenIDConnectDiscoveryOptions: opts.OpenIDConnectDiscoveryOptions,
+ }
+
+ if opts.OpenIDConnectFrontChannelLogoutDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectFrontChannelLogoutDiscoveryOptions = &OpenIDConnectFrontChannelLogoutDiscoveryOptions{}
+ *optsCopy.OpenIDConnectFrontChannelLogoutDiscoveryOptions = *opts.OpenIDConnectFrontChannelLogoutDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectBackChannelLogoutDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectBackChannelLogoutDiscoveryOptions = &OpenIDConnectBackChannelLogoutDiscoveryOptions{}
+ *optsCopy.OpenIDConnectBackChannelLogoutDiscoveryOptions = *opts.OpenIDConnectBackChannelLogoutDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectSessionManagementDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectSessionManagementDiscoveryOptions = &OpenIDConnectSessionManagementDiscoveryOptions{}
+ *optsCopy.OpenIDConnectSessionManagementDiscoveryOptions = *opts.OpenIDConnectSessionManagementDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectRPInitiatedLogoutDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectRPInitiatedLogoutDiscoveryOptions = &OpenIDConnectRPInitiatedLogoutDiscoveryOptions{}
+ *optsCopy.OpenIDConnectRPInitiatedLogoutDiscoveryOptions = *opts.OpenIDConnectRPInitiatedLogoutDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectPromptCreateDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectPromptCreateDiscoveryOptions = &OpenIDConnectPromptCreateDiscoveryOptions{}
+ *optsCopy.OpenIDConnectPromptCreateDiscoveryOptions = *opts.OpenIDConnectPromptCreateDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions = &OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions{}
+ *optsCopy.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions = *opts.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions
+ }
+
+ if opts.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions != nil {
+ optsCopy.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions = &OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions{}
+ *optsCopy.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions = *opts.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions
+ }
+
+ if opts.OpenIDFederationDiscoveryOptions != nil {
+ optsCopy.OpenIDFederationDiscoveryOptions = &OpenIDFederationDiscoveryOptions{}
+ *optsCopy.OpenIDFederationDiscoveryOptions = *opts.OpenIDFederationDiscoveryOptions
+ }
+
+ return optsCopy
}
diff --git a/internal/oidc/discovery_test.go b/internal/oidc/discovery_test.go
index 63ad18b65..34ca1f732 100644
--- a/internal/oidc/discovery_test.go
+++ b/internal/oidc/discovery_test.go
@@ -13,51 +13,51 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
desc string
pkcePlainChallenge bool
enforcePAR bool
- clients map[string]*Client
+ clients map[string]Client
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string
}{
{
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
pkcePlainChallenge: false,
- clients: map[string]*Client{"a": {}},
+ clients: map[string]Client{"a": &BaseClient{}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
- expectSubjectTypesSupported: []string{SubjectTypePublic},
+ expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
pkcePlainChallenge: true,
- clients: map[string]*Client{"a": {}},
+ clients: map[string]Client{"a": &BaseClient{}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
- expectSubjectTypesSupported: []string{SubjectTypePublic},
+ expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
pkcePlainChallenge: false,
- clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
+ clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
pkcePlainChallenge: true,
- clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
+ clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveTokenAuthMethodsNone",
pkcePlainChallenge: true,
- clients: map[string]*Client{"a": {SectorIdentifier: "yes"}},
+ clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
},
{
desc: "ShouldHaveTokenAuthMethodsNone",
pkcePlainChallenge: true,
- clients: map[string]*Client{
- "a": {SectorIdentifier: "yes"},
- "b": {SectorIdentifier: "yes"},
+ clients: map[string]Client{
+ "a": &BaseClient{SectorIdentifier: "yes"},
+ "b": &BaseClient{SectorIdentifier: "yes"},
},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
@@ -73,7 +73,7 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
},
}
- actual := NewOpenIDConnectWellKnownConfiguration(&c, tc.clients)
+ actual := NewOpenIDConnectWellKnownConfiguration(&c)
for _, codeChallengeMethod := range tc.expectCodeChallengeMethodsSupported {
assert.Contains(t, actual.CodeChallengeMethodsSupported, codeChallengeMethod)
}
diff --git a/internal/oidc/provider.go b/internal/oidc/provider.go
index 54bd1595d..b8333351b 100644
--- a/internal/oidc/provider.go
+++ b/internal/oidc/provider.go
@@ -37,17 +37,14 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store s
provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy())
- provider.discovery = NewOpenIDConnectWellKnownConfiguration(config, provider.Store.clients)
+ provider.discovery = NewOpenIDConnectWellKnownConfiguration(config)
return provider, nil
}
// GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration.
func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) OAuth2WellKnownConfiguration {
- options := OAuth2WellKnownConfiguration{
- CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions,
- OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions,
- }
+ options := p.discovery.OAuth2WellKnownConfiguration.Copy()
options.Issuer = issuer
@@ -63,13 +60,7 @@ func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) O
// GetOpenIDConnectWellKnownConfiguration returns the discovery document for the OpenID Configuration.
func (p *OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer string) OpenIDConnectWellKnownConfiguration {
- options := OpenIDConnectWellKnownConfiguration{
- CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions,
- OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions,
- OpenIDConnectDiscoveryOptions: p.discovery.OpenIDConnectDiscoveryOptions,
- OpenIDConnectFrontChannelLogoutDiscoveryOptions: p.discovery.OpenIDConnectFrontChannelLogoutDiscoveryOptions,
- OpenIDConnectBackChannelLogoutDiscoveryOptions: p.discovery.OpenIDConnectBackChannelLogoutDiscoveryOptions,
- }
+ options := p.discovery.Copy()
options.Issuer = issuer
diff --git a/internal/oidc/provider_test.go b/internal/oidc/provider_test.go
index 3045c6fc3..4a1a0b115 100644
--- a/internal/oidc/provider_test.go
+++ b/internal/oidc/provider_test.go
@@ -27,15 +27,15 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
EnablePKCEPlainChallenge: true,
- HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
+ HMACSecret: badhmac,
Clients: []schema.OpenIDConnectClientConfiguration{
{
- ID: "a-client",
- Secret: MustDecodeSecret("$plaintext$a-client-secret"),
- SectorIdentifier: url.URL{Host: "google.com"},
- Policy: "one_factor",
+ ID: myclient,
+ Secret: MustDecodeSecret(badsecret),
+ SectorIdentifier: url.URL{Host: examplecomsid},
+ Policy: onefactor,
RedirectURIs: []string{
- "https://google.com",
+ examplecom,
},
},
},
@@ -43,7 +43,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
assert.NoError(t, err)
- disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
+ disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
@@ -58,12 +58,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
- HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
+ HMACSecret: badhmac,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
- Policy: "one_factor",
+ Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
@@ -72,7 +72,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
ID: "b-client",
Description: "Normal Description",
Secret: MustDecodeSecret("$plaintext$b-client-secret"),
- Policy: "two_factor",
+ Policy: twofactor,
RedirectURIs: []string{
"https://google.com",
},
@@ -103,7 +103,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
- Policy: "one_factor",
+ Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
@@ -113,9 +113,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.NoError(t, err)
- disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
+ disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
- assert.Equal(t, "https://example.com", disco.Issuer)
+ assert.Equal(t, examplecom, disco.Issuer)
assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI)
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
@@ -139,8 +139,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery)
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
- assert.Len(t, disco.SubjectTypesSupported, 1)
+ assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
+ assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
@@ -151,10 +152,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
- assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
+ assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
- assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
@@ -169,9 +169,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmNone)
- assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2)
- assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
- assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmNone)
+ assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 0)
assert.Len(t, disco.ClaimsSupported, 18)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
@@ -203,7 +201,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
- Policy: "one_factor",
+ Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
@@ -213,9 +211,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
assert.NoError(t, err)
- disco := provider.GetOAuth2WellKnownConfiguration("https://example.com")
+ disco := provider.GetOAuth2WellKnownConfiguration(examplecom)
- assert.Equal(t, "https://example.com", disco.Issuer)
+ assert.Equal(t, examplecom, disco.Issuer)
assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI)
assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint)
assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint)
@@ -238,8 +236,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery)
assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment)
- assert.Len(t, disco.SubjectTypesSupported, 1)
+ assert.Len(t, disco.SubjectTypesSupported, 2)
assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic)
+ assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise)
assert.Len(t, disco.ResponseTypesSupported, 7)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow)
@@ -250,10 +249,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
- assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
+ assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
- assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
@@ -292,7 +290,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
- Policy: "one_factor",
+ Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
@@ -302,7 +300,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.NoError(t, err)
- disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com")
+ disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
require.Len(t, disco.CodeChallengeMethodsSupported, 2)
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
diff --git a/internal/oidc/store.go b/internal/oidc/store.go
index a5a181c2b..2f21b368c 100644
--- a/internal/oidc/store.go
+++ b/internal/oidc/store.go
@@ -24,7 +24,7 @@ func NewStore(config *schema.OpenIDConnectConfiguration, provider storage.Provid
store = &Store{
provider: provider,
- clients: map[string]*Client{},
+ clients: map[string]Client{},
}
for _, client := range config.Clients {
@@ -72,11 +72,11 @@ func (s *Store) GetClientPolicy(id string) (level authorization.Level) {
return authorization.TwoFactor
}
- return client.Policy
+ return client.GetAuthorizationPolicy()
}
// GetFullClient returns a fosite.Client asserted as an Client matching the provided id.
-func (s *Store) GetFullClient(id string) (client *Client, err error) {
+func (s *Store) GetFullClient(id string) (client Client, err error) {
client, ok := s.clients[id]
if !ok {
return nil, fosite.ErrInvalidClient
diff --git a/internal/oidc/store_test.go b/internal/oidc/store_test.go
index 580e864e4..def1d4e8e 100644
--- a/internal/oidc/store_test.go
+++ b/internal/oidc/store_test.go
@@ -4,6 +4,7 @@ import (
"context"
"testing"
+ "github.com/ory/fosite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -17,23 +18,23 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{
{
- ID: "myclient",
- Description: "myclient desc",
- Policy: "one_factor",
+ ID: myclient,
+ Description: myclientdesc,
+ Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
},
{
ID: "myotherclient",
- Description: "myclient desc",
- Policy: "two_factor",
+ Description: myclientdesc,
+ Policy: twofactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
},
},
}, nil)
- policyOne := s.GetClientPolicy("myclient")
+ policyOne := s.GetClientPolicy(myclient)
assert.Equal(t, authorization.OneFactor, policyOne)
policyTwo := s.GetClientPolicy("myotherclient")
@@ -49,9 +50,9 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{
{
- ID: "myclient",
- Description: "myclient desc",
- Policy: "one_factor",
+ ID: myclient,
+ Description: myclientdesc,
+ Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
},
@@ -62,17 +63,19 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
assert.EqualError(t, err, "invalid_client")
assert.Nil(t, client)
- client, err = s.GetClient(context.Background(), "myclient")
+ client, err = s.GetClient(context.Background(), myclient)
require.NoError(t, err)
require.NotNil(t, client)
- assert.Equal(t, "myclient", client.GetID())
+ assert.Equal(t, myclient, client.GetID())
}
func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
+ id := myclient
+
c1 := schema.OpenIDConnectClientConfiguration{
- ID: "myclient",
- Description: "myclient desc",
- Policy: "one_factor",
+ ID: id,
+ Description: myclientdesc,
+ Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
}
@@ -83,24 +86,24 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
Clients: []schema.OpenIDConnectClientConfiguration{c1},
}, nil)
- client, err := s.GetFullClient(c1.ID)
+ client, err := s.GetFullClient(id)
require.NoError(t, err)
require.NotNil(t, client)
- assert.Equal(t, client.ID, c1.ID)
- assert.Equal(t, client.Description, c1.Description)
- assert.Equal(t, client.Scopes, c1.Scopes)
- assert.Equal(t, client.GrantTypes, c1.GrantTypes)
- assert.Equal(t, client.ResponseTypes, c1.ResponseTypes)
- assert.Equal(t, client.RedirectURIs, c1.RedirectURIs)
- assert.Equal(t, client.Policy, authorization.OneFactor)
- assert.Equal(t, client.Secret.Encode(), "$plaintext$mysecret")
+ assert.Equal(t, id, client.GetID())
+ assert.Equal(t, myclientdesc, client.GetDescription())
+ assert.Equal(t, fosite.Arguments(c1.Scopes), client.GetScopes())
+ assert.Equal(t, fosite.Arguments([]string{GrantTypeAuthorizationCode}), client.GetGrantTypes())
+ assert.Equal(t, fosite.Arguments([]string{ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes())
+ assert.Equal(t, []string(nil), client.GetRedirectURIs())
+ assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy())
+ assert.Equal(t, "$plaintext$mysecret", client.GetSecret().Encode())
}
func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
c1 := schema.OpenIDConnectClientConfiguration{
- ID: "myclient",
- Description: "myclient desc",
- Policy: "one_factor",
+ ID: myclient,
+ Description: myclientdesc,
+ Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
}
@@ -122,16 +125,16 @@ func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{
{
- ID: "myclient",
- Description: "myclient desc",
- Policy: "one_factor",
+ ID: myclient,
+ Description: myclientdesc,
+ Policy: onefactor,
Scopes: []string{ScopeOpenID, ScopeProfile},
Secret: MustDecodeSecret("$plaintext$mysecret"),
},
},
}, nil)
- validClient := s.IsValidClientID("myclient")
+ validClient := s.IsValidClientID(myclient)
invalidClient := s.IsValidClientID("myinvalidclient")
assert.True(t, validClient)
diff --git a/internal/oidc/types.go b/internal/oidc/types.go
index 7403f2fed..8606fe3a3 100644
--- a/internal/oidc/types.go
+++ b/internal/oidc/types.go
@@ -12,6 +12,7 @@ import (
"github.com/ory/herodot"
"gopkg.in/square/go-jose.v2"
+ "github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/storage"
@@ -97,17 +98,19 @@ type OpenIDConnectProvider struct {
// openid.OpenIDConnectRequestStorage, and partially implements rfc7523.RFC7523KeyStorage.
type Store struct {
provider storage.Provider
- clients map[string]*Client
+ clients map[string]Client
}
-// Client represents the client internally.
-type Client struct {
+// BaseClient is the base for all clients.
+type BaseClient struct {
ID string
Description string
Secret algorithm.Digest
SectorIdentifier string
Public bool
+ EnforcePAR bool
+
EnforcePKCE bool
EnforcePKCEChallengeMethod bool
PKCEChallengeMethod string
@@ -119,8 +122,6 @@ type Client struct {
ResponseTypes []string
ResponseModes []fosite.ResponseModeType
- EnforcePAR bool
-
UserinfoSigningAlgorithm string
Policy authorization.Level
@@ -128,6 +129,43 @@ type Client struct {
Consent ClientConsent
}
+// FullClient is the client with comprehensive supported features.
+type FullClient struct {
+ *BaseClient
+
+ RequestURIs []string
+ JSONWebKeys *jose.JSONWebKeySet
+ JSONWebKeysURI string
+ RequestObjectSigningAlgorithm string
+ TokenEndpointAuthMethod string
+ TokenEndpointAuthSigningAlgorithm string
+}
+
+// Client represents the internal client definitions.
+type Client interface {
+ fosite.Client
+ fosite.ResponseModeClient
+
+ GetDescription() string
+ GetSecret() algorithm.Digest
+ GetSectorIdentifier() string
+ GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody
+ GetUserinfoSigningAlgorithm() string
+
+ GetPAREnforcement() bool
+ GetPKCEEnforcement() bool
+ GetPKCEChallengeMethodEnforcement() bool
+ GetPKCEChallengeMethod() string
+ GetAuthorizationPolicy() authorization.Level
+ GetConsentPolicy() ClientConsent
+
+ IsAuthenticationLevelSufficient(level authentication.Level) bool
+
+ ValidatePKCEPolicy(r fosite.Requester) (err error)
+ ValidatePARPolicy(r fosite.Requester, prefix string) (err error)
+ ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error)
+}
+
// NewClientConsent converts the schema.OpenIDConnectClientConsentConfig into a oidc.ClientConsent.
func NewClientConsent(mode string, duration *time.Duration) ClientConsent {
switch mode {
@@ -344,6 +382,12 @@ type CommonDiscoveryOptions struct {
Client if it is given.
*/
OPTOSURI string `json:"op_tos_uri,omitempty"`
+
+ /*
+ A JWT containing metadata values about the authorization server as claims. This is a string value consisting of
+ the entire signed JWT. A "signed_metadata" metadata value SHOULD NOT appear as a claim in the JWT.
+ */
+ SignedMetadata string `json:"signed_metadata,omitempty"`
}
// OAuth2DiscoveryOptions represents the discovery options specific to OAuth 2.0.
@@ -427,6 +471,98 @@ type OAuth2DiscoveryOptions struct {
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
}
+type OAuth2JWTIntrospectionResponseDiscoveryOptions struct {
+ /*
+ OPTIONAL. JSON array containing a list of the JWS [RFC7515] signing algorithms ("alg" values) as defined in JWA
+ [RFC7518] supported by the introspection endpoint to sign the response.
+ */
+ IntrospectionSigningAlgValuesSupported []string `json:"introspection_signing_alg_values_supported,omitempty"`
+
+ /*
+ OPTIONAL. JSON array containing a list of the JWE [RFC7516] encryption algorithms ("alg" values) as defined in
+ JWA [RFC7518] supported by the introspection endpoint to encrypt the content encryption key for introspection
+ responses (content key encryption).
+ */
+ IntrospectionEncryptionAlgValuesSupported []string `json:"introspection_encryption_alg_values_supported"`
+
+ /*
+ OPTIONAL. JSON array containing a list of the JWE [RFC7516] encryption algorithms ("enc" values) as defined in
+ JWA [RFC7518] supported by the introspection endpoint to encrypt the response (content encryption).
+ */
+ IntrospectionEncryptionEncValuesSupported []string `json:"introspection_encryption_enc_values_supported"`
+}
+
+type OAuth2DeviceAuthorizationGrantDiscoveryOptions struct {
+ /*
+ OPTIONAL. URL of the authorization server's device authorization endpoint, as defined in Section 3.1.
+ */
+ DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
+}
+
+type OAuth2MutualTLSClientAuthenticationDiscoveryOptions struct {
+ /*
+ OPTIONAL. Boolean value indicating server support for mutual-TLS client certificate-bound access tokens. If
+ omitted, the default value is false.
+ */
+ TLSClientCertificateBoundAccessTokens bool `json:"tls_client_certificate_bound_access_tokens"`
+
+ /*
+ OPTIONAL. A JSON object containing alternative authorization server endpoints that, when present, an OAuth
+ client intending to do mutual TLS uses in preference to the conventional endpoints. The parameter value itself
+ consists of one or more endpoint parameters, such as token_endpoint, revocation_endpoint,
+ introspection_endpoint, etc., conventionally defined for the top level of authorization server metadata. An
+ OAuth client intending to do mutual TLS (for OAuth client authentication and/or to acquire or use
+ certificate-bound tokens) when making a request directly to the authorization server MUST use the alias URL of
+ the endpoint within the mtls_endpoint_aliases, when present, in preference to the endpoint URL of the same name
+ at the top level of metadata. When an endpoint is not present in mtls_endpoint_aliases, then the client uses the
+ conventional endpoint URL defined at the top level of the authorization server metadata. Metadata parameters
+ within mtls_endpoint_aliases that do not define endpoints to which an OAuth client makes a direct request have
+ no meaning and SHOULD be ignored.
+ */
+ MutualTLSEndpointAliases struct {
+ AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"`
+ TokenEndpoint string `json:"token_endpoint,omitempty"`
+ IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"`
+ RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
+ EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
+ UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
+ BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint,omitempty"`
+ FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"`
+ PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"`
+ RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
+ } `json:"mtls_endpoint_aliases"`
+}
+
+type OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions struct {
+ /*
+ Indicates where authorization request needs to be protected as Request Object and provided through either
+ request or request_uri parameter.
+ */
+ RequireSignedRequestObject bool `json:"require_signed_request_object"`
+}
+
+type OAuth2IssuerIdentificationDiscoveryOptions struct {
+ AuthorizationResponseIssuerParameterSupported bool `json:"authorization_response_iss_parameter_supported"`
+}
+
+// OAuth2PushedAuthorizationDiscoveryOptions represents the well known discovery document specific to the
+// OAuth 2.0 Pushed Authorization Requests (RFC9126) implementation.
+//
+// OAuth 2.0 Pushed Authorization Requests: https://datatracker.ietf.org/doc/html/rfc9126#section-5
+type OAuth2PushedAuthorizationDiscoveryOptions struct {
+ /*
+ The URL of the pushed authorization request endpoint at which a client can post an authorization request to
+ exchange for a "request_uri" value usable at the authorization server.
+ */
+ PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint"`
+
+ /*
+ Boolean parameter indicating whether the authorization server accepts authorization request data only via PAR.
+ If omitted, the default value is "false".
+ */
+ RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests"`
+}
+
// OpenIDConnectDiscoveryOptions represents the discovery options specific to OpenID Connect.
type OpenIDConnectDiscoveryOptions struct {
/*
@@ -553,6 +689,12 @@ type OpenIDConnectDiscoveryOptions struct {
ClaimLocalesSupported []string `json:"claims_locales_supported,omitempty"`
/*
+ OPTIONAL. Boolean value specifying whether the OP supports use of the request parameter, with true indicating
+ support. If omitted, the default value is false.
+ */
+ RequestParameterSupported bool `json:"request_parameter_supported"`
+
+ /*
OPTIONAL. Boolean value specifying whether the OP supports use of the request_uri parameter, with true indicating
support. If omitted, the default value is true.
*/
@@ -612,39 +754,202 @@ type OpenIDConnectBackChannelLogoutDiscoveryOptions struct {
BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported"`
}
-// PushedAuthorizationDiscoveryOptions represents the well known discovery document specific to the
-// OAuth 2.0 Pushed Authorization Requests (RFC9126) implementation.
+// OpenIDConnectSessionManagementDiscoveryOptions represents the discovery options specific to OpenID Connect 1.0
+// Session Management.
//
-// OAuth 2.0 Pushed Authorization Requests: https://datatracker.ietf.org/doc/html/rfc9126#section-5
-type PushedAuthorizationDiscoveryOptions struct {
+// To support OpenID Connect Session Management, the RP needs to obtain the Session Management related OP metadata. This
+// OP metadata is normally obtained via the OP's Discovery response, as described in OpenID Connect Discovery 1.0, or
+// MAY be learned via other mechanisms. This OpenID Provider Metadata parameter MUST be included in the Server's
+// discovery responses when Session Management and Discovery are supported.
+//
+// See Also:
+//
+// OpenID Connect 1.0 Session Management: https://openid.net/specs/openid-connect-session-1_0.html
+type OpenIDConnectSessionManagementDiscoveryOptions struct {
/*
- The URL of the pushed authorization request endpoint at which a client can post an authorization request to
- exchange for a "request_uri" value usable at the authorization server.
+ REQUIRED. URL of an OP iframe that supports cross-origin communications for session state information with the
+ RP Client, using the HTML5 postMessage API. This URL MUST use the https scheme and MAY contain port, path, and
+ query parameter components. The page is loaded from an invisible iframe embedded in an RP page so that it can
+ run in the OP's security context. It accepts postMessage requests from the relevant RP iframe and uses
+ postMessage to post back the login status of the End-User at the OP.
*/
- PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"`
+ CheckSessionIFrame string `json:"check_session_iframe"`
+}
+// OpenIDConnectRPInitiatedLogoutDiscoveryOptions represents the discovery options specific to
+// OpenID Connect RP-Initiated Logout 1.0.
+//
+// To support OpenID Connect RP-Initiated Logout, the RP needs to obtain the RP-Initiated Logout related OP metadata.
+// This OP metadata is normally obtained via the OP's Discovery response, as described in OpenID Connect Discovery 1.0,
+// or MAY be learned via other mechanisms. This OpenID Provider Metadata parameter MUST be included in the Server's
+// discovery responses when RP-Initiated Logout and Discovery are supported.
+//
+// See Also:
+//
+// OpenID Connect RP-Initiated Logout 1.0: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
+type OpenIDConnectRPInitiatedLogoutDiscoveryOptions struct {
/*
- Boolean parameter indicating whether the authorization server accepts authorization request data only via PAR.
- If omitted, the default value is "false".
+ REQUIRED. URL at the OP to which an RP can perform a redirect to request that the End-User be logged out at the
+ OP. This URL MUST use the https scheme and MAY contain port, path, and query parameter components.
*/
- RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests"`
+ EndSessionEndpoint string `json:"end_session_endpoint"`
+}
+
+// OpenIDConnectPromptCreateDiscoveryOptions represents the discovery options specific to Initiating User Registration
+// via OpenID Connect 1.0 functionality.
+//
+// This specification extends the OpenID Connect Discovery Metadata Section 3.
+//
+// See Also:
+//
+// Initiating User Registration via OpenID Connect 1.0: https://openid.net/specs/openid-connect-prompt-create-1_0.html
+type OpenIDConnectPromptCreateDiscoveryOptions struct {
+ /*
+ OPTIONAL. JSON array containing the list of prompt values that this OP supports.
+
+ This metadata element is OPTIONAL in the context of the OpenID Provider not supporting the create value. If
+ omitted, the Relying Party should assume that this specification is not supported. The OpenID Provider MAY
+ provide this metadata element even if it doesn't support the create value.
+ Specific to this specification, a value of create in the array indicates to the Relying party that this OpenID
+ Provider supports this specification. If an OpenID Provider supports this specification it MUST define this metadata
+ element in the openid-configuration file. Additionally, if this metadata element is defined by the OpenID
+ Provider, the OP must also specify all other prompt values which it supports.
+ See Also:
+ OpenID.PromptCreate: https://openid.net/specs/openid-connect-prompt-create-1_0.html
+ */
+ PromptValuesSupported []string `json:"prompt_values_supported,omitempty"`
+}
+
+// OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions represents the discovery options specific to
+// OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0
+//
+// The following authorization server metadata parameters are introduced by this specification for OPs publishing their
+// support of the CIBA flow and details thereof.
+//
+// See Also:
+//
+// OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0:
+// https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html#rfc.section.4
+type OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions struct {
+ /*
+ REQUIRED. URL of the OP's Backchannel Authentication Endpoint as defined in Section 7.
+ */
+ BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint"`
+
+ /*
+ REQUIRED. JSON array containing one or more of the following values: poll, ping, and push.
+ */
+ BackChannelTokenDeliveryModesSupported []string `json:"backchannel_token_delivery_modes_supported"`
+
+ /*
+ OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for signed
+ authentication requests, which are described in Section 7.1.1. If omitted, signed authentication requests are
+ not supported by the OP.
+ */
+ BackChannelAuthRequestSigningAlgValuesSupported []string `json:"backchannel_authentication_request_signing_alg_values_supported,omitempty"`
+
+ /*
+ OPTIONAL. Boolean value specifying whether the OP supports the use of the user_code parameter, with true
+ indicating support. If omitted, the default value is false.
+ */
+ BackChannelUserCodeParameterSupported bool `json:"backchannel_user_code_parameter_supported"`
+}
+
+// OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions represents the discovery options specific to
+// JWT Secured Authorization Response Mode for OAuth 2.0 (JARM).
+//
+// Authorization servers SHOULD publish the supported algorithms for signing and encrypting the JWT of an authorization
+// response by utilizing OAuth 2.0 Authorization Server Metadata [RFC8414] parameters. The following parameters are
+// introduced by this specification.
+//
+// See Also:
+//
+// JWT Secured Authorization Response Mode for OAuth 2.0 (JARM):
+// https://openid.net/specs/oauth-v2-jarm.html#name-authorization-server-metada
+type OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions struct {
+ /*
+ OPTIONAL. A JSON array containing a list of the JWS [RFC7515] signing algorithms (alg values) supported by the
+ authorization endpoint to sign the response.
+ */
+ AuthorizationSigningAlgValuesSupported []string `json:"authorization_signing_alg_values_supported,omitempty"`
+
+ /*
+ OPTIONAL. A JSON array containing a list of the JWE [RFC7516] encryption algorithms (alg values) supported by
+ the authorization endpoint to encrypt the response.
+ */
+ AuthorizationEncryptionAlgValuesSupported []string `json:"authorization_encryption_alg_values_supported,omitempty"`
+
+ /*
+ OPTIONAL. A JSON array containing a list of the JWE [RFC7516] encryption algorithms (enc values) supported by
+ the authorization endpoint to encrypt the response.
+ */
+ AuthorizationEncryptionEncValuesSupported []string `json:"authorization_encryption_enc_values_supported,omitempty"`
+}
+
+type OpenIDFederationDiscoveryOptions struct {
+ /*
+ OPTIONAL. URL of the OP's federation-specific Dynamic Client Registration Endpoint. If the OP supports explicit
+ client registration as described in Section 10.2, then this claim is REQUIRED.
+ */
+ FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"`
+
+ /*
+ REQUIRED. Array specifying the federation types supported. Federation-type values defined by this specification
+ are automatic and explicit.
+ */
+ ClientRegistrationTypesSupported []string `json:"client_registration_types_supported"`
+
+ /*
+ OPTIONAL. A JSON Object defining the client authentications supported for each endpoint. The endpoint names are
+ defined in the IANA "OAuth Authorization Server Metadata" registry [IANA.OAuth.Parameters]. Other endpoints and
+ authentication methods are possible if made recognizable according to established standards and not in conflict
+ with the operating principles of this specification. In OpenID Connect Core, no client authentication is
+ performed at the authentication endpoint. Instead, the request itself is authenticated. The OP maps information
+ in the request (like the redirect_uri) to information it has gained on the client through static or dynamic
+ registration. If the mapping is successful, the request can be processed. If the RP uses Automatic Registration,
+ as defined in Section 10.1, the OP has no prior knowledge of the RP. Therefore, the OP must start by gathering
+ information about the RP using the process outlined in Section 6. Once it has the RP's metadata, the OP can
+ verify the request in the same way as if it had known the RP's metadata beforehand. To make the request
+ verification more secure, we demand the use of a client authentication or verification method that proves that
+ the RP is in possession of a key that appears in the RP's metadata.
+ */
+ RequestAuthenticationMethodsSupported []string `json:"request_authentication_methods_supported,omitempty"`
+
+ /*
+ OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values) supported for the signature on
+ the JWT [RFC7519] used in the request_object contained in the request parameter of an authorization request or
+ in the private_key_jwt of a pushed authorization request. This entry MUST be present if either of these
+ authentication methods are specified in the request_authentication_methods_supported entry. No default
+ algorithms are implied if this entry is omitted. Servers SHOULD support RS256. The value none MUST NOT be used.
+ */
+ RequestAuthenticationSigningAlgValuesSupproted []string `json:"request_authentication_signing_alg_values_supported,omitempty"`
}
// OAuth2WellKnownConfiguration represents the well known discovery document specific to OAuth 2.0.
type OAuth2WellKnownConfiguration struct {
CommonDiscoveryOptions
OAuth2DiscoveryOptions
- PushedAuthorizationDiscoveryOptions
+ *OAuth2DeviceAuthorizationGrantDiscoveryOptions
+ *OAuth2MutualTLSClientAuthenticationDiscoveryOptions
+ *OAuth2IssuerIdentificationDiscoveryOptions
+ *OAuth2JWTIntrospectionResponseDiscoveryOptions
+ *OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions
+ *OAuth2PushedAuthorizationDiscoveryOptions
}
// OpenIDConnectWellKnownConfiguration represents the well known discovery document specific to OpenID Connect.
type OpenIDConnectWellKnownConfiguration struct {
- CommonDiscoveryOptions
- OAuth2DiscoveryOptions
- PushedAuthorizationDiscoveryOptions
+ OAuth2WellKnownConfiguration
+
OpenIDConnectDiscoveryOptions
- OpenIDConnectFrontChannelLogoutDiscoveryOptions
- OpenIDConnectBackChannelLogoutDiscoveryOptions
+ *OpenIDConnectFrontChannelLogoutDiscoveryOptions
+ *OpenIDConnectBackChannelLogoutDiscoveryOptions
+ *OpenIDConnectSessionManagementDiscoveryOptions
+ *OpenIDConnectRPInitiatedLogoutDiscoveryOptions
+ *OpenIDConnectPromptCreateDiscoveryOptions
+ *OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions
+ *OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions
+ *OpenIDFederationDiscoveryOptions
}
// OpenIDConnectContext represents the context implementation that is used by some OpenID Connect 1.0 implementations.
diff --git a/internal/oidc/types_test.go b/internal/oidc/types_test.go
index b84461a07..d604ad009 100644
--- a/internal/oidc/types_test.go
+++ b/internal/oidc/types_test.go
@@ -40,7 +40,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
Request: fosite.Request{
ID: requestID.String(),
Form: formValues,
- Client: &Client{ID: "example"},
+ Client: &BaseClient{ID: "example"},
},
}
@@ -50,7 +50,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
requested := time.Unix(1647332518, 0)
authAt := time.Unix(1647332500, 0)
- issuer := "https://example.com"
+ issuer := examplecom
amr := []string{AMRPasswordBasedAuthentication}
consent := &model.OAuth2ConsentSession{