Skip to content

feat(ci): Enhance authorization bdd test coverage#3298

Open
elizabethhealy wants to merge 57 commits intomainfrom
dspx-1311-add-more-authorization-bdd-tests
Open

feat(ci): Enhance authorization bdd test coverage#3298
elizabethhealy wants to merge 57 commits intomainfrom
dspx-1311-add-more-authorization-bdd-tests

Conversation

@elizabethhealy
Copy link
Copy Markdown
Member

@elizabethhealy elizabethhealy commented Apr 10, 2026

Proposed Changes

  • Adds optional direct entitlement support to the claims ERS
  • Enhances cukes authorization test covererage to cover: attribute rules, direct entitlements, enhanced obligations coverage, enhanced RR coverage, multi resource decisions

Checklist

  • I have added or updated unit tests
  • I have added or updated integration tests (if appropriate)
  • I have added or updated documentation

Testing Instructions

Summary by CodeRabbit

  • New Features

    • Configuration-driven enabling of direct entitlements from claims-based entities
    • Multi-resource authorization decisioning with per-resource decision evaluation
    • Attribute rules decisioning supporting anyOf, allOf, and hierarchy evaluation patterns
  • Tests

    • Comprehensive BDD coverage for direct entitlements validation
    • Multi-resource decision request scenarios and assertions
    • Registered resource decisioning and attribute rules evaluation test cases

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 203.738998ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 95.996174ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 395.768043ms
Throughput 252.67 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.332602725s
Average Latency 401.824921ms
Throughput 123.97 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 169.966131ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 80.488602ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 399.71568ms
Throughput 250.18 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 40.637885971s
Average Latency 404.232092ms
Throughput 123.04 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 212.546073ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 105.492143ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 409.053192ms
Throughput 244.47 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 42.028736643s
Average Latency 418.744828ms
Throughput 118.97 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 205.414894ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 101.940077ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 400.986946ms
Throughput 249.38 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 41.860962538s
Average Latency 417.14711ms
Throughput 119.44 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 195.515474ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 99.671175ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 398.536906ms
Throughput 250.92 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 42.426087379s
Average Latency 422.579076ms
Throughput 117.85 requests/second

Base automatically changed from dspx-2753-rr-aav-bdd-namespaced-policy-tests to main April 22, 2026 20:52
@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 175.525407ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 91.162492ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 420.433638ms
Throughput 237.85 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 42.628249579s
Average Latency 424.946181ms
Throughput 117.29 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • otdfctl
  • sdk
  • service
  • lib/fixtures
  • tests-bdd

See the workflow run for details.

@elizabethhealy elizabethhealy marked this pull request as ready for review April 24, 2026 19:21
@elizabethhealy elizabethhealy requested a review from a team as a code owner April 24, 2026 19:21
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@service/entityresolution/claims/v2/entity_resolution_test.go`:
- Around line 96-153: Add a third test (e.g.,
Test_ClaimsResolveEntityDirectEntitlementsMalformed) in
entity_resolution_test.go that builds a claims struct with malformed/partial
"direct_entitlements" (e.g., an entry missing "attribute_value_fqn" and another
with "actions" as a non-array), call claims.EntityResolution(...) with the
feature flag enabled (true), and assert the function safely ignores malformed
entries (no panic/error and resulting
entityRepresentations[0].GetDirectEntitlements() does not include invalid items
— expect empty or only valid entries) to pin down parser skip/error behavior;
reference claims.EntityResolution and the existing
Test_ClaimsResolveEntityDirectEntitlements/Test_ClaimsResolveEntityDirectEntitlementsDisabled
for structure.

In `@service/entityresolution/claims/v2/entity_resolution.go`:
- Around line 262-298: In parseDirectEntitlementActions, the case handling
[]string is effectively unreachable when inputs come from structpb
(Struct.AsMap/Value_ListValue yields []interface{}); either remove the []string
branch to avoid confusion or keep it but add a brief comment above the case
noting it is defensive for non-structpb inputs, so future readers understand why
it exists; reference the function name parseDirectEntitlementActions and the
[]string case to locate the change.
- Around line 38-49: Replace the stdlib log.Fatalf call in RegisterClaimsERS
with a panic using fmt.Sprintf so startup errors are raised consistently with
other registry-aware ERS constructors; when mapstructure.Decode returns an
error, keep the existing logger.Error line and then call
panic(fmt.Sprintf("Failed to decode claims entity resolution configuration: %v",
err)) so the service registry can catch and handle the panic (refer to
RegisterClaimsERS, mapstructure.Decode and logger.Error in this function).

In `@tests-bdd/cukes/steps_authorization.go`:
- Around line 493-545: In parseClaimsTable, avoid silently swallowing JSON parse
errors: when rawValue is non-empty and looks JSON-like (e.g., starts with '{' or
'[' or a digit) attempt json.Unmarshal and if it fails return that unmarshal
error instead of falling back to the raw string; only use parsed = rawValue as a
fallback for non-JSON-like tokens. Update the json.Unmarshal error handling in
parseClaimsTable to detect JSON-like rawValue and propagate the error for those
cases while preserving the existing string fallback for plain tokens.
- Around line 129-135: The local variable named "entity" shadows the imported
package "entity"; rename the local variable (e.g., to entityObj or
subjectEntity) where it's defined in the Entity literal so it no longer
conflicts with the package, update all subsequent uses (including
scenarioContext.RecordObject(referenceID, ...)) to the new name, and ensure
references to package-level identifiers like entity.Entity_CATEGORY_SUBJECT and
entity.Entity_Claims remain as package-qualified names.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b1a5b8cd-7ba8-49cb-b6f5-0e4baf2f576e

📥 Commits

Reviewing files that changed from the base of the PR and between 1179486 and 4adac6f.

📒 Files selected for processing (11)
  • service/entityresolution/claims/v2/entity_resolution.go
  • service/entityresolution/claims/v2/entity_resolution_test.go
  • tests-bdd/cukes/resources/platform.direct_entitlements.template
  • tests-bdd/cukes/steps_authorization.go
  • tests-bdd/cukes/steps_obligations.go
  • tests-bdd/cukes/steps_registeredresources.go
  • tests-bdd/features/attribute-rules.feature
  • tests-bdd/features/direct-entitlements.feature
  • tests-bdd/features/multi-resource.feature
  • tests-bdd/features/obligations.feature
  • tests-bdd/features/registered-resources.feature
💤 Files with no reviewable changes (1)
  • tests-bdd/cukes/steps_obligations.go

Comment on lines +96 to +153
func Test_ClaimsResolveEntityDirectEntitlements(t *testing.T) {
customclaims := map[string]interface{}{
"direct_entitlements": []interface{}{
map[string]interface{}{
"attribute_value_fqn": "https://example.com/attr/department/value/eng",
"actions": []interface{}{"read", "update"},
},
},
}
structClaims, err := structpb.NewStruct(customclaims)
require.NoError(t, err)

anyClaims, err := anypb.New(structClaims)
require.NoError(t, err)

var validBody []*entity.Entity
validBody = append(validBody, &entity.Entity{EphemeralId: "1234", EntityType: &entity.Entity_Claims{Claims: anyClaims}})

req := entityresolutionV2.ResolveEntitiesRequest{Entities: validBody}

resp, reserr := claims.EntityResolution(t.Context(), &req, logger.CreateTestLogger(), true)
require.NoError(t, reserr)

entityRepresentations := resp.GetEntityRepresentations()
require.Len(t, entityRepresentations, 1)

entitlements := entityRepresentations[0].GetDirectEntitlements()
require.Len(t, entitlements, 1)
assert.Equal(t, "https://example.com/attr/department/value/eng", entitlements[0].GetAttributeValueFqn())
assert.ElementsMatch(t, []string{"read", "update"}, entitlements[0].GetActions())
}

func Test_ClaimsResolveEntityDirectEntitlementsDisabled(t *testing.T) {
customclaims := map[string]interface{}{
"direct_entitlements": []interface{}{
map[string]interface{}{
"attribute_value_fqn": "https://example.com/attr/department/value/eng",
"actions": []interface{}{"read"},
},
},
}
structClaims, err := structpb.NewStruct(customclaims)
require.NoError(t, err)

anyClaims, err := anypb.New(structClaims)
require.NoError(t, err)

req := entityresolutionV2.ResolveEntitiesRequest{Entities: []*entity.Entity{
{EphemeralId: "1234", EntityType: &entity.Entity_Claims{Claims: anyClaims}},
}}

resp, reserr := claims.EntityResolution(t.Context(), &req, logger.CreateTestLogger(), false)
require.NoError(t, reserr)

entityRepresentations := resp.GetEntityRepresentations()
require.Len(t, entityRepresentations, 1)
assert.Empty(t, entityRepresentations[0].GetDirectEntitlements())
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

LGTM — good positive/negative coverage for the new flag.

The two new tests tightly mirror the enabled (true) and disabled (false) configuration paths introduced in EntityResolution, and assert.ElementsMatch on actions correctly avoids coupling to ordering. Paired with the assert.Empty(... GetDirectEntitlements()) on the disabled path, these protect against regressions in either direction (flag off accidentally emitting entitlements, or flag on dropping them).

Optional follow-up (nice-to-have, not blocking): consider a third case that exercises malformed/partial direct_entitlements claim content (e.g., missing attribute_value_fqn, non-array actions) so the parser's error/skip behavior is pinned down by a test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/entityresolution/claims/v2/entity_resolution_test.go` around lines 96
- 153, Add a third test (e.g.,
Test_ClaimsResolveEntityDirectEntitlementsMalformed) in
entity_resolution_test.go that builds a claims struct with malformed/partial
"direct_entitlements" (e.g., an entry missing "attribute_value_fqn" and another
with "actions" as a non-array), call claims.EntityResolution(...) with the
feature flag enabled (true), and assert the function safely ignores malformed
entries (no panic/error and resulting
entityRepresentations[0].GetDirectEntitlements() does not include invalid items
— expect empty or only valid entries) to pin down parser skip/error behavior;
reference claims.EntityResolution and the existing
Test_ClaimsResolveEntityDirectEntitlements/Test_ClaimsResolveEntityDirectEntitlementsDisabled
for structure.

Comment on lines +38 to 49
func RegisterClaimsERS(cfg config.ServiceConfig, logger *logger.Logger) (EntityResolutionServiceV2, serviceregistry.HandlerServer) {
var inputConfig Config
if err := mapstructure.Decode(cfg, &inputConfig); err != nil {
logger.Error("failed to decode claims entity resolution configuration", slog.Any("error", err))
log.Fatalf("Failed to decode claims entity resolution configuration: %v", err)
}
claimsSVC := EntityResolutionServiceV2{
logger: logger,
allowDirectEntitlements: inputConfig.AllowDirectEntitlements,
}
return claimsSVC, nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how other ERS constructors handle Config decode errors.
rg -nP --type=go -C5 'func Register(Keycloak|MultiStrategy|Claims)ERS' service/entityresolution/

# Look for existing log.Fatalf usage in service registry paths to gauge convention.
rg -nP --type=go -C2 'log\.Fatalf' service/

Repository: opentdf/platform

Length of output: 12178


Replace log.Fatalf with panic for consistent config decode error handling.

The current log.Fatalf call bypasses deferred cleanup in the server boot path and double-logs (slog Error + stdlib log.Fatalf). Registry-aware ERS constructors like RegisterMultiStrategyERSV2 use panic(fmt.Sprintf(...)) instead, which allows the service registry to catch and handle startup errors gracefully. Align this with that pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/entityresolution/claims/v2/entity_resolution.go` around lines 38 -
49, Replace the stdlib log.Fatalf call in RegisterClaimsERS with a panic using
fmt.Sprintf so startup errors are raised consistently with other registry-aware
ERS constructors; when mapstructure.Decode returns an error, keep the existing
logger.Error line and then call panic(fmt.Sprintf("Failed to decode claims
entity resolution configuration: %v", err)) so the service registry can catch
and handle the panic (refer to RegisterClaimsERS, mapstructure.Decode and
logger.Error in this function).

Comment on lines +262 to +298
func parseDirectEntitlementActions(raw interface{}) ([]string, error) {
actions := make([]string, 0)
switch typed := raw.(type) {
case []interface{}:
for _, action := range typed {
actionStr, ok := action.(string)
if !ok {
return nil, errors.New("action must be a string")
}
actionStr = strings.TrimSpace(strings.ToLower(actionStr))
if actionStr != "" {
actions = append(actions, actionStr)
}
}
case []string:
for _, action := range typed {
action = strings.TrimSpace(strings.ToLower(action))
if action != "" {
actions = append(actions, action)
}
}
case string:
for _, action := range strings.Split(typed, ",") {
action = strings.TrimSpace(strings.ToLower(action))
if action != "" {
actions = append(actions, action)
}
}
default:
return nil, errors.New("actions must be an array or string")
}

if len(actions) == 0 {
return nil, errors.New("no actions provided")
}
return actions, nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Minor: the []string branch is unreachable via structpb.Struct.AsMap().

structpb.Value_ListValue.AsInterface() produces []interface{} — never []string. The case []string (line 276-282) is defensive dead code from this caller's perspective. Safe to keep for robustness if you envision non-structpb inputs later, but worth a brief comment or removal to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/entityresolution/claims/v2/entity_resolution.go` around lines 262 -
298, In parseDirectEntitlementActions, the case handling []string is effectively
unreachable when inputs come from structpb (Struct.AsMap/Value_ListValue yields
[]interface{}); either remove the []string branch to avoid confusion or keep it
but add a brief comment above the case noting it is defensive for non-structpb
inputs, so future readers understand why it exists; reference the function name
parseDirectEntitlementActions and the []string case to locate the change.

Comment on lines +129 to +135
entity := &entity.Entity{
EphemeralId: referenceID,
Category: entity.Entity_CATEGORY_SUBJECT,
EntityType: &entity.Entity_Claims{Claims: anyClaims},
}
scenarioContext.RecordObject(referenceID, entity)
return ctx, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Avoid shadowing the entity package name with a local variable.

entity := &entity.Entity{...} compiles (the new variable's scope begins after the := statement per Go spec, so entity.Entity_CATEGORY_SUBJECT and entity.Entity_Claims on the RHS still bind to the package), but the shadowing is confusing and brittle — any future edit that moves these field references outside the struct literal, or adds another statement that references the package between this line and the end of the function, will silently re-bind to the local variable or fail to compile. The rest of the file already avoids this (see iAddClaimsToSubjectEntityWith using entityObj).

♻️ Rename the local variable to avoid shadowing
-	entity := &entity.Entity{
+	ent := &entity.Entity{
 		EphemeralId: referenceID,
 		Category:    entity.Entity_CATEGORY_SUBJECT,
 		EntityType:  &entity.Entity_Claims{Claims: anyClaims},
 	}
-	scenarioContext.RecordObject(referenceID, entity)
+	scenarioContext.RecordObject(referenceID, ent)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
entity := &entity.Entity{
EphemeralId: referenceID,
Category: entity.Entity_CATEGORY_SUBJECT,
EntityType: &entity.Entity_Claims{Claims: anyClaims},
}
scenarioContext.RecordObject(referenceID, entity)
return ctx, nil
ent := &entity.Entity{
EphemeralId: referenceID,
Category: entity.Entity_CATEGORY_SUBJECT,
EntityType: &entity.Entity_Claims{Claims: anyClaims},
}
scenarioContext.RecordObject(referenceID, ent)
return ctx, nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests-bdd/cukes/steps_authorization.go` around lines 129 - 135, The local
variable named "entity" shadows the imported package "entity"; rename the local
variable (e.g., to entityObj or subjectEntity) where it's defined in the Entity
literal so it no longer conflicts with the package, update all subsequent uses
(including scenarioContext.RecordObject(referenceID, ...)) to the new name, and
ensure references to package-level identifiers like
entity.Entity_CATEGORY_SUBJECT and entity.Entity_Claims remain as
package-qualified names.

Comment on lines +493 to +545
func parseClaimsTable(tbl *godog.Table) (map[string]interface{}, error) {
if tbl == nil || len(tbl.Rows) == 0 {
return nil, errors.New("claims table is empty")
}

cellMap := map[string]int{}
for ri, row := range tbl.Rows {
if ri == 0 {
for ci, cell := range row.Cells {
cellMap[cell.Value] = ci
}
break
}
}

nameIdx, ok := cellMap["name"]
if !ok {
return nil, errors.New("claims table requires column name")
}
valueIdx, ok := cellMap["value"]
if !ok {
return nil, errors.New("claims table requires column value")
}

out := map[string]interface{}{}
for ri, row := range tbl.Rows {
if ri == 0 {
continue
}
key := strings.TrimSpace(row.Cells[nameIdx].Value)
if key == "" {
return nil, errors.New("claims table requires name values")
}
rawValue := ""
if valueIdx < len(row.Cells) {
rawValue = strings.TrimSpace(row.Cells[valueIdx].Value)
}

var parsed interface{}
if rawValue != "" {
if err := json.Unmarshal([]byte(rawValue), &parsed); err != nil {
parsed = rawValue
}
}
out[key] = parsed
}

if len(out) == 0 {
return nil, errors.New("claims table has no rows")
}

return out, nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Nit: json.Unmarshal fallback into parsed = rawValue hides typed parse errors.

When rawValue is non-empty but not valid JSON, the value is silently stored as a raw string. That's fine for scalar inputs, but if a test author intends to pass a JSON object/array and there's a typo (trailing comma, single quotes, etc.), the string-fallback may produce a passing scenario that never actually exercises the claim shape you expected. Consider returning the unmarshal error when rawValue looks JSON-like (starts with {, [, or a digit) and only falling back to string for plain tokens.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests-bdd/cukes/steps_authorization.go` around lines 493 - 545, In
parseClaimsTable, avoid silently swallowing JSON parse errors: when rawValue is
non-empty and looks JSON-like (e.g., starts with '{' or '[' or a digit) attempt
json.Unmarshal and if it fails return that unmarshal error instead of falling
back to the raw string; only use parsed = rawValue as a fallback for
non-JSON-like tokens. Update the json.Unmarshal error handling in
parseClaimsTable to detect JSON-like rawValue and propagate the error for those
cases while preserving the existing string fallback for plain tokens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant