mirror of
https://github.com/project-zot/zot.git
synced 2026-06-19 05:57:57 +08:00
Introduce support for OIDC workload identity federation (#3711)
* feat(oidc): introduce support for OIDC workload identity federation Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com> * feat(oidc): add e2e test for bearer OIDC and a kind cluster Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com> * feat(oidc): make OIDC workload identity federation its own feature Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com> * feat(oidc): move errors to the errors package Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com> * feat(oidc): fix race in cel package Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com> * feat(oidc): compile cel expressions Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com> --------- Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
This commit is contained in:
@@ -0,0 +1,644 @@
|
||||
package cel_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"zotregistry.dev/zot/v2/pkg/api/config"
|
||||
"zotregistry.dev/zot/v2/pkg/cel"
|
||||
)
|
||||
|
||||
func TestNewClaimProcessor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
audiences []string
|
||||
conf *config.CELClaimValidationAndMapping
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "nil config uses defaults",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: nil,
|
||||
},
|
||||
{
|
||||
name: "empty config uses defaults",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{},
|
||||
},
|
||||
{
|
||||
name: "custom username expression",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Username: "claims.email",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "custom groups expression",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Groups: "claims.groups",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple audiences",
|
||||
audiences: []string{"aud1", "aud2", "aud3"},
|
||||
conf: nil,
|
||||
},
|
||||
{
|
||||
name: "with variables",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "org", Expression: "claims.org"},
|
||||
{Name: "team", Expression: "claims.team"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with validations",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "claims.email_verified == true", Message: "email must be verified"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty audiences",
|
||||
audiences: []string{},
|
||||
conf: nil,
|
||||
err: "at least one audience must be specified",
|
||||
},
|
||||
{
|
||||
name: "nil audiences",
|
||||
audiences: nil,
|
||||
conf: nil,
|
||||
err: "at least one audience must be specified",
|
||||
},
|
||||
{
|
||||
name: "empty audience in list",
|
||||
audiences: []string{"valid", ""},
|
||||
conf: nil,
|
||||
err: "audience[1]:",
|
||||
},
|
||||
{
|
||||
name: "variable with empty name",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "", Expression: "claims.org"},
|
||||
},
|
||||
},
|
||||
err: "variable[0]:",
|
||||
},
|
||||
{
|
||||
name: "variable with invalid expression",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "org", Expression: "claims."},
|
||||
},
|
||||
},
|
||||
err: "failed to parse CEL expression for variable[0] (name: org)",
|
||||
},
|
||||
{
|
||||
name: "validation with empty message",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "true", Message: ""},
|
||||
},
|
||||
},
|
||||
err: "validation[0]:",
|
||||
},
|
||||
{
|
||||
name: "validation with invalid expression",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "claims.", Message: "some error"},
|
||||
},
|
||||
},
|
||||
err: "failed to parse CEL expression for validation[0]",
|
||||
},
|
||||
{
|
||||
name: "invalid username expression",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Username: "claims.",
|
||||
},
|
||||
err: "failed to parse CEL expression for username",
|
||||
},
|
||||
{
|
||||
name: "invalid groups expression",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Groups: "claims.",
|
||||
},
|
||||
err: "failed to parse CEL expression for groups",
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gomega := NewWithT(t)
|
||||
|
||||
processor, err := cel.NewClaimProcessor(testCase.audiences, testCase.conf)
|
||||
|
||||
if testCase.err != "" {
|
||||
gomega.Expect(err).To(HaveOccurred())
|
||||
gomega.Expect(err.Error()).To(ContainSubstring(testCase.err))
|
||||
gomega.Expect(processor).To(BeNil())
|
||||
} else {
|
||||
gomega.Expect(err).NotTo(HaveOccurred())
|
||||
gomega.Expect(processor).NotTo(BeNil())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaimProcessor_Process(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
audiences []string
|
||||
conf *config.CELClaimValidationAndMapping
|
||||
claims map[string]any
|
||||
username string
|
||||
groups []string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "default config extracts iss/sub as username",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: nil,
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
groups: nil,
|
||||
},
|
||||
{
|
||||
name: "custom username from email claim",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Username: "claims.email",
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"email": "user@example.com",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "user@example.com",
|
||||
groups: nil,
|
||||
},
|
||||
{
|
||||
name: "extract groups from claims",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Groups: "claims.groups",
|
||||
},
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"groups": []string{"admin", "developers"},
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
groups: []string{"admin", "developers"},
|
||||
},
|
||||
{
|
||||
name: "extract groups from any slice",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Groups: "claims.groups",
|
||||
},
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"groups": []any{"admin", "developers"},
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
groups: []string{"admin", "developers"},
|
||||
},
|
||||
{
|
||||
name: "audience validation - single audience match",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: nil,
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
},
|
||||
{
|
||||
name: "audience validation - multiple audiences, one matches",
|
||||
audiences: []string{"aud1", "aud2"},
|
||||
conf: nil,
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"aud": []any{"aud2", "other"},
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
},
|
||||
{
|
||||
name: "audience validation - token has multiple, config has one",
|
||||
audiences: []string{"aud2"},
|
||||
conf: nil,
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"aud": []any{"aud1", "aud2", "aud3"},
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
},
|
||||
{
|
||||
name: "audience validation - single string audience matches",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: nil,
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"aud": "my-audience",
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
},
|
||||
{
|
||||
name: "audience validation - []string type audience matches",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: nil,
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"aud": []string{"my-audience", "other-audience"},
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
},
|
||||
{
|
||||
name: "audience validation fails - single string audience no match",
|
||||
audiences: []string{"expected-aud"},
|
||||
conf: nil,
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"aud": "other-aud",
|
||||
},
|
||||
err: "does not match any of the expected audiences",
|
||||
},
|
||||
{
|
||||
name: "audience validation fails - no match",
|
||||
audiences: []string{"expected-aud"},
|
||||
conf: nil,
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"aud": []any{"other-aud"},
|
||||
},
|
||||
err: "token audience does not match any of the expected audiences",
|
||||
},
|
||||
{
|
||||
name: "audience validation fails - empty token audience",
|
||||
audiences: []string{"expected-aud"},
|
||||
conf: nil,
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"aud": []any{},
|
||||
},
|
||||
err: "does not match any of the expected audiences",
|
||||
},
|
||||
{
|
||||
name: "variables can be used in username expression",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "prefix", Expression: "'user-'"},
|
||||
},
|
||||
Username: "vars.prefix + claims.sub",
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "123",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "user-123",
|
||||
},
|
||||
{
|
||||
name: "variables can reference claims",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "domain", Expression: "claims.email.split('@')[1]"},
|
||||
},
|
||||
Username: "vars.domain + '/' + claims.sub",
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"email": "user@example.com",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "example.com/user123",
|
||||
},
|
||||
{
|
||||
name: "variables can reference other variables",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "org", Expression: "claims.org"},
|
||||
{Name: "fullOrg", Expression: "'org-' + vars.org"},
|
||||
},
|
||||
Username: "vars.fullOrg + '/' + claims.sub",
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"org": "myorg",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "org-myorg/user123",
|
||||
},
|
||||
{
|
||||
name: "validation passes",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "claims.email_verified == true", Message: "email must be verified"},
|
||||
},
|
||||
},
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"email_verified": true,
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
},
|
||||
{
|
||||
name: "validation fails",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "claims.email_verified == true", Message: "email must be verified"},
|
||||
},
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"email_verified": false,
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
err: "OIDC claim validation failed: email must be verified",
|
||||
},
|
||||
{
|
||||
name: "multiple validations all pass",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "claims.email_verified == true", Message: "email must be verified"},
|
||||
{Expression: "claims.org == 'myorg'", Message: "must be in myorg"},
|
||||
},
|
||||
},
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"email_verified": true,
|
||||
"org": "myorg",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
},
|
||||
{
|
||||
name: "multiple validations - second fails",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "claims.email_verified == true", Message: "email must be verified"},
|
||||
{Expression: "claims.org == 'myorg'", Message: "must be in myorg"},
|
||||
},
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"email_verified": true,
|
||||
"org": "otherorg",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
err: "OIDC claim validation failed: must be in myorg",
|
||||
},
|
||||
{
|
||||
name: "validation can use variables",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "allowedOrgs", Expression: "['org1', 'org2', 'org3']"},
|
||||
},
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "claims.org in vars.allowedOrgs", Message: "organization not allowed"},
|
||||
},
|
||||
},
|
||||
claims: map[string]any{
|
||||
"iss": "https://issuer.example.com",
|
||||
"sub": "user123",
|
||||
"org": "org2",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
username: "https://issuer.example.com/user123",
|
||||
},
|
||||
{
|
||||
name: "validation using variables fails",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "allowedOrgs", Expression: "['org1', 'org2', 'org3']"},
|
||||
},
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "claims.org in vars.allowedOrgs", Message: "organization not allowed"},
|
||||
},
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"org": "org4",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
err: "OIDC claim validation failed: organization not allowed",
|
||||
},
|
||||
{
|
||||
name: "username expression evaluation error",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Username: "claims.nonexistent",
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
err: "failed to evaluate username expression",
|
||||
},
|
||||
{
|
||||
name: "groups expression evaluation error",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Username: "claims.sub",
|
||||
Groups: "claims.nonexistent",
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
err: "failed to evaluate groups expression",
|
||||
},
|
||||
{
|
||||
name: "variable expression evaluation error",
|
||||
audiences: []string{"my-audience"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "bad", Expression: "claims.nonexistent"},
|
||||
},
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"aud": []any{"my-audience"},
|
||||
},
|
||||
err: "failed to evaluate variable 'bad'",
|
||||
},
|
||||
{
|
||||
name: "complex real-world scenario - GitHub Actions OIDC",
|
||||
audiences: []string{"zot-registry"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "repo", Expression: "claims.repository"},
|
||||
{Name: "owner", Expression: "claims.repository_owner"},
|
||||
},
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "vars.owner == 'myorg'", Message: "only myorg repositories allowed"},
|
||||
{Expression: "claims.ref.startsWith('refs/heads/')", Message: "must be a branch ref"},
|
||||
},
|
||||
Username: "vars.repo",
|
||||
Groups: "['github-actions', 'ci']",
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "repo:myorg/myrepo:ref:refs/heads/main",
|
||||
"repository": "myorg/myrepo",
|
||||
"repository_owner": "myorg",
|
||||
"ref": "refs/heads/main",
|
||||
"aud": []any{"zot-registry"},
|
||||
},
|
||||
username: "myorg/myrepo",
|
||||
groups: []string{"github-actions", "ci"},
|
||||
},
|
||||
{
|
||||
name: "complex real-world scenario - Kubernetes service account",
|
||||
audiences: []string{"zot"},
|
||||
conf: &config.CELClaimValidationAndMapping{
|
||||
Variables: []config.CELVariable{
|
||||
{Name: "ns", Expression: "claims['kubernetes.io/serviceaccount/namespace']"},
|
||||
{Name: "sa", Expression: "claims['kubernetes.io/serviceaccount/service-account.name']"},
|
||||
},
|
||||
Validations: []config.CELValidation{
|
||||
{Expression: "vars.ns in ['production', 'staging']", Message: "namespace not allowed"},
|
||||
},
|
||||
Username: "vars.ns + ':' + vars.sa",
|
||||
Groups: "['k8s-workloads']",
|
||||
},
|
||||
claims: map[string]any{
|
||||
"sub": "system:serviceaccount:production:my-app",
|
||||
"kubernetes.io/serviceaccount/namespace": "production",
|
||||
"kubernetes.io/serviceaccount/service-account.name": "my-app",
|
||||
"aud": []any{"zot"},
|
||||
},
|
||||
username: "production:my-app",
|
||||
groups: []string{"k8s-workloads"},
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gomega := NewWithT(t)
|
||||
|
||||
processor, err := cel.NewClaimProcessor(testCase.audiences, testCase.conf)
|
||||
gomega.Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := processor.Process(context.Background(), testCase.claims)
|
||||
|
||||
if testCase.err != "" {
|
||||
gomega.Expect(err).To(HaveOccurred())
|
||||
gomega.Expect(err.Error()).To(ContainSubstring(testCase.err))
|
||||
gomega.Expect(result).To(BeNil())
|
||||
} else {
|
||||
gomega.Expect(err).NotTo(HaveOccurred())
|
||||
gomega.Expect(result).NotTo(BeNil())
|
||||
gomega.Expect(result.Username).To(Equal(testCase.username))
|
||||
gomega.Expect(result.Groups).To(Equal(testCase.groups))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaimProcessor_Process_AudienceEdgeCases(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
audiences []string
|
||||
claims map[string]any
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "missing aud claim",
|
||||
audiences: []string{"my-audience"},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
},
|
||||
err: "missing 'aud' claim",
|
||||
},
|
||||
{
|
||||
name: "aud claim with wrong type (integer)",
|
||||
audiences: []string{"my-audience"},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"iss": "test-issuer",
|
||||
"aud": 12345,
|
||||
},
|
||||
err: "does not match any of the expected audiences",
|
||||
},
|
||||
{
|
||||
name: "aud claim with wrong type (map)",
|
||||
audiences: []string{"my-audience"},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"iss": "test-issuer",
|
||||
"aud": map[string]any{"key": "value"},
|
||||
},
|
||||
err: "does not match any of the expected audiences",
|
||||
},
|
||||
{
|
||||
name: "aud array contains non-string value",
|
||||
audiences: []string{"my-audience"},
|
||||
claims: map[string]any{
|
||||
"sub": "user123",
|
||||
"iss": "test-issuer",
|
||||
"aud": []any{"valid-aud", 123},
|
||||
},
|
||||
err: "'aud' claim contains non-string value",
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
gomega := NewWithT(t)
|
||||
|
||||
processor, err := cel.NewClaimProcessor(testCase.audiences, nil)
|
||||
gomega.Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := processor.Process(context.Background(), testCase.claims)
|
||||
|
||||
gomega.Expect(err).To(HaveOccurred())
|
||||
gomega.Expect(err.Error()).To(ContainSubstring(testCase.err))
|
||||
gomega.Expect(result).To(BeNil())
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user