Skip to content
Open
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
44 changes: 44 additions & 0 deletions internal/api/handlers/v0/auth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,50 @@ func (h *OIDCHandler) validateExtraClaims(claims *OIDCClaims) error {
return fmt.Errorf("claim validation failed: required claim %s not found", key)
}

//Handle array values
if actualArray, ok := actualValue.([]any); ok {
// Check if expected value is also an array
if expectedArray, ok := expectedValue.([]any); ok {
// Both are arrays - check if any actual value exists in expected array
found := false
for _, actVal := range actualArray {
for _, expVal := range expectedArray {
if actVal == expVal {
found = true
break
}
}
if found {
break
}
}
if !found {
return fmt.Errorf("claim validation failed: %s no matching values found between actual %v and expected %v", key, actualArray, expectedArray)
}
continue
}

// Expected is scalar, actual is array
if len(actualArray) == 1 {
// Normalize single-element arrays to scalars
actualValue = actualArray[0]
} else if len(actualArray) > 1 {
// Check if expected value exists in the array
found := false
for _, item := range actualArray {
if item == expectedValue {
found = true
break
}
}
if !found {
return fmt.Errorf("claim validation failed: %s expected %v to be in array %v", key, expectedValue, actualArray)
}
continue
}
}

// Compare values if both are scalars
if actualValue != expectedValue {
return fmt.Errorf("claim validation failed: %s expected %v, got %v", key, expectedValue, actualValue)
}
Expand Down
180 changes: 180 additions & 0 deletions internal/api/handlers/v0/auth/oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,186 @@ func TestOIDCHandler_ExchangeToken(t *testing.T) {
token: "invalid-domain-token",
expectedError: true,
},
{
name: "successful validation with extra claim 'client_id'",
config: &config.Config{
OIDCEnabled: true,
OIDCIssuer: "https://cigna.oktapreview.com",
OIDCClientID: "api://glbcore",
OIDCExtraClaims: `[{"client_id":"matched_client_id_value"}]`,
OIDCPublishPerms: "*",
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
},
mockValidator: &MockGenericOIDCValidator{
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
return &auth.OIDCClaims{
Subject: "user-subject-123",
ExtraClaims: map[string]any{
"email": "user@cigna.com",
"client_id": "matched_client_id_value",
},
}, nil
},
},
token: "valid-okta-token",
expectedError: false,
},
{
name: "failed validation with wrong extra claim 'client_id'",
config: &config.Config{
OIDCEnabled: true,
OIDCIssuer: "https://cigna.oktapreview.com",
OIDCClientID: "api://glbcore",
OIDCExtraClaims: `[{"client_id":"client_id_value"}]`,
OIDCPublishPerms: "*",
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
},
mockValidator: &MockGenericOIDCValidator{
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
return &auth.OIDCClaims{
Subject: "user-subject-123",
ExtraClaims: map[string]any{
"email": "user@cigna.com",
"client_id": "wrong_client_id_value",
},
}, nil
},
},
token: "invalid-client-id-token",
expectedError: true,
},
{
name: "successful validation with array claim - scalar expected value in array",
config: &config.Config{
OIDCEnabled: true,
OIDCIssuer: "https://accounts.google.com",
OIDCClientID: "test-client-id",
OIDCExtraClaims: `[{"groups":"admin"}]`,
OIDCPublishPerms: "*",
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
},
mockValidator: &MockGenericOIDCValidator{
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
return &auth.OIDCClaims{
ExtraClaims: map[string]any{
"groups": []any{"admin", "users", "developers"},
},
}, nil
},
},
token: "valid-array-claim-token",
expectedError: false,
},
{
name: "failed validation with array claim - scalar not in array",
config: &config.Config{
OIDCEnabled: true,
OIDCIssuer: "https://accounts.google.com",
OIDCClientID: "test-client-id",
OIDCExtraClaims: `[{"groups":"super-admin"}]`,
OIDCPublishPerms: "*",
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
},
mockValidator: &MockGenericOIDCValidator{
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
return &auth.OIDCClaims{
ExtraClaims: map[string]any{
"groups": []any{"admin", "users", "developers"},
},
}, nil
},
},
token: "invalid-array-claim-token",
expectedError: true,
},
{
name: "successful validation with array to array comparison - overlapping values",
config: &config.Config{
OIDCEnabled: true,
OIDCIssuer: "https://accounts.google.com",
OIDCClientID: "test-client-id",
OIDCExtraClaims: `[{"roles":["admin","moderator"]}]`,
OIDCPublishPerms: "*",
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
},
mockValidator: &MockGenericOIDCValidator{
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
return &auth.OIDCClaims{
ExtraClaims: map[string]any{
"roles": []any{"admin", "users"},
},
}, nil
},
},
token: "valid-array-array-token",
expectedError: false,
},
{
name: "failed validation with array to array comparison - no overlapping values",
config: &config.Config{
OIDCEnabled: true,
OIDCIssuer: "https://accounts.google.com",
OIDCClientID: "test-client-id",
OIDCExtraClaims: `[{"roles":["super-admin","owner"]}]`,
OIDCPublishPerms: "*",
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
},
mockValidator: &MockGenericOIDCValidator{
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
return &auth.OIDCClaims{
ExtraClaims: map[string]any{
"roles": []any{"admin", "users"},
},
}, nil
},
},
token: "invalid-array-array-token",
expectedError: true,
},
{
name: "successful validation with single-element array normalization",
config: &config.Config{
OIDCEnabled: true,
OIDCIssuer: "https://accounts.google.com",
OIDCClientID: "test-client-id",
OIDCExtraClaims: `[{"department":"engineering"}]`,
OIDCPublishPerms: "*",
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
},
mockValidator: &MockGenericOIDCValidator{
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
return &auth.OIDCClaims{
ExtraClaims: map[string]any{
"department": []any{"engineering"}, // Single element array
},
}, nil
},
},
token: "valid-single-array-token",
expectedError: false,
},
{
name: "failed validation with missing claim",
config: &config.Config{
OIDCEnabled: true,
OIDCIssuer: "https://accounts.google.com",
OIDCClientID: "test-client-id",
OIDCExtraClaims: `[{"required_claim":"expected_value"}]`,
OIDCPublishPerms: "*",
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
},
mockValidator: &MockGenericOIDCValidator{
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
return &auth.OIDCClaims{
ExtraClaims: map[string]any{
"other_claim": "some_value",
},
}, nil
},
},
token: "missing-claim-token",
expectedError: true,
},
}

for _, tt := range tests {
Expand Down