diff --git a/example/server/exampleop/op.go b/example/server/exampleop/op.go index 4794d8a..50fe48a 100644 --- a/example/server/exampleop/op.go +++ b/example/server/exampleop/op.go @@ -11,6 +11,7 @@ import ( "golang.org/x/text/language" "github.com/zitadel/oidc/example/server/storage" + "github.com/zitadel/oidc/pkg/oidc" "github.com/zitadel/oidc/pkg/op" ) @@ -80,31 +81,33 @@ func SetupServer(ctx context.Context, issuer string, storage Storage) *mux.Route // and a predefined default logout uri // it will enable all options (see descriptions) func newOP(ctx context.Context, storage op.Storage, issuer string, key [32]byte) (op.OpenIDProvider, error) { - config := &op.Config{ - Issuer: issuer, - CryptoKey: key, + config := op.NewConfig() + config.Issuer = issuer + config.CryptoKey = key - // will be used if the end_session endpoint is called without a post_logout_redirect_uri - DefaultLogoutRedirectURI: pathLoggedOut, + config.SupportedScopes = []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess} - // enables code_challenge_method S256 for PKCE (and therefore PKCE in general) - CodeMethodS256: true, + // will be used if the end_session endpoint is called without a post_logout_redirect_uri + config.DefaultLogoutRedirectURI = pathLoggedOut - // enables additional client_id/client_secret authentication by form post (not only HTTP Basic Auth) - AuthMethodPost: true, + // enables code_challenge_method S256 for PKCE (and therefore PKCE in general) + config.CodeMethodS256 = true - // enables additional authentication by using private_key_jwt - AuthMethodPrivateKeyJWT: true, + // enables additional client_id/client_secret authentication by form post (not only HTTP Basic Auth) + config.AuthMethodPost = true - // enables refresh_token grant use - GrantTypeRefreshToken: true, + // enables additional authentication by using private_key_jwt + config.AuthMethodPrivateKeyJWT = true - // enables use of the `request` Object parameter - RequestObjectSupported: true, + // enables refresh_token grant use + config.GrantTypeRefreshToken = true + + // enables use of the `request` Object parameter + config.RequestObjectSupported = true + + // this example has only static texts (in English), so we'll set the here accordingly + config.SupportedUILocales = []language.Tag{language.English} - // this example has only static texts (in English), so we'll set the here accordingly - SupportedUILocales: []language.Tag{language.English}, - } handler, err := op.NewOpenIDProvider(ctx, config, storage, // as an example on how to customize an endpoint this will change the authorization_endpoint from /authorize to /auth op.WithCustomAuthEndpoint(op.NewEndpoint("auth")), diff --git a/pkg/op/config.go b/pkg/op/config.go index 8882964..3045022 100644 --- a/pkg/op/config.go +++ b/pkg/op/config.go @@ -36,6 +36,8 @@ type Configuration interface { RequestObjectSigningAlgorithmsSupported() []string SupportedUILocales() []language.Tag + + SupportedScopes() []string } func ValidateIssuer(issuer string) error { diff --git a/pkg/op/discovery.go b/pkg/op/discovery.go index 100bfc8..cb77cb8 100644 --- a/pkg/op/discovery.go +++ b/pkg/op/discovery.go @@ -27,7 +27,7 @@ func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfigurati RevocationEndpoint: c.RevocationEndpoint().Absolute(c.Issuer()), EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()), JwksURI: c.KeysEndpoint().Absolute(c.Issuer()), - ScopesSupported: Scopes(c), + ScopesSupported: c.SupportedScopes(), ResponseTypesSupported: ResponseTypes(c), GrantTypesSupported: GrantTypes(c), SubjectTypesSupported: SubjectTypes(c), diff --git a/pkg/op/mock/configuration.mock.go b/pkg/op/mock/configuration.mock.go index e0c90dc..bdc4254 100644 --- a/pkg/op/mock/configuration.mock.go +++ b/pkg/op/mock/configuration.mock.go @@ -301,6 +301,20 @@ func (mr *MockConfigurationMockRecorder) RevocationEndpointSigningAlgorithmsSupp return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevocationEndpointSigningAlgorithmsSupported", reflect.TypeOf((*MockConfiguration)(nil).RevocationEndpointSigningAlgorithmsSupported)) } +// SupportedScopes mocks base method. +func (m *MockConfiguration) SupportedScopes() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SupportedScopes") + ret0, _ := ret[0].([]string) + return ret0 +} + +// SupportedScopes indicates an expected call of SupportedScopes. +func (mr *MockConfigurationMockRecorder) SupportedScopes() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportedScopes", reflect.TypeOf((*MockConfiguration)(nil).SupportedScopes)) +} + // SupportedUILocales mocks base method. func (m *MockConfiguration) SupportedUILocales() []language.Tag { m.ctrl.T.Helper() diff --git a/pkg/op/op.go b/pkg/op/op.go index db35a87..591f4c1 100644 --- a/pkg/op/op.go +++ b/pkg/op/op.go @@ -14,6 +14,7 @@ import ( httphelper "github.com/zitadel/oidc/pkg/http" "github.com/zitadel/oidc/pkg/oidc" + str "github.com/zitadel/oidc/pkg/strings" ) const ( @@ -92,7 +93,7 @@ func authCallbackPath(o OpenIDProvider) string { return o.AuthorizationEndpoint().Relative() + authCallbackPathSuffix } -type Config struct { +type config struct { Issuer string CryptoKey [32]byte DefaultLogoutRedirectURI string @@ -102,6 +103,7 @@ type Config struct { GrantTypeRefreshToken bool RequestObjectSupported bool SupportedUILocales []language.Tag + SupportedScopes []string } type endpoints struct { @@ -115,6 +117,14 @@ type endpoints struct { JwksURI Endpoint } +func NewConfig() *config { + // config defaults + config := &config{ + SupportedScopes: DefaultSupportedScopes, + } + return config +} + // NewOpenIDProvider creates a provider. The provider provides (with HttpHandler()) // a http.Router that handles a suite of endpoints (some paths can be overridden): // /healthz @@ -133,7 +143,7 @@ type endpoints struct { // Successful logins should mark the request as authorized and redirect back to to // op.AuthCallbackURL(provider) which is probably /callback. On the redirect back // to the AuthCallbackURL, the request id should be passed as the "id" parameter. -func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opOpts ...Option) (OpenIDProvider, error) { +func NewOpenIDProvider(ctx context.Context, config *config, storage Storage, opOpts ...Option) (OpenIDProvider, error) { err := ValidateIssuer(config.Issuer) if err != nil { return nil, err @@ -175,7 +185,7 @@ func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opO } type openidProvider struct { - config *Config + config *config endpoints *endpoints storage Storage signer Signer @@ -192,6 +202,23 @@ type openidProvider struct { accessTokenVerifierOpts []AccessTokenVerifierOpt } +func RestrictToSupportedScopes(provider *openidProvider, scopes []string) []string { + newScopeList := make([]string, 0, len(scopes)) + // filter out unsupported scopes + for _, scope := range scopes { + if str.Contains(provider.config.SupportedScopes, scope) { + newScopeList = append(newScopeList, scope) + } + } + + return newScopeList +} + +func (o *openidProvider) ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier IDTokenHintVerifier) (string, error) { + authReq.Scopes = RestrictToSupportedScopes(o, authReq.Scopes) + return ValidateAuthRequest(ctx, authReq, storage, verifier) +} + func (o *openidProvider) Issuer() string { return o.config.Issuer } @@ -285,6 +312,14 @@ func (o *openidProvider) SupportedUILocales() []language.Tag { return o.config.SupportedUILocales } +func (o *openidProvider) SupportedScopes() []string { + return o.config.SupportedScopes +} + +func (o *openidProvider) SetScopesSupported(scopes []string) { + o.config.SupportedScopes = scopes +} + func (o *openidProvider) Storage() Storage { return o.storage }