Files
cc-backend/internal/auth/ldap_test.go
T
moebiusbandandClaude Opus 4.8 63a82d022b feat(auth): assign elevated LDAP roles via configurable filters
The LDAP sync only ever granted the base "user" role. Add an optional
auth.ldap.role-filters map (role -> LDAP filter) so accounts matching a
filter are granted that elevated role (admin/support/api/manager).

LDAP is authoritative for the configured roles: sync both adds and removes
them to match group membership, while roles not listed (e.g. a manually
granted manager) are preserved. A managed manager that still has assigned
projects is never stripped. With no role-filters configured behaviour is
identical to before.

Roles are reconciled during periodic sync and at login. Sync evaluates
each filter once over the whole base (one search per role, not per user)
and reconciles existing users via a single ListUsers lookup plus the new
UserRepository.UpdateRoles helper.

Closes #74

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: e38526c3259d
2026-06-19 06:36:55 +02:00

107 lines
2.7 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 TestMergeLdapRoles(t *testing.T) {
var (
user = schema.GetRoleString(schema.RoleUser)
admin = schema.GetRoleString(schema.RoleAdmin)
support = schema.GetRoleString(schema.RoleSupport)
api = schema.GetRoleString(schema.RoleAPI)
manager = schema.GetRoleString(schema.RoleManager)
)
tests := []struct {
name string
current []string
managed []string
matched []string
projects []string
want []string
}{
{
name: "no managed roles is a no-op keeping user baseline",
current: []string{user},
managed: nil,
matched: nil,
want: []string{user},
},
{
name: "add matched elevated role to plain user",
current: []string{user},
managed: []string{admin, support},
matched: []string{admin},
want: []string{admin, user},
},
{
name: "remove managed role when no longer matched",
current: []string{admin, user},
managed: []string{admin},
matched: nil,
want: []string{user},
},
{
name: "preserve non-managed roles (manager, api)",
current: []string{api, manager, user},
managed: []string{admin, support},
matched: []string{support},
want: []string{api, manager, support, user},
},
{
name: "managed manager with projects is not removed",
current: []string{manager, user},
managed: []string{manager},
matched: nil,
projects: []string{"projA"},
want: []string{manager, user},
},
{
name: "managed manager without projects is removed",
current: []string{manager, user},
managed: []string{manager},
matched: nil,
want: []string{user},
},
{
name: "user baseline always present even if absent in current",
current: []string{admin},
managed: []string{admin},
matched: []string{admin},
want: []string{admin, user},
},
{
name: "new user (nil current) gets matched roles plus baseline",
current: nil,
managed: []string{admin, support, api},
matched: []string{admin, api},
want: []string{admin, api, user},
},
{
name: "result is deduplicated",
current: []string{admin, admin, user},
managed: []string{admin},
matched: []string{admin},
want: []string{admin, user},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := mergeLdapRoles(tt.current, tt.managed, tt.matched, tt.projects)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("mergeLdapRoles() = %v, want %v", got, tt.want)
}
})
}
}