mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
The resolved-identity match in canonical auto-register always preserved
the stored host, so the route-aware IP preference added to the agent in
022f170df could never take effect for an already-registered node: a
reinstall re-matched the node and kept the stale short-DNS host forever.
Preserve the stored host only when it is absent from the agent's ordered
candidate list (an admin-managed endpoint the agent cannot see); when the
agent itself lists the stored host as a lower-priority candidate, adopt
the newly selected host. Identity continuity (same node record, same
token) is unchanged.
121 lines
2.9 KiB
Go
121 lines
2.9 KiB
Go
package api
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
"github.com/rcourtman/pulse-go-rewrite/pkg/auth"
|
|
)
|
|
|
|
type mockAuthManager struct {
|
|
auth.Manager
|
|
updatedUser string
|
|
updatedRoles []string
|
|
}
|
|
|
|
func TestOIDCRoleMappingLogic(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
groupsClaim string
|
|
mappings map[string]string
|
|
claims map[string]any
|
|
expectedRoles []string
|
|
}{
|
|
{
|
|
name: "simple mapping",
|
|
groupsClaim: "groups",
|
|
mappings: map[string]string{
|
|
"oidc-admin": "admin",
|
|
},
|
|
claims: map[string]any{
|
|
"groups": []string{"oidc-admin", "other"},
|
|
},
|
|
expectedRoles: []string{"admin"},
|
|
},
|
|
{
|
|
name: "multiple mappings",
|
|
groupsClaim: "groups",
|
|
mappings: map[string]string{
|
|
"oidc-admin": "admin",
|
|
"oidc-dev": "operator",
|
|
},
|
|
claims: map[string]any{
|
|
"groups": []string{"oidc-admin", "oidc-dev"},
|
|
},
|
|
expectedRoles: []string{"admin", "operator"},
|
|
},
|
|
{
|
|
name: "comma separated string groups",
|
|
groupsClaim: "groups",
|
|
mappings: map[string]string{
|
|
"admin": "admin",
|
|
"user": "viewer",
|
|
},
|
|
claims: map[string]any{
|
|
"groups": "admin, user",
|
|
},
|
|
expectedRoles: []string{"admin", "viewer"},
|
|
},
|
|
{
|
|
name: "no matches",
|
|
groupsClaim: "groups",
|
|
mappings: map[string]string{
|
|
"admin": "admin",
|
|
},
|
|
claims: map[string]any{
|
|
"groups": []string{"guest"},
|
|
},
|
|
expectedRoles: nil,
|
|
},
|
|
// Regression cases for #1535: the IdP's group casing/whitespace must
|
|
// not have to match the configured mapping exactly. AllowedGroups
|
|
// already match case-insensitively (intersects), so role mapping has to
|
|
// as well or a user passes the group gate but silently lands with no
|
|
// role.
|
|
{
|
|
name: "case-insensitive group match",
|
|
groupsClaim: "groups",
|
|
mappings: map[string]string{
|
|
"admins": "admin",
|
|
},
|
|
claims: map[string]any{
|
|
"groups": []string{"Admins"},
|
|
},
|
|
expectedRoles: []string{"admin"},
|
|
},
|
|
{
|
|
name: "whitespace-padded mapping key match",
|
|
groupsClaim: "groups",
|
|
mappings: map[string]string{
|
|
" admin ": "admin",
|
|
},
|
|
claims: map[string]any{
|
|
"groups": []string{"admin"},
|
|
},
|
|
expectedRoles: []string{"admin"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg := &config.OIDCConfig{
|
|
GroupsClaim: tt.groupsClaim,
|
|
GroupRoleMappings: tt.mappings,
|
|
}
|
|
|
|
groups := extractStringSliceClaim(tt.claims, cfg.GroupsClaim)
|
|
rolesToAssign := resolveGroupRoles(groups, cfg.GroupRoleMappings)
|
|
|
|
if len(rolesToAssign) != len(tt.expectedRoles) {
|
|
t.Errorf("expected %d roles, got %d", len(tt.expectedRoles), len(rolesToAssign))
|
|
return
|
|
}
|
|
|
|
for i, r := range rolesToAssign {
|
|
if r != tt.expectedRoles[i] {
|
|
t.Errorf("expected role %s at index %d, got %s", tt.expectedRoles[i], i, r)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|