diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index 117172a440..5770a2b67a 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -119,7 +119,8 @@ func NewServer( } server := &Server{ - LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)), + LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)), + signingKeyAlgorithm: config.SigningKeyAlgorithm, } metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount} server.Handler = op.RegisterLegacyServer(server, op.WithHTTPMiddleware( diff --git a/internal/api/oidc/server.go b/internal/api/oidc/server.go index f9a38a2613..6e250ebeaf 100644 --- a/internal/api/oidc/server.go +++ b/internal/api/oidc/server.go @@ -6,12 +6,14 @@ import ( "github.com/zitadel/oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/op" + "github.com/zitadel/zitadel/internal/telemetry/tracing" ) type Server struct { http.Handler *op.LegacyServer + signingKeyAlgorithm string } func endpoints(endpointConfig *EndpointConfig) op.Endpoints { @@ -79,7 +81,7 @@ func (s *Server) Discovery(ctx context.Context, r *op.Request[struct{}]) (_ *op. ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() - return s.LegacyServer.Discovery(ctx, r) + return op.NewResponse(s.createDiscoveryConfig(ctx)), nil } func (s *Server) Keys(ctx context.Context, r *op.Request[struct{}]) (_ *op.Response, err error) { @@ -186,3 +188,34 @@ func (s *Server) EndSession(ctx context.Context, r *op.Request[oidc.EndSessionRe return s.LegacyServer.EndSession(ctx, r) } + +func (s *Server) createDiscoveryConfig(ctx context.Context) *oidc.DiscoveryConfiguration { + issuer := op.IssuerFromContext(ctx) + return &oidc.DiscoveryConfiguration{ + Issuer: issuer, + AuthorizationEndpoint: s.Endpoints().Authorization.Absolute(issuer), + TokenEndpoint: s.Endpoints().Token.Absolute(issuer), + IntrospectionEndpoint: s.Endpoints().Introspection.Absolute(issuer), + UserinfoEndpoint: s.Endpoints().Userinfo.Absolute(issuer), + RevocationEndpoint: s.Endpoints().Revocation.Absolute(issuer), + EndSessionEndpoint: s.Endpoints().EndSession.Absolute(issuer), + JwksURI: s.Endpoints().JwksURI.Absolute(issuer), + DeviceAuthorizationEndpoint: s.Endpoints().DeviceAuthorization.Absolute(issuer), + ScopesSupported: op.Scopes(s.Provider()), + ResponseTypesSupported: op.ResponseTypes(s.Provider()), + GrantTypesSupported: op.GrantTypes(s.Provider()), + SubjectTypesSupported: op.SubjectTypes(s.Provider()), + IDTokenSigningAlgValuesSupported: []string{s.signingKeyAlgorithm}, + RequestObjectSigningAlgValuesSupported: op.RequestObjectSigAlgorithms(s.Provider()), + TokenEndpointAuthMethodsSupported: op.AuthMethodsTokenEndpoint(s.Provider()), + TokenEndpointAuthSigningAlgValuesSupported: op.TokenSigAlgorithms(s.Provider()), + IntrospectionEndpointAuthSigningAlgValuesSupported: op.IntrospectionSigAlgorithms(s.Provider()), + IntrospectionEndpointAuthMethodsSupported: op.AuthMethodsIntrospectionEndpoint(s.Provider()), + RevocationEndpointAuthSigningAlgValuesSupported: op.RevocationSigAlgorithms(s.Provider()), + RevocationEndpointAuthMethodsSupported: op.AuthMethodsRevocationEndpoint(s.Provider()), + ClaimsSupported: op.SupportedClaims(s.Provider()), + CodeChallengeMethodsSupported: op.CodeChallengeMethods(s.Provider()), + UILocalesSupported: s.Provider().SupportedUILocales(), + RequestParameterSupported: s.Provider().RequestObjectSupported(), + } +} diff --git a/internal/api/oidc/server_test.go b/internal/api/oidc/server_test.go new file mode 100644 index 0000000000..d7f258d0d2 --- /dev/null +++ b/internal/api/oidc/server_test.go @@ -0,0 +1,119 @@ +package oidc + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/zitadel/oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v3/pkg/op" + "golang.org/x/text/language" +) + +func TestServer_createDiscoveryConfig(t *testing.T) { + type fields struct { + LegacyServer *op.LegacyServer + signingKeyAlgorithm string + } + type args struct { + ctx context.Context + } + tests := []struct { + name string + fields fields + args args + want *oidc.DiscoveryConfiguration + }{ + { + "config", + fields{ + LegacyServer: op.NewLegacyServer( + func() *op.Provider { + provider, _ := op.NewForwardedOpenIDProvider("path", + &op.Config{ + CodeMethodS256: true, + AuthMethodPost: true, + AuthMethodPrivateKeyJWT: true, + GrantTypeRefreshToken: true, + RequestObjectSupported: true, + SupportedUILocales: []language.Tag{language.English, language.German}, + }, + nil, + ) + return provider + }(), + op.Endpoints{ + Authorization: op.NewEndpoint("auth"), + Token: op.NewEndpoint("token"), + Introspection: op.NewEndpoint("introspect"), + Userinfo: op.NewEndpoint("userinfo"), + Revocation: op.NewEndpoint("revoke"), + EndSession: op.NewEndpoint("logout"), + JwksURI: op.NewEndpoint("keys"), + DeviceAuthorization: op.NewEndpoint("device"), + }, + ), + signingKeyAlgorithm: "RS256", + }, + args{ + ctx: op.ContextWithIssuer(context.Background(), "https://issuer.com"), + }, + &oidc.DiscoveryConfiguration{ + Issuer: "https://issuer.com", + AuthorizationEndpoint: "https://issuer.com/auth", + TokenEndpoint: "https://issuer.com/token", + IntrospectionEndpoint: "https://issuer.com/introspect", + UserinfoEndpoint: "https://issuer.com/userinfo", + RevocationEndpoint: "https://issuer.com/revoke", + EndSessionEndpoint: "https://issuer.com/logout", + DeviceAuthorizationEndpoint: "https://issuer.com/device", + CheckSessionIframe: "", + JwksURI: "https://issuer.com/keys", + RegistrationEndpoint: "", + ScopesSupported: []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopePhone, oidc.ScopeAddress, oidc.ScopeOfflineAccess}, + ResponseTypesSupported: []string{string(oidc.ResponseTypeCode), string(oidc.ResponseTypeIDTokenOnly), string(oidc.ResponseTypeIDToken)}, + ResponseModesSupported: nil, + GrantTypesSupported: []oidc.GrantType{oidc.GrantTypeCode, oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeBearer}, + ACRValuesSupported: nil, + SubjectTypesSupported: []string{"public"}, + IDTokenSigningAlgValuesSupported: []string{"RS256"}, + IDTokenEncryptionAlgValuesSupported: nil, + IDTokenEncryptionEncValuesSupported: nil, + UserinfoSigningAlgValuesSupported: nil, + UserinfoEncryptionAlgValuesSupported: nil, + UserinfoEncryptionEncValuesSupported: nil, + RequestObjectSigningAlgValuesSupported: []string{"RS256"}, + RequestObjectEncryptionAlgValuesSupported: nil, + RequestObjectEncryptionEncValuesSupported: nil, + TokenEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost, oidc.AuthMethodPrivateKeyJWT}, + TokenEndpointAuthSigningAlgValuesSupported: []string{"RS256"}, + RevocationEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost, oidc.AuthMethodPrivateKeyJWT}, + RevocationEndpointAuthSigningAlgValuesSupported: []string{"RS256"}, + IntrospectionEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodBasic, oidc.AuthMethodPrivateKeyJWT}, + IntrospectionEndpointAuthSigningAlgValuesSupported: []string{"RS256"}, + DisplayValuesSupported: nil, + ClaimTypesSupported: nil, + ClaimsSupported: []string{"sub", "aud", "exp", "iat", "iss", "auth_time", "nonce", "acr", "amr", "c_hash", "at_hash", "act", "scopes", "client_id", "azp", "preferred_username", "name", "family_name", "given_name", "locale", "email", "email_verified", "phone_number", "phone_number_verified"}, + ClaimsParameterSupported: false, + CodeChallengeMethodsSupported: []oidc.CodeChallengeMethod{"S256"}, + ServiceDocumentation: "", + ClaimsLocalesSupported: nil, + UILocalesSupported: []language.Tag{language.English, language.German}, + RequestParameterSupported: true, + RequestURIParameterSupported: false, + RequireRequestURIRegistration: false, + OPPolicyURI: "", + OPTermsOfServiceURI: "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Server{ + LegacyServer: tt.fields.LegacyServer, + signingKeyAlgorithm: tt.fields.signingKeyAlgorithm, + } + assert.Equalf(t, tt.want, s.createDiscoveryConfig(tt.args.ctx), "createDiscoveryConfig(%v)", tt.args.ctx) + }) + } +}