mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-08-31 00:47:15 +02:00
OIDC roles were translated by a hardcoded switch that only recognised the literal names user/admin/manager/support (dropping api) and required the IdP to emit exactly those names. Add an optional auth.oidc.role-mapping (OIDC role/group claim value -> CC role) so operators can map their own realm/client role names, including api. The mapping is the sole source of roles: only mapped names are honored, unmapped token roles are ignored, and users without any mapped role receive the base "user" role. Mapping targets are validated once at startup. Role assignment stays authoritative on every login as before. Note: deployments relying on the IdP emitting literal CC role names must now map them explicitly (e.g. "admin": "admin"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 66451678484b
83 lines
2.1 KiB
Go
83 lines
2.1 KiB
Go
// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
|
|
// All rights reserved. This file is part of cc-backend.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package auth
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/ClusterCockpit/cc-lib/v2/schema"
|
|
)
|
|
|
|
func TestMapOIDCRoles(t *testing.T) {
|
|
var (
|
|
user = schema.GetRoleString(schema.RoleUser)
|
|
admin = schema.GetRoleString(schema.RoleAdmin)
|
|
support = schema.GetRoleString(schema.RoleSupport)
|
|
api = schema.GetRoleString(schema.RoleAPI)
|
|
)
|
|
|
|
mapping := map[string]string{
|
|
"cc-admins": admin,
|
|
"cc-support": support,
|
|
"cc-api": api,
|
|
"staff": support, // second name mapping to the same role
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
oidcRoles []string
|
|
mapping map[string]string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "explicit mapping to elevated roles",
|
|
oidcRoles: []string{"cc-admins", "cc-api"},
|
|
mapping: mapping,
|
|
want: []string{admin, api},
|
|
},
|
|
{
|
|
name: "unmapped names are ignored (no identity fallback)",
|
|
oidcRoles: []string{"admin", "support", "unknown"},
|
|
mapping: mapping,
|
|
want: []string{user},
|
|
},
|
|
{
|
|
name: "mix of mapped and unmapped keeps only mapped",
|
|
oidcRoles: []string{"cc-admins", "admin", "noise"},
|
|
mapping: mapping,
|
|
want: []string{admin},
|
|
},
|
|
{
|
|
name: "empty token roles default to user",
|
|
oidcRoles: nil,
|
|
mapping: mapping,
|
|
want: []string{user},
|
|
},
|
|
{
|
|
name: "no mapping configured defaults to user",
|
|
oidcRoles: []string{"cc-admins", "admin"},
|
|
mapping: map[string]string{},
|
|
want: []string{user},
|
|
},
|
|
{
|
|
name: "duplicate target roles are deduplicated and sorted",
|
|
oidcRoles: []string{"cc-support", "staff", "cc-admins"},
|
|
mapping: mapping,
|
|
want: []string{admin, support},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := mapOIDCRoles(tt.oidcRoles, tt.mapping)
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("mapOIDCRoles() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|