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>
55 lines
1.8 KiB
Go
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)
|
|
}
|
|
}
|