mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-07-15 19:53:58 +03:00
fix: restore behavior that unknown scopes get ignored
This commit is contained in:
@@ -74,14 +74,16 @@ func (b *ClientPreviewBuilder) BuildClientPreview(ctx context.Context, client mo
|
||||
}
|
||||
|
||||
func (b *ClientPreviewBuilder) validatedScopes(ctx context.Context, client model.OidcClient, scopes []string) (fosite.Arguments, error) {
|
||||
scopeStrategy := b.strategies.config.GetScopeStrategy(ctx)
|
||||
clientScopes := Client{OidcClient: client}.GetScopes()
|
||||
// Mirror the authorize endpoint's scope handling so the preview matches the tokens a real
|
||||
// authorization would produce, including dropping unknown scopes when configured.
|
||||
requested := fosite.Arguments(fosite.RemoveEmpty(scopes))
|
||||
validated, err := fosite.FilterRequestedScopes(b.strategies.config.GetScopeStrategy(ctx), Client{OidcClient: client}, requested, b.strategies.config.GetIgnoreUnknownScopes(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scopeArgs := make(fosite.Arguments, 0, len(scopes))
|
||||
for _, scope := range fosite.RemoveEmpty(scopes) {
|
||||
if !scopeStrategy(clientScopes, scope) {
|
||||
return nil, fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)
|
||||
}
|
||||
scopeArgs := make(fosite.Arguments, 0, len(validated))
|
||||
for _, scope := range validated {
|
||||
if !scopeArgs.Has(scope) {
|
||||
scopeArgs = append(scopeArgs, scope)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestClientPreviewBuilderUsesFositeTokenStrategies(t *testing.T) {
|
||||
require.Equal(t, true, preview.UserInfo["email_verified"])
|
||||
}
|
||||
|
||||
func TestClientPreviewBuilderRejectsInvalidScope(t *testing.T) {
|
||||
func TestClientPreviewBuilderIgnoresUnknownScopes(t *testing.T) {
|
||||
db := testutils.NewDatabaseForTest(t)
|
||||
signerKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
@@ -72,13 +72,20 @@ func TestClientPreviewBuilderRejectsInvalidScope(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, db.Create(&model.User{
|
||||
Base: model.Base{ID: "test-user"},
|
||||
Username: "test-user",
|
||||
}).Error)
|
||||
|
||||
// The preview mirrors the authorize endpoint: unknown scopes are dropped
|
||||
// from the previewed tokens instead of failing the preview.
|
||||
builder := newClientPreviewBuilder(newClaimsService(db, nil, "https://issuer.example.com", nil), provider.tokenStrategies)
|
||||
_, err = builder.BuildClientPreview(t.Context(), model.OidcClient{
|
||||
preview, err := builder.BuildClientPreview(t.Context(), model.OidcClient{
|
||||
Base: model.Base{ID: "test-client"},
|
||||
Name: "Test Client",
|
||||
}, "test-user", []string{"openid", "unknown"}, "")
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "invalid_scope")
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, []string{"openid"}, stringSliceClaim(t, preview.AccessToken["scp"]))
|
||||
}
|
||||
|
||||
func stringSliceClaim(t *testing.T, value any) []string {
|
||||
|
||||
@@ -46,6 +46,7 @@ func newProvider(store *Store, authenticator *federatedClientAuthenticator, sign
|
||||
AccessTokenIssuer: config.BaseURL,
|
||||
TokenURL: config.TokenBaseURL + "/api/oidc/token",
|
||||
ScopeStrategy: fosite.ExactScopeStrategy,
|
||||
IgnoreUnknownScopes: true,
|
||||
AudienceMatchingStrategy: fosite.ExactAudienceMatchingStrategy,
|
||||
RedirectURIMatcher: matchRedirectURI,
|
||||
EnforcePKCEForPublicClients: true,
|
||||
|
||||
@@ -283,3 +283,34 @@ func generateEd25519TestKey(t *testing.T) any {
|
||||
require.NoError(t, err)
|
||||
return key
|
||||
}
|
||||
|
||||
func TestProviderIgnoresUnknownScopes(t *testing.T) {
|
||||
db := testutils.NewDatabaseForTest(t)
|
||||
signerKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.Create(&model.OidcClient{
|
||||
Base: model.Base{ID: "test-client"},
|
||||
Name: "Test Client",
|
||||
CallbackURLs: model.UrlList{"https://app.example.com/callback"},
|
||||
}).Error)
|
||||
|
||||
provider, err := newProvider(NewStore(db, nil), nil, testTokenSigner{key: signerKey}, Config{ //nolint:gosec // static test-only provider secret
|
||||
BaseURL: "https://issuer.example.com",
|
||||
TokenBaseURL: "https://issuer.example.com",
|
||||
Secret: []byte("test-secret"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Clients such as MCP clients blindly request scopes Pocket ID does not support, like
|
||||
// "phone". These are dropped instead of failing the request with invalid_scope.
|
||||
req := httptest.NewRequestWithContext(
|
||||
t.Context(),
|
||||
http.MethodGet,
|
||||
"/api/oidc/authorize?client_id=test-client&response_type=code&scope=openid+email+phone&state=state-with-enough-entropy&redirect_uri=https://app.example.com/callback",
|
||||
nil,
|
||||
)
|
||||
|
||||
ar, err := provider.NewAuthorizeRequest(req.Context(), req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fosite.Arguments{"openid", "email"}, ar.GetRequestedScopes())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user