mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-09-02 01:01:33 +02:00
fix: ignore trailing slash in resources
This commit is contained in:
@@ -88,6 +88,9 @@ func (s *Service) Get(ctx context.Context, tx *gorm.DB, id string) (api API, err
|
||||
}
|
||||
|
||||
func (s *Service) Create(ctx context.Context, input apiCreateDto) (api API, err error) {
|
||||
// Store one canonical resource identifier so trailing-slash variants cannot create separate audiences
|
||||
input.Resource = strings.TrimRight(input.Resource, "/")
|
||||
|
||||
// Reject the issuer as an audience so a custom API cannot impersonate Pocket ID's own identity tokens
|
||||
if isIssuerAudience(input.Resource, s.issuer) {
|
||||
return API{}, apperror.InvalidField("resource", "reserved", "is reserved by Pocket ID and cannot be used for a custom API")
|
||||
@@ -443,6 +446,9 @@ func (s *Service) DescribePermissions(ctx context.Context, audience string, keys
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Match scope information against the same canonical audience used during resource resolution
|
||||
audience = strings.TrimRight(audience, "/")
|
||||
|
||||
var permissions []Permission
|
||||
err := s.db.WithContext(ctx).
|
||||
Model(&Permission{}).
|
||||
|
||||
@@ -250,6 +250,19 @@ func TestCreateAcceptsAbsoluteResourceURIs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTrimsResourceTrailingSlashes(t *testing.T) {
|
||||
db := testutils.NewDatabaseForTest(t)
|
||||
svc := New(Dependencies{DB: db}).service
|
||||
|
||||
created, err := svc.Create(t.Context(), apiCreateDto{Name: "Orders", Resource: "https://api.orders.example.com///"})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://api.orders.example.com", created.Audience)
|
||||
|
||||
// A trailing-slash variant must conflict with the canonical resource instead of creating a second audience
|
||||
_, err = svc.Create(t.Context(), apiCreateDto{Name: "Duplicate", Resource: "https://api.orders.example.com/"})
|
||||
require.True(t, apperror.IsCode(err, apperror.CodeAlreadyInUse))
|
||||
}
|
||||
|
||||
func TestDescribePermissions(t *testing.T) {
|
||||
db := testutils.NewDatabaseForTest(t)
|
||||
svc := New(Dependencies{DB: db}).service
|
||||
@@ -263,7 +276,7 @@ func TestDescribePermissions(t *testing.T) {
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
|
||||
infos, err := svc.DescribePermissions(t.Context(), "https://api.orders.example.com", []string{"read:orders", "unknown"})
|
||||
infos, err := svc.DescribePermissions(t.Context(), "https://api.orders.example.com/", []string{"read:orders", "unknown"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, infos, 1)
|
||||
assert.Equal(t, "read:orders", infos[0].Key)
|
||||
|
||||
@@ -3,6 +3,7 @@ package oidc
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/ory/fosite"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||
@@ -53,6 +54,8 @@ func resolveResource(ctx context.Context, tx *gorm.DB, provider APIAccessProvide
|
||||
if !fosite.IsValidResourceIndicatorURI(resource) || provider == nil {
|
||||
return "", nil, fosite.ErrInvalidTarget.WithHintf("The requested resource '%s' is invalid, missing, unknown, or malformed.", resource)
|
||||
}
|
||||
// Resolve every trailing-slash variant against the same canonical resource and stamp that value into the token audience
|
||||
resource = strings.TrimRight(resource, "/")
|
||||
|
||||
allowedScopes, apiExists, err := provider.AllowedScopesForAudience(ctx, tx, clientID, resource, subjectType)
|
||||
if err != nil {
|
||||
|
||||
@@ -95,6 +95,17 @@ func TestResolveResourceCustomAPIGrantsValidScopes(t *testing.T) {
|
||||
assert.ElementsMatch(t, []string{"openid", "read:orders"}, granted)
|
||||
}
|
||||
|
||||
func TestResolveResourceTrimsTrailingSlashes(t *testing.T) {
|
||||
provider := userAccess(map[string][]string{
|
||||
"https://api.orders.example.com": {"read:orders"},
|
||||
})
|
||||
|
||||
audience, granted, err := resolveResource(t.Context(), nil, provider, "client-1", "https://api.orders.example.com///", []string{"read:orders"}, SubjectTypeUser)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://api.orders.example.com", audience)
|
||||
assert.Equal(t, []string{"read:orders"}, granted)
|
||||
}
|
||||
|
||||
func TestResolveResourceRejectsScopeFromAnotherAPI(t *testing.T) {
|
||||
provider := userAccess(map[string][]string{
|
||||
"https://api.orders.example.com": {"read:orders", "write:orders"},
|
||||
|
||||
Reference in New Issue
Block a user