From 61db5fc487b1fc1c834408fd5b4b06c0ea2cf123 Mon Sep 17 00:00:00 2001 From: Allison Larson Date: Thu, 27 Nov 2025 14:00:32 -0800 Subject: [PATCH 1/4] Add provider.Claims for accessing metadata from service discovery --- oidc/provider.go | 8 ++++++++ oidc/provider_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/oidc/provider.go b/oidc/provider.go index 7116a46..7436b16 100644 --- a/oidc/provider.go +++ b/oidc/provider.go @@ -116,6 +116,14 @@ func NewProvider(c *Config) (*Provider, error) { return p, nil } +// Claims unmarshals raw fields returned by the server during discovery. +func (p *Provider) Claims(v any) error { + if p == nil { + return fmt.Errorf("provider is nil") + } + return p.provider.Claims(v) +} + // Done with the provider's background resources and must be called for every // Provider created func (p *Provider) Done() { diff --git a/oidc/provider_test.go b/oidc/provider_test.go index 546f2a2..f597cae 100644 --- a/oidc/provider_test.go +++ b/oidc/provider_test.go @@ -1756,6 +1756,33 @@ func TestProvider_DiscoveryInfo(t *testing.T) { } } +func TestProvider_Claims(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + tp := StartTestProvider(t) + config := testNewConfig( + t, + "test-client-id", + "test-client-secret", + "https://test-redirect", + tp, + ) + provider, err := NewProvider(config) + require.NoError(err) + + providerMetadata := struct { + ScopesSupported []string `json:"scopes_supported"` + SubjectTypesSupported []string `json:"subject_types_supported"` + ResponseTypesSupported []string `json:"response_types_supported"` + }{} + + err = provider.Claims(&providerMetadata) + require.NoError(err) + assert.ElementsMatch(providerMetadata.ScopesSupported, []string{"openid"}) + assert.ElementsMatch(providerMetadata.SubjectTypesSupported, []string{"public"}) + assert.ElementsMatch(providerMetadata.ResponseTypesSupported, []string{"code", "id_token", "token id_token"}) +} + var _ JWTSerializer = &mockSerializer{} type mockSerializer struct { From 0d91e4bfc711fd50aab857a158fd872b58297b64 Mon Sep 17 00:00:00 2001 From: Allison Larson Date: Thu, 27 Nov 2025 14:43:22 -0800 Subject: [PATCH 2/4] Allow test openid config to configurable --- oidc/provider_test.go | 5 +++++ oidc/testing_provider.go | 45 +++++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/oidc/provider_test.go b/oidc/provider_test.go index f597cae..4806974 100644 --- a/oidc/provider_test.go +++ b/oidc/provider_test.go @@ -1767,6 +1767,9 @@ func TestProvider_Claims(t *testing.T) { "https://test-redirect", tp, ) + tp.SetAdditionalConfiguration(map[string]interface{}{ + "authorization_required": true, + }) provider, err := NewProvider(config) require.NoError(err) @@ -1774,6 +1777,7 @@ func TestProvider_Claims(t *testing.T) { ScopesSupported []string `json:"scopes_supported"` SubjectTypesSupported []string `json:"subject_types_supported"` ResponseTypesSupported []string `json:"response_types_supported"` + AuthRequired bool `json:"authorization_required"` }{} err = provider.Claims(&providerMetadata) @@ -1781,6 +1785,7 @@ func TestProvider_Claims(t *testing.T) { assert.ElementsMatch(providerMetadata.ScopesSupported, []string{"openid"}) assert.ElementsMatch(providerMetadata.SubjectTypesSupported, []string{"public"}) assert.ElementsMatch(providerMetadata.ResponseTypesSupported, []string{"code", "id_token", "token id_token"}) + assert.True(providerMetadata.AuthRequired) } var _ JWTSerializer = &mockSerializer{} diff --git a/oidc/testing_provider.go b/oidc/testing_provider.go index 3125eb8..3838792 100644 --- a/oidc/testing_provider.go +++ b/oidc/testing_provider.go @@ -176,6 +176,7 @@ type TestProvider struct { invalidJWKs bool nowFunc func() time.Time pkceVerifier CodeVerifier + additionalConfig map[string]interface{} clientAssertionJWT string @@ -929,6 +930,12 @@ func (p *TestProvider) UserInfoReply() map[string]interface{} { return p.replyUserinfo } +func (p *TestProvider) SetAdditionalConfiguration(config map[string]interface{}) { + p.mu.Lock() + defer p.mu.Unlock() + p.additionalConfig = config +} + // Addr returns the current base URL for the test provider's running webserver, // which can be used as an OIDC issuer for discovery and is also used for the // iss claim when issuing JWTs. @@ -1178,29 +1185,25 @@ func (p *TestProvider) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - reply := struct { - Issuer string `json:"issuer"` - AuthEndpoint string `json:"authorization_endpoint"` - TokenEndpoint string `json:"token_endpoint"` - JWKSURI string `json:"jwks_uri"` - UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` - SupportedAlgs []string `json:"id_token_signing_alg_values_supported"` - SupportedScopes []string `json:"scopes_supported"` - SubjectTypesSupported []string `json:"subject_types_supported"` - ResponseTypesSupported []string `json:"response_types_supported"` - }{ - Issuer: p.Addr(), - AuthEndpoint: p.Addr() + authorize, - TokenEndpoint: p.Addr() + token, - JWKSURI: p.Addr() + wellKnownJwks, - UserinfoEndpoint: p.Addr() + userInfo, - SupportedAlgs: []string{string(p.alg)}, - SupportedScopes: p.supportedScopes, - SubjectTypesSupported: []string{"public"}, - ResponseTypesSupported: []string{"code", "id_token", "token id_token"}, + reply := map[string]interface{}{ + "issuer": p.Addr(), + "authorization_endpoint": p.Addr() + authorize, + "token_endpoint": p.Addr() + token, + "jwks_uri": p.Addr() + wellKnownJwks, + "userinfo_endpoint": p.Addr() + userInfo, + "id_token_signing_alg_values_supported": []string{string(p.alg)}, + "scopes_supported": p.supportedScopes, + "subject_types_supported": []string{"public"}, + "response_types_supported": []string{"code", "id_token", "token id_token"}, } if p.disableUserInfo { - reply.UserinfoEndpoint = "" + reply["userinfo_endpoint"] = "" + } + + if p.additionalConfig != nil { + for k, v := range p.additionalConfig { + reply[k] = v + } } err := p.writeJSON(w, &reply) From f52be16befafee11b498075cae7f98edfc5f6b53 Mon Sep 17 00:00:00 2001 From: Allison Larson Date: Mon, 1 Dec 2025 14:08:49 -0800 Subject: [PATCH 3/4] Update godocs --- oidc/provider.go | 10 +++++++++- oidc/testing_provider.go | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/oidc/provider.go b/oidc/provider.go index 7436b16..30a3195 100644 --- a/oidc/provider.go +++ b/oidc/provider.go @@ -116,7 +116,15 @@ func NewProvider(c *Config) (*Provider, error) { return p, nil } -// Claims unmarshals raw fields returned by the server during discovery. +// Claims unmarshals raw fields returned by the provider during discovery. +// +// For a list of fields defined by the OpenID Connect spec see: +// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata +// see also: https://datatracker.ietf.org/doc/html/rfc8414 +// +// This list of fields may include 'authorization_response_iss_parameter_supported' +// which can be used to prevent mix-up attacks. +// https://datatracker.ietf.org/doc/html/rfc9207#section-3 func (p *Provider) Claims(v any) error { if p == nil { return fmt.Errorf("provider is nil") diff --git a/oidc/testing_provider.go b/oidc/testing_provider.go index 3838792..97056bc 100644 --- a/oidc/testing_provider.go +++ b/oidc/testing_provider.go @@ -930,6 +930,8 @@ func (p *TestProvider) UserInfoReply() map[string]interface{} { return p.replyUserinfo } +// SetAdditionalConfiguration sets additional provider metadata to be returned +// during provider discovery. func (p *TestProvider) SetAdditionalConfiguration(config map[string]interface{}) { p.mu.Lock() defer p.mu.Unlock() From b1c85588f1be2ead3d421e6f3d8396e355649417 Mon Sep 17 00:00:00 2001 From: Allison Larson Date: Wed, 3 Dec 2025 11:04:19 -0800 Subject: [PATCH 4/4] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42ab53a..38779c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Canonical reference for changes, improvements, and bugfixes for cap. ## Next +* feat (oidc): add Claims for exposing provider server metadata ([PR #172](https://github.com/hashicorp/cap/pull/172)) + ## 0.11.0 * fix (ldap): fix slice append to be concurrent safe when searching for ldap