Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pkg/authserver/server/handlers/authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ func (h *Handler) AuthorizeHandler(w http.ResponseWriter, req *http.Request) {
return
}

slog.Debug("parsed client-requested scopes", //nolint:gosec // G706: scope count is an integer
slog.Debug("authorize request received",
"client_id", clientID,
"redirect_uri", redirectURI,
"scope_count", len(scopes),
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/authserver/server/handlers/dcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (h *Handler) RegisterClientHandler(w http.ResponseWriter, req *http.Request
// offline_access) — every DCR-registered client gains the ability to request
// these scopes at /oauth/authorize regardless of what they registered with.
if len(h.config.BaselineClientScopes) > 0 {
effective := unionScopes(scopes, h.config.BaselineClientScopes)
effective := registration.UnionScopes(scopes, h.config.BaselineClientScopes)
if !slices.Equal(effective, scopes) {
// Baseline-driven expansion is the intended behavior whenever
// baseline_client_scopes is configured, so per-registration
Expand Down
38 changes: 0 additions & 38 deletions pkg/authserver/server/handlers/scopes.go

This file was deleted.

115 changes: 0 additions & 115 deletions pkg/authserver/server/handlers/scopes_test.go

This file was deleted.

44 changes: 44 additions & 0 deletions pkg/authserver/server/registration/dcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,47 @@ func ValidateScopes(requestedScopes, allowedScopes []string) ([]string, *DCRErro

return scopes, nil
}

// UnionScopes returns the union of requested and baseline scopes, preserving
// the order of requested first, then appending any baseline scopes not already
// present. Duplicates are removed. Returns nil when the result is empty.
//
// Both inputs must already be validated by the caller. UnionScopes does not
// filter empty strings or validate scope syntax — it only deduplicates and
// merges in stable order.
func UnionScopes(requested, baseline []string) []string {
seen := make(map[string]bool, len(requested)+len(baseline))
out := make([]string, 0, len(requested)+len(baseline))
for _, s := range requested {
if !seen[s] {
seen[s] = true
out = append(out, s)
}
}
for _, s := range baseline {
if !seen[s] {
seen[s] = true
out = append(out, s)
}
}
if len(out) == 0 {
return nil
}
return out
}

// ValidatePublicGrantTypes validates the grant_types for a public OAuth client,
// applying the same rules as DCR: authorization_code must be present, and all
// declared values must be in the allowed set. Returns the validated slice (with
// defaults applied when nil/empty) or a *DCRError on violation.
func ValidatePublicGrantTypes(grantTypes []string) ([]string, *DCRError) {
return validateGrantTypes(grantTypes)
}

// ValidatePublicResponseTypes validates the response_types for a public OAuth
// client, applying the same rules as DCR: code must be present and all declared
// values must be in the allowed set. Returns the validated slice (with defaults
// applied when nil/empty) or a *DCRError on violation.
func ValidatePublicResponseTypes(responseTypes []string) ([]string, *DCRError) {
return validateResponseTypes(responseTypes)
}
28 changes: 28 additions & 0 deletions pkg/authserver/server/registration/dcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,31 @@ func TestValidateScopeSubset(t *testing.T) {
})
}
}

func TestUnionScopes(t *testing.T) {
t.Parallel()

tests := []struct {
name string
req []string
baseline []string
want []string
}{
{name: "both nil returns nil", req: nil, baseline: nil, want: nil},
{name: "both empty returns nil", req: []string{}, baseline: []string{}, want: nil},
{name: "requested only preserved unchanged", req: []string{"openid", "profile"}, baseline: nil, want: []string{"openid", "profile"}},
{name: "baseline only returned when no requested", req: nil, baseline: []string{"openid", "email"}, want: []string{"openid", "email"}},
{name: "requested subset of baseline expands correctly", req: []string{"openid"}, baseline: []string{"openid", "profile", "email"}, want: []string{"openid", "profile", "email"}},
{name: "disjoint sets: requested first then baseline", req: []string{"openid", "profile"}, baseline: []string{"email", "offline_access"}, want: []string{"openid", "profile", "email", "offline_access"}},
{name: "exact match produces no duplicates", req: []string{"openid", "profile"}, baseline: []string{"openid", "profile"}, want: []string{"openid", "profile"}},
{name: "duplicates in requested are deduplicated", req: []string{"openid", "openid", "profile"}, baseline: nil, want: []string{"openid", "profile"}},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := UnionScopes(tt.req, tt.baseline)
assert.Equal(t, tt.want, got)
})
}
}
14 changes: 13 additions & 1 deletion pkg/authserver/server_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,19 @@ func newServer(ctx context.Context, cfg Config, stor storage.Storage, opts ...se
// so that GetClient calls for HTTPS client_id values are intercepted at the
// fosite level (not just the handler level).
if cfg.CIMDEnabled {
stor, err = storage.NewCIMDStorageDecorator(stor, true, cfg.CIMDCacheMaxSize, cfg.CIMDCacheFallbackTTL)
if len(cfg.BaselineClientScopes) > 0 {
slog.Warn("CIMD is enabled with baseline_client_scopes configured; "+
Comment thread
amirejaz marked this conversation as resolved.
"any third-party client resolved via CIMD will also receive these scopes — "+
"ensure they are scopes you would grant by default to any unknown client",
"baseline_client_scopes", cfg.BaselineClientScopes)
}
stor, err = storage.NewCIMDStorageDecorator(stor, storage.CIMDDecoratorConfig{
Enabled: true,
CacheMaxSize: cfg.CIMDCacheMaxSize,
FallbackTTL: cfg.CIMDCacheFallbackTTL,
ScopesSupported: cfg.ScopesSupported,
BaselineClientScopes: cfg.BaselineClientScopes,
})
if err != nil {
return nil, fmt.Errorf("failed to initialize CIMD storage decorator: %w", err)
}
Expand Down
Loading
Loading