fix(09): WR-17 merge an admin's own permissions over the role's, honouring denies
This commit is contained in:
@@ -630,3 +630,65 @@ func TestAdminLogoutRevokesExpiredRefreshableToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBackendUserPermissionsOverrideRole pins WR-17: an administrator's own
|
||||
// backend_users.permissions are merged over the role's the way Winter merges
|
||||
// them. A user-level -1 removes a permission the role grants and a user-level 1
|
||||
// adds one the role lacks, so a row copied from WinterCMS keeps its denies.
|
||||
func TestBackendUserPermissionsOverrideRole(t *testing.T) {
|
||||
gdb := adminGorm(t)
|
||||
role := cabana.BackendUserRole{Name: "WR-17 role", Code: "wr17", Permissions: `{"wr17.a":1,"wr17.b":1,"wr17.wild.*":1}`}
|
||||
if err := gdb.Create(&role).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
perms string
|
||||
roleP *uint
|
||||
want map[string]bool
|
||||
}{
|
||||
{name: "no user level permissions", perms: "", roleP: &role.ID, want: map[string]bool{"wr17.a": true, "wr17.b": true, "wr17.wild.*": true}},
|
||||
{name: "empty object", perms: "{}", roleP: &role.ID, want: map[string]bool{"wr17.a": true, "wr17.b": true, "wr17.wild.*": true}},
|
||||
{name: "deny removes a role grant and a grant adds one", perms: `{"wr17.b":-1,"wr17.c":1}`, roleP: &role.ID, want: map[string]bool{"wr17.a": true, "wr17.c": true, "wr17.wild.*": true}},
|
||||
{name: "zero is not a grant", perms: `{"wr17.a":0}`, roleP: &role.ID, want: map[string]bool{"wr17.b": true, "wr17.wild.*": true}},
|
||||
{name: "deny of a wildcard grant", perms: `{"wr17.wild.*":-1}`, roleP: &role.ID, want: map[string]bool{"wr17.a": true, "wr17.b": true}},
|
||||
{name: "no role", perms: `{"wr17.only":1}`, roleP: nil, want: map[string]bool{"wr17.only": true}},
|
||||
{name: "malformed JSON is ignored", perms: `not json`, roleP: &role.ID, want: map[string]bool{"wr17.a": true, "wr17.b": true, "wr17.wild.*": true}},
|
||||
}
|
||||
for i, tc := range cases {
|
||||
hash, err := bouncer.HashPassword(4, "x")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
user := cabana.BackendUser{
|
||||
Login: "wr17-" + strconv.Itoa(i), Email: "wr17-" + strconv.Itoa(i) + "@example.test", Password: hash,
|
||||
IsActivated: true, RoleID: tc.roleP, Permissions: tc.perms,
|
||||
}
|
||||
if err := gdb.Create(&user).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
principal, err := (cabana.BackendUsers{DB: gdb}).FindByID(context.Background(), user.ID)
|
||||
if err != nil || principal == nil {
|
||||
t.Fatalf("%s: FindByID = %v, %v", tc.name, principal, err)
|
||||
}
|
||||
got := map[string]bool{}
|
||||
for code, on := range principal.PermissionGrants {
|
||||
if on {
|
||||
got[code] = true
|
||||
}
|
||||
}
|
||||
if len(got) != len(tc.want) {
|
||||
t.Fatalf("%s: grants = %v, want %v", tc.name, got, tc.want)
|
||||
}
|
||||
for code := range tc.want {
|
||||
if !got[code] {
|
||||
t.Fatalf("%s: grants = %v, want %v", tc.name, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
// The check the guard feeds: the denied code no longer passes Allows.
|
||||
denied := &bouncer.Principal{Backend: true, PermissionGrants: map[string]bool{"wr17.a": true}}
|
||||
if cabana.Allows(denied, []string{"wr17.b"}) {
|
||||
t.Fatal("a denied code passed Allows")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user