Files
zot/pkg/api/authn_internal_test.go
T
Andrei Aaron fa2960b705 fix(auth): refine OIDC identity handling and claim-mapping logs (#4028)
Rename getOpenIDUsername to getOpenIDIdentity and thread "identity"
through bearer OIDC, Basic-auth parsing, OAuth2Callback, and log fields.

Only fall back (and warn) to the default email claim when the configured
username claim is non-default but missing or empty.

Stop emitting Info logs when groups are absent on only UserInfo or only ID
token claims; log once at Debug when no groups remain after merging both.

Update ClaimMapping docs to mention username and groups claims; fix mTLS
extractIdentity comment typo; clarify GetAuthUserFromRequestSession doc.

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
2026-05-01 09:34:48 -07:00

337 lines
8.9 KiB
Go

package api
import (
"slices"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/oidc/v3/pkg/oidc"
"zotregistry.dev/zot/v2/pkg/api/config"
"zotregistry.dev/zot/v2/pkg/log"
)
func TestGetOpenIDClaimMapping(t *testing.T) {
t.Parallel()
tests := []struct {
name string
authConfig *config.AuthConfig
providerName string
expectedIdentityClaim string
expectedGroups string
expectedIdentityConfigured bool
}{
{
name: "nil auth config uses defaults",
expectedIdentityClaim: defaultUsernameClaim,
expectedGroups: defaultGroupsClaim,
expectedIdentityConfigured: false,
},
{
name: "empty provider uses defaults",
authConfig: &config.AuthConfig{
OpenID: &config.OpenIDConfig{
Providers: map[string]config.OpenIDProviderConfig{},
},
},
expectedIdentityClaim: defaultUsernameClaim,
expectedGroups: defaultGroupsClaim,
expectedIdentityConfigured: false,
},
{
name: "missing provider uses defaults",
authConfig: &config.AuthConfig{
OpenID: &config.OpenIDConfig{
Providers: map[string]config.OpenIDProviderConfig{},
},
},
providerName: "oidc",
expectedIdentityClaim: defaultUsernameClaim,
expectedGroups: defaultGroupsClaim,
expectedIdentityConfigured: false,
},
{
name: "provider without claim mapping uses defaults",
authConfig: &config.AuthConfig{
OpenID: &config.OpenIDConfig{
Providers: map[string]config.OpenIDProviderConfig{
"oidc": {},
},
},
},
providerName: "oidc",
expectedIdentityClaim: defaultUsernameClaim,
expectedGroups: defaultGroupsClaim,
expectedIdentityConfigured: false,
},
{
name: "custom username and groups claims",
authConfig: &config.AuthConfig{
OpenID: &config.OpenIDConfig{
Providers: map[string]config.OpenIDProviderConfig{
"oidc": {
ClaimMapping: &config.ClaimMapping{
Username: "preferred_username",
Groups: "roles",
},
},
},
},
},
providerName: "oidc",
expectedIdentityClaim: "preferred_username",
expectedGroups: "roles",
expectedIdentityConfigured: true,
},
{
name: "custom groups keeps default username",
authConfig: &config.AuthConfig{
OpenID: &config.OpenIDConfig{
Providers: map[string]config.OpenIDProviderConfig{
"oidc": {
ClaimMapping: &config.ClaimMapping{
Groups: "roles",
},
},
},
},
},
providerName: "oidc",
expectedIdentityClaim: defaultUsernameClaim,
expectedGroups: "roles",
expectedIdentityConfigured: false,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
identityClaim, groupsClaim, identityConfigured := getOpenIDClaimMapping(test.authConfig, test.providerName)
assert.Equal(t, test.expectedIdentityClaim, identityClaim)
assert.Equal(t, test.expectedGroups, groupsClaim)
assert.Equal(t, test.expectedIdentityConfigured, identityConfigured)
})
}
logger := log.NewTestLogger()
t.Run("extractOpenIDIdentity_fallbackToEmailWhenConfiguredNonDefaultClaimMissing", func(t *testing.T) {
t.Parallel()
authConfig := &config.AuthConfig{
OpenID: &config.OpenIDConfig{
Providers: map[string]config.OpenIDProviderConfig{
"oidc": {
ClaimMapping: &config.ClaimMapping{
Username: "preferred_username",
},
},
},
},
}
info := &oidc.UserInfo{
UserInfoProfile: oidc.UserInfoProfile{
PreferredUsername: "",
},
UserInfoEmail: oidc.UserInfoEmail{Email: "user@example.com"},
}
identity, groups, ok := extractOpenIDIdentity(logger, authConfig, "oidc", info, nil)
assert.True(t, ok)
assert.Equal(t, "user@example.com", identity)
assert.Empty(t, groups)
})
t.Run("extractOpenIDIdentity_explicitDefaultClaimMissingRejects", func(t *testing.T) {
t.Parallel()
authConfig := &config.AuthConfig{
OpenID: &config.OpenIDConfig{
Providers: map[string]config.OpenIDProviderConfig{
"oidc": {
ClaimMapping: &config.ClaimMapping{
Username: "email",
},
},
},
},
}
info := &oidc.UserInfo{
UserInfoEmail: oidc.UserInfoEmail{Email: ""},
}
identity, groups, ok := extractOpenIDIdentity(logger, authConfig, "oidc", info, nil)
assert.False(t, ok)
assert.Empty(t, identity)
assert.Nil(t, groups)
})
t.Run("extractOpenIDIdentity_mergesSortsDedupesGroupsFromUserInfoAndIDToken", func(t *testing.T) {
t.Parallel()
info := &oidc.UserInfo{
UserInfoEmail: oidc.UserInfoEmail{Email: "user@example.com"},
Claims: map[string]any{
"groups": []any{"b", "a", "", nil, "a"},
},
}
idTokenClaims := map[string]any{
"groups": []string{"c", "b"},
}
identity, groups, ok := extractOpenIDIdentity(logger, nil, "", info, idTokenClaims)
assert.True(t, ok)
assert.Equal(t, "user@example.com", identity)
expected := []string{"a", "b", "c"}
assert.True(t, slices.Equal(expected, groups))
})
}
func TestGetOpenIDIdentity(t *testing.T) {
t.Parallel()
info := &oidc.UserInfo{
Subject: "subject-id",
UserInfoProfile: oidc.UserInfoProfile{
Name: "Full Name",
PreferredUsername: "preferred-user",
},
UserInfoEmail: oidc.UserInfoEmail{Email: "user@example.com"},
Claims: map[string]any{
"custom_username": "custom-user",
"numeric": 42,
},
}
tests := []struct {
name string
info *oidc.UserInfo
claim string
expected string
}{
{name: "nil userinfo", claim: defaultUsernameClaim},
{name: "preferred username", info: info, claim: "preferred_username", expected: "preferred-user"},
{name: "email", info: info, claim: defaultUsernameClaim, expected: "user@example.com"},
{name: "subject", info: info, claim: "sub", expected: "subject-id"},
{name: "name", info: info, claim: "name", expected: "Full Name"},
{name: "custom string claim", info: info, claim: "custom_username", expected: "custom-user"},
{name: "custom non-string claim", info: info, claim: "numeric"},
{name: "missing custom claim", info: info, claim: "missing"},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
identity := getOpenIDIdentity(test.info, test.claim)
assert.Equal(t, test.expected, identity)
})
}
}
func TestAppendOpenIDGroups(t *testing.T) {
t.Parallel()
tests := []struct {
name string
groups []string
claims map[string]any
claim string
expected []string
expectedFound bool
}{
{
name: "appends any slice",
groups: []string{"existing"},
claims: map[string]any{"roles": []any{"dev", 7}},
claim: "roles",
expected: []string{"existing", "dev", "7"},
expectedFound: true,
},
{
name: "skips nil and empty entries in any slice",
claims: map[string]any{"roles": []any{"dev", nil, ""}},
claim: "roles",
expected: []string{"dev"},
expectedFound: true,
},
{
name: "appends string slice",
claims: map[string]any{"roles": []string{"admin", "ops"}},
claim: "roles",
expected: []string{"admin", "ops"},
expectedFound: true,
},
{
name: "skips empty entries in string slice",
claims: map[string]any{"roles": []string{"admin", "", "ops"}},
claim: "roles",
expected: []string{"admin", "ops"},
expectedFound: true,
},
{
name: "appends non-empty string",
claims: map[string]any{"roles": "admin"},
claim: "roles",
expected: []string{"admin"},
expectedFound: true,
},
{
name: "finds empty string",
claims: map[string]any{"roles": ""},
claim: "roles",
expected: nil,
expectedFound: true,
},
{
name: "finds empty any slice",
claims: map[string]any{"roles": []any{}},
claim: "roles",
expected: nil,
expectedFound: true,
},
{
name: "finds empty string slice",
claims: map[string]any{"roles": []string{}},
claim: "roles",
expected: nil,
expectedFound: true,
},
{
name: "does not find missing claim",
claims: map[string]any{},
claim: "roles",
expected: nil,
expectedFound: false,
},
{
name: "does not find unsupported claim type",
claims: map[string]any{"roles": 7},
claim: "roles",
expected: nil,
expectedFound: false,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
groups, found := appendOpenIDGroups(test.groups, test.claims, test.claim)
assert.Equal(t, test.expectedFound, found)
assert.Equal(t, test.expected, groups)
})
}
}