myxce/src/frontend/apiserver/store/usergroups_test.go
Rongsong Shen 4a38bb3ca9
fix(apiserver): enforce self_ug job scopes and accept project filters (bug-1934, bug-1935)
bug-1934: every job-permission check read a single scope and compared it
only with "self", so a job:view or job:control grant scoped to self_ug acted
like "all" (list, get by ID, control), and self + self_ug collapsed to self.
userJobScope() now collects all of a user's grants of a job permission
before deduplication and adds them up (all; self; self_ug = the user and
everyone sharing a user group). The job list appends a submit_user filter
for narrower scopes, get-by-ID keeps covered rows in a new slice (removing
from the ranged slice skipped rows) and answers 403, both control paths
and job output check the owner against the scope. User groups now reach
the apiserver: the config push converts cluster_config_t.ugroups and
OnUserGroupsUpdated() replaces the new store (nested groups, all,
exclusions). The default basic_user now also sees its user-group mates'
jobs, as its configuration says.

bug-1935: project_name and license_project are accepted job filters, so
xcecli jobs list -P works.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 06:56:57 +00:00

55 lines
1.8 KiB
Go

package store
import (
"testing"
"apiserver/models"
)
// bug-1934: user-group membership as the cluster config defines it
// (members may be users, other groups or "all"; ex_members are removed).
func TestUserGroups_Membership(t *testing.T) {
orig := UserGroupsSnapshot()
t.Cleanup(func() { SetUserGroups(orig) })
SetUserGroups([]models.UserGroupInfo{
{Name: "team", Members: []string{"alice", "bob"}},
{Name: "dept", Members: []string{"team", "carol"}, ExMembers: []string{"bob"}},
{Name: "everyone", Members: []string{"all"}, ExMembers: []string{"mallory"}},
{Name: "loop1", Members: []string{"loop2", "x"}},
{Name: "loop2", Members: []string{"loop1"}},
})
cases := []struct {
a, b string
want bool
}{
{"alice", "bob", true}, // team
{"alice", "carol", true}, // dept (via nested team)
{"bob", "carol", true}, // everyone
{"alice", "mallory", false}, // mallory is excluded from everyone
{"mallory", "mallory", true},
{"alice", "zed", true}, // everyone
{"x", "alice", true}, // everyone
}
for _, c := range cases {
if got := UsersShareGroup(c.a, c.b); got != c.want {
t.Errorf("UsersShareGroup(%q, %q) = %v, want %v", c.a, c.b, got, c.want)
}
}
SetUserGroups([]models.UserGroupInfo{
{Name: "team", Members: []string{"alice", "bob"}},
{Name: "dept", Members: []string{"team", "carol"}, ExMembers: []string{"bob"}},
})
if UsersShareGroup("bob", "carol") {
t.Error("bob is excluded from dept and shares no group with carol")
}
peers := UserGroupPeers("alice")
if peers.All || !peers.Has("bob") || !peers.Has("carol") || !peers.Has("alice") || peers.Has("zed") {
t.Errorf("UserGroupPeers(alice) = %+v, want alice, bob, carol", peers)
}
if p := UserGroupPeers("zed"); p.All || !p.Has("zed") || p.Has("alice") {
t.Errorf("UserGroupPeers(zed) = %+v, want zed only", p)
}
}