forked from casdoor/casdoor
feat: deduplicate permission RBAC by building grouping policies in run time (#5374)
This commit is contained in:
@@ -120,18 +120,6 @@ func checkPermissionValid(permission *Permission) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -171,11 +159,6 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
err = removeGroupingPolicies(oldPermission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = removePolicies(oldPermission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -191,11 +174,6 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
|
||||
// }
|
||||
// }
|
||||
|
||||
err = addGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = addPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -212,11 +190,6 @@ func AddPermission(permission *Permission) (bool, error) {
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
err = addGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = addPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -241,11 +214,6 @@ func AddPermissions(permissions []*Permission) (bool, error) {
|
||||
for _, permission := range permissions {
|
||||
// add using for loop
|
||||
if affected != 0 {
|
||||
err = addGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = addPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -302,11 +270,6 @@ func DeletePermission(permission *Permission) (bool, error) {
|
||||
}
|
||||
|
||||
if affected {
|
||||
err = removeGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = removePolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -52,11 +52,9 @@ func getPermissionEnforcer(p *Permission, permissionIDs ...string) (*casbin.Enfo
|
||||
}
|
||||
|
||||
policyFilter := xormadapter.Filter{
|
||||
V5: policyFilterV5,
|
||||
}
|
||||
|
||||
if !HasRoleDefinition(enforcer.GetModel()) {
|
||||
policyFilter.Ptype = []string{"p"}
|
||||
// Permission enforcers only persist p rules. Legacy g rows are rebuilt from roles at runtime.
|
||||
Ptype: []string{"p"},
|
||||
V5: policyFilterV5,
|
||||
}
|
||||
|
||||
err = enforcer.LoadFilteredPolicy(policyFilter)
|
||||
@@ -64,6 +62,12 @@ func getPermissionEnforcer(p *Permission, permissionIDs ...string) (*casbin.Enfo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// we can rebuild group policies in memory
|
||||
err = loadRuntimeGroupingPolicies(enforcer, p, permissionIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return enforcer, nil
|
||||
}
|
||||
|
||||
@@ -141,13 +145,47 @@ func getPolicies(permission *Permission) [][]string {
|
||||
return policies
|
||||
}
|
||||
|
||||
func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error) {
|
||||
type permissionRoleResolver struct {
|
||||
rolesByOwner map[string][]*Role
|
||||
roleByID map[string]*Role
|
||||
}
|
||||
|
||||
func newPermissionRoleResolver() *permissionRoleResolver {
|
||||
return &permissionRoleResolver{
|
||||
rolesByOwner: map[string][]*Role{},
|
||||
roleByID: map[string]*Role{},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *permissionRoleResolver) getRoles(owner string) ([]*Role, error) {
|
||||
if roles, ok := r.rolesByOwner[owner]; ok {
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
roles, err := GetRoles(owner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.rolesByOwner[owner] = roles
|
||||
for _, role := range roles {
|
||||
r.roleByID[role.GetId()] = role
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (r *permissionRoleResolver) getRolesInRole(permissionOwner string, roleId string, visited map[string]struct{}) ([]*Role, error) {
|
||||
if roleId == "*" {
|
||||
roleId = util.GetId(permissionOwner, "*")
|
||||
}
|
||||
|
||||
roleOwner, roleName, err := util.GetOwnerAndNameFromIdWithError(roleId)
|
||||
if err != nil {
|
||||
return []*Role{}, err
|
||||
}
|
||||
if roleName == "*" {
|
||||
roles, err := GetRoles(roleOwner)
|
||||
roles, err := r.getRoles(roleOwner)
|
||||
if err != nil {
|
||||
return []*Role{}, err
|
||||
}
|
||||
@@ -155,11 +193,13 @@ func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error)
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
role, err := GetRole(roleId)
|
||||
_, err = r.getRoles(roleOwner)
|
||||
if err != nil {
|
||||
return []*Role{}, err
|
||||
}
|
||||
|
||||
role := r.roleByID[roleId]
|
||||
|
||||
if role == nil {
|
||||
return []*Role{}, nil
|
||||
}
|
||||
@@ -168,55 +208,94 @@ func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error)
|
||||
roles := []*Role{role}
|
||||
for _, subRole := range role.Roles {
|
||||
if _, ok := visited[subRole]; !ok {
|
||||
r, err := getRolesInRole(subRole, visited)
|
||||
subRoles, err := r.getRolesInRole(roleOwner, subRole, visited)
|
||||
if err != nil {
|
||||
return []*Role{}, err
|
||||
}
|
||||
|
||||
roles = append(roles, r...)
|
||||
roles = append(roles, subRoles...)
|
||||
}
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func getGroupingPolicies(permission *Permission) ([][]string, error) {
|
||||
var groupingPolicies [][]string
|
||||
func getPermissionEnforcerTargets(permission *Permission, permissionIDs ...string) ([]*Permission, error) {
|
||||
if len(permissionIDs) == 0 {
|
||||
return []*Permission{permission}, nil
|
||||
}
|
||||
|
||||
domainExist := len(permission.Domains) > 0
|
||||
permissionId := permission.GetId()
|
||||
|
||||
for _, roleId := range permission.Roles {
|
||||
visited := map[string]struct{}{}
|
||||
|
||||
if roleId == "*" {
|
||||
roleId = util.GetId(permission.Owner, "*")
|
||||
permissions := make([]*Permission, 0, len(permissionIDs))
|
||||
visited := map[string]struct{}{}
|
||||
for _, permissionID := range permissionIDs {
|
||||
if _, ok := visited[permissionID]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
rolesInRole, err := getRolesInRole(roleId, visited)
|
||||
targetPermission, err := GetPermission(permissionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if targetPermission == nil {
|
||||
return nil, fmt.Errorf("the permission: %s doesn't exist", permissionID)
|
||||
}
|
||||
|
||||
for _, role := range rolesInRole {
|
||||
roleId = role.GetId()
|
||||
for _, subUser := range role.Users {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, roleId, domain, "", "", permissionId})
|
||||
}
|
||||
} else {
|
||||
groupingPolicies = append(groupingPolicies, []string{subUser, roleId, "", "", "", permissionId})
|
||||
}
|
||||
permissions = append(permissions, targetPermission)
|
||||
visited[permissionID] = struct{}{}
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func newRuntimeGroupingPolicy(sub string, roleId string, domain string) []string {
|
||||
return []string{sub, roleId, domain, "", "", ""}
|
||||
}
|
||||
|
||||
func appendRuntimeGroupingPolicy(groupingPolicies *[][]string, visited map[string]struct{}, rule []string) {
|
||||
// we can't use []string as key, so use null character
|
||||
key := strings.Join(rule, "\x00")
|
||||
if _, ok := visited[key]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
*groupingPolicies = append(*groupingPolicies, rule)
|
||||
visited[key] = struct{}{}
|
||||
}
|
||||
|
||||
func getRuntimeGroupingPolicies(permissions []*Permission) ([][]string, error) {
|
||||
var groupingPolicies [][]string
|
||||
visitedPolicies := map[string]struct{}{}
|
||||
roleResolver := newPermissionRoleResolver()
|
||||
|
||||
for _, permission := range permissions {
|
||||
domainExist := len(permission.Domains) > 0
|
||||
for _, roleId := range permission.Roles {
|
||||
visited := map[string]struct{}{}
|
||||
rolesInRole, err := roleResolver.getRolesInRole(permission.Owner, roleId, visited)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, subRole := range role.Roles {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, roleId, domain, "", "", permissionId})
|
||||
for _, role := range rolesInRole {
|
||||
currentRoleID := role.GetId()
|
||||
for _, subUser := range role.Users {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
appendRuntimeGroupingPolicy(&groupingPolicies, visitedPolicies, newRuntimeGroupingPolicy(subUser, currentRoleID, domain))
|
||||
}
|
||||
} else {
|
||||
appendRuntimeGroupingPolicy(&groupingPolicies, visitedPolicies, newRuntimeGroupingPolicy(subUser, currentRoleID, ""))
|
||||
}
|
||||
}
|
||||
|
||||
for _, subRole := range role.Roles {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
appendRuntimeGroupingPolicy(&groupingPolicies, visitedPolicies, newRuntimeGroupingPolicy(subRole, currentRoleID, domain))
|
||||
}
|
||||
} else {
|
||||
appendRuntimeGroupingPolicy(&groupingPolicies, visitedPolicies, newRuntimeGroupingPolicy(subRole, currentRoleID, ""))
|
||||
}
|
||||
} else {
|
||||
groupingPolicies = append(groupingPolicies, []string{subRole, roleId, "", "", "", permissionId})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,6 +304,35 @@ func getGroupingPolicies(permission *Permission) ([][]string, error) {
|
||||
return groupingPolicies, nil
|
||||
}
|
||||
|
||||
func loadRuntimeGroupingPolicies(enforcer *casbin.Enforcer, permission *Permission, permissionIDs ...string) error {
|
||||
if !HasRoleDefinition(enforcer.GetModel()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
targetPermissions, err := getPermissionEnforcerTargets(permission, permissionIDs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupingPolicies, err := getRuntimeGroupingPolicies(targetPermissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
enforcer.EnableAutoSave(false)
|
||||
defer enforcer.EnableAutoSave(true)
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addPolicies(permission *Permission) error {
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
@@ -249,48 +357,6 @@ func removePolicies(permission *Permission) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func addGroupingPolicies(permission *Permission) error {
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeGroupingPolicies(permission *Permission) error {
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.RemoveGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Enforce(permission *Permission, request []string, permissionIds ...string) (bool, error) {
|
||||
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
|
||||
if err != nil {
|
||||
|
||||
314
object/permission_rbac_dedup_test.go
Normal file
314
object/permission_rbac_dedup_test.go
Normal file
@@ -0,0 +1,314 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !skipCi
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type permissionRuleRecord struct {
|
||||
Id int64 `xorm:"pk autoincr"`
|
||||
Ptype string `xorm:"varchar(100) index not null default ''"`
|
||||
V0 string `xorm:"varchar(100) index not null default ''"`
|
||||
V1 string `xorm:"varchar(100) index not null default ''"`
|
||||
V2 string `xorm:"varchar(100) index not null default ''"`
|
||||
V3 string `xorm:"varchar(100) index not null default ''"`
|
||||
V4 string `xorm:"varchar(100) index not null default ''"`
|
||||
V5 string `xorm:"varchar(100) index not null default ''"`
|
||||
}
|
||||
|
||||
func (permissionRuleRecord) TableName() string {
|
||||
return "permission_rule"
|
||||
}
|
||||
|
||||
var permissionRbacTestInit sync.Once
|
||||
|
||||
func initPermissionRbacTestDb(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
permissionRbacTestInit.Do(func() {
|
||||
oldCreateDatabase := createDatabase
|
||||
createDatabase = false
|
||||
InitConfig()
|
||||
createDatabase = oldCreateDatabase
|
||||
})
|
||||
}
|
||||
|
||||
func newPermissionRbacTestOwner(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
initPermissionRbacTestDb(t)
|
||||
|
||||
owner := "rbac-dedup-" + util.GenerateId()
|
||||
|
||||
t.Cleanup(func() {
|
||||
_, err := ormer.Engine.Where("v5 like ?", owner+"/%").Delete(&permissionRuleRecord{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to delete permission rules for owner %s: %v", owner, err)
|
||||
}
|
||||
|
||||
_, err = ormer.Engine.Where("owner = ?", owner).Delete(&Permission{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to delete permissions for owner %s: %v", owner, err)
|
||||
}
|
||||
|
||||
_, err = ormer.Engine.Where("owner = ?", owner).Delete(&Role{})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to delete roles for owner %s: %v", owner, err)
|
||||
}
|
||||
})
|
||||
|
||||
return owner
|
||||
}
|
||||
|
||||
func newTestPermission(owner string, name string, roleIDs ...string) *Permission {
|
||||
return &Permission{
|
||||
Owner: owner,
|
||||
Name: name,
|
||||
Roles: roleIDs,
|
||||
Resources: []string{"data1"},
|
||||
Actions: []string{"read"},
|
||||
Effect: "Allow",
|
||||
}
|
||||
}
|
||||
|
||||
func getPermissionRulesByPermissionID(t *testing.T, permissionID string) []permissionRuleRecord {
|
||||
t.Helper()
|
||||
|
||||
rules := make([]permissionRuleRecord, 0)
|
||||
err := ormer.Engine.Where("v5 = ?", permissionID).Asc("id").Find(&rules)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query permission rules for %s: %v", permissionID, err)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
func TestPermissionRuntimeGroupingIgnoresPersistedG(t *testing.T) {
|
||||
owner := newPermissionRbacTestOwner(t)
|
||||
|
||||
role := &Role{
|
||||
Owner: owner,
|
||||
Name: "reader",
|
||||
Users: []string{owner + "/alice"},
|
||||
}
|
||||
affected, err := AddRole(role)
|
||||
if err != nil {
|
||||
t.Fatalf("AddRole() error: %v", err)
|
||||
}
|
||||
if !affected {
|
||||
t.Fatalf("expected AddRole to affect rows")
|
||||
}
|
||||
|
||||
permission := newTestPermission(owner, "perm-reader", role.GetId())
|
||||
affected, err = AddPermission(permission)
|
||||
if err != nil {
|
||||
t.Fatalf("AddPermission() error: %v", err)
|
||||
}
|
||||
if !affected {
|
||||
t.Fatalf("expected AddPermission to affect rows")
|
||||
}
|
||||
|
||||
rules := getPermissionRulesByPermissionID(t, permission.GetId())
|
||||
if len(rules) != 1 || rules[0].Ptype != "p" {
|
||||
t.Fatalf("expected exactly one persisted p rule, got %+v", rules)
|
||||
}
|
||||
|
||||
allowed, err := Enforce(permission, []string{owner + "/alice", "data1", "read"})
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce() for alice error: %v", err)
|
||||
}
|
||||
if !allowed {
|
||||
t.Fatalf("expected alice to be allowed")
|
||||
}
|
||||
|
||||
_, err = ormer.Engine.Insert(&permissionRuleRecord{
|
||||
Ptype: "g",
|
||||
V0: owner + "/mallory",
|
||||
V1: role.GetId(),
|
||||
V5: permission.GetId(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert legacy g rule: %v", err)
|
||||
}
|
||||
|
||||
allowed, err = Enforce(permission, []string{owner + "/mallory", "data1", "read"})
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce() for mallory error: %v", err)
|
||||
}
|
||||
if allowed {
|
||||
t.Fatalf("expected legacy persisted g rule to be ignored")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRoleUsesRuntimeGroupingAndOnlyRenameRewritesP(t *testing.T) {
|
||||
owner := newPermissionRbacTestOwner(t)
|
||||
|
||||
role := &Role{
|
||||
Owner: owner,
|
||||
Name: "reader-old",
|
||||
Users: []string{owner + "/alice"},
|
||||
}
|
||||
affected, err := AddRole(role)
|
||||
if err != nil {
|
||||
t.Fatalf("AddRole() error: %v", err)
|
||||
}
|
||||
if !affected {
|
||||
t.Fatalf("expected AddRole to affect rows")
|
||||
}
|
||||
|
||||
permission := newTestPermission(owner, "perm-reader", role.GetId())
|
||||
affected, err = AddPermission(permission)
|
||||
if err != nil {
|
||||
t.Fatalf("AddPermission() error: %v", err)
|
||||
}
|
||||
if !affected {
|
||||
t.Fatalf("expected AddPermission to affect rows")
|
||||
}
|
||||
|
||||
rulesBefore := getPermissionRulesByPermissionID(t, permission.GetId())
|
||||
|
||||
updatedRole := *role
|
||||
updatedRole.Users = []string{owner + "/bob"}
|
||||
affected, err = UpdateRole(role.GetId(), &updatedRole)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateRole() for membership change error: %v", err)
|
||||
}
|
||||
if !affected {
|
||||
t.Fatalf("expected UpdateRole membership change to affect rows")
|
||||
}
|
||||
|
||||
rulesAfterMembershipChange := getPermissionRulesByPermissionID(t, permission.GetId())
|
||||
if fmt.Sprintf("%#v", rulesBefore) != fmt.Sprintf("%#v", rulesAfterMembershipChange) {
|
||||
t.Fatalf("expected membership change to keep persisted permission rules unchanged")
|
||||
}
|
||||
|
||||
allowed, err := Enforce(permission, []string{owner + "/alice", "data1", "read"})
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce() for alice after membership change error: %v", err)
|
||||
}
|
||||
if allowed {
|
||||
t.Fatalf("expected alice to lose permission after membership change")
|
||||
}
|
||||
|
||||
allowed, err = Enforce(permission, []string{owner + "/bob", "data1", "read"})
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce() for bob after membership change error: %v", err)
|
||||
}
|
||||
if !allowed {
|
||||
t.Fatalf("expected bob to gain permission after membership change")
|
||||
}
|
||||
|
||||
renamedRole := updatedRole
|
||||
renamedRole.Name = "reader-new"
|
||||
affected, err = UpdateRole(updatedRole.GetId(), &renamedRole)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateRole() for rename error: %v", err)
|
||||
}
|
||||
if !affected {
|
||||
t.Fatalf("expected UpdateRole rename to affect rows")
|
||||
}
|
||||
|
||||
updatedPermission, err := GetPermission(permission.GetId())
|
||||
if err != nil {
|
||||
t.Fatalf("GetPermission() error: %v", err)
|
||||
}
|
||||
if len(updatedPermission.Roles) != 1 || updatedPermission.Roles[0] != renamedRole.GetId() {
|
||||
t.Fatalf("expected permission role reference to be renamed")
|
||||
}
|
||||
|
||||
rulesAfterRename := getPermissionRulesByPermissionID(t, permission.GetId())
|
||||
if len(rulesAfterRename) != 1 || rulesAfterRename[0].Ptype != "p" || rulesAfterRename[0].V0 != renamedRole.GetId() {
|
||||
t.Fatalf("expected rename to rebuild persisted p rule with new role id, got %+v", rulesAfterRename)
|
||||
}
|
||||
|
||||
allowed, err = Enforce(updatedPermission, []string{owner + "/bob", "data1", "read"})
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce() for bob after rename error: %v", err)
|
||||
}
|
||||
if !allowed {
|
||||
t.Fatalf("expected bob to stay allowed after role rename")
|
||||
}
|
||||
}
|
||||
|
||||
// issue 5346
|
||||
func TestPermissionEnforcerDeduplicatesRuntimeGroupingPoliciesAcross1000Permissions(t *testing.T) {
|
||||
owner := newPermissionRbacTestOwner(t)
|
||||
|
||||
const (
|
||||
permissionCount = 1000
|
||||
userCount = 1000
|
||||
)
|
||||
|
||||
users := make([]string, 0, userCount)
|
||||
for i := range userCount {
|
||||
users = append(users, fmt.Sprintf("%s/user-%04d", owner, i))
|
||||
}
|
||||
|
||||
role := &Role{
|
||||
Owner: owner,
|
||||
Name: "shared-role",
|
||||
Users: users,
|
||||
}
|
||||
affected, err := AddRole(role)
|
||||
if err != nil {
|
||||
t.Fatalf("AddRole() error: %v", err)
|
||||
}
|
||||
if !affected {
|
||||
t.Fatalf("expected AddRole to affect rows")
|
||||
}
|
||||
|
||||
permissions := make([]*Permission, 0, permissionCount)
|
||||
permissionIDs := make([]string, 0, permissionCount)
|
||||
for i := 0; i < permissionCount; i++ {
|
||||
permission := newTestPermission(owner, fmt.Sprintf("perm-%04d", i), role.GetId())
|
||||
permissions = append(permissions, permission)
|
||||
permissionIDs = append(permissionIDs, permission.GetId())
|
||||
}
|
||||
|
||||
affected, err = AddPermissions(permissions)
|
||||
if err != nil {
|
||||
t.Fatalf("AddPermissions() error: %v", err)
|
||||
}
|
||||
if !affected {
|
||||
t.Fatalf("expected AddPermissions to affect rows")
|
||||
}
|
||||
|
||||
enforcer, err := getPermissionEnforcer(permissions[0], permissionIDs...)
|
||||
if err != nil {
|
||||
t.Fatalf("getPermissionEnforcer() error: %v", err)
|
||||
}
|
||||
|
||||
if len(enforcer.GetPolicy()) != permissionCount {
|
||||
t.Fatalf("expected %d p rules in merged enforcer, got %d", permissionCount, len(enforcer.GetPolicy()))
|
||||
}
|
||||
if len(enforcer.GetGroupingPolicy()) != userCount {
|
||||
t.Fatalf("expected deduplicated runtime g rules to stay at %d, got %d", userCount, len(enforcer.GetGroupingPolicy()))
|
||||
}
|
||||
|
||||
allowed, err := enforcer.Enforce(users[userCount-1], "data1", "read")
|
||||
if err != nil {
|
||||
t.Fatalf("Enforce() in 1000x1000 scenario error: %v", err)
|
||||
}
|
||||
if !allowed {
|
||||
t.Fatalf("expected last user to be allowed in 1000x1000 scenario")
|
||||
}
|
||||
}
|
||||
@@ -98,40 +98,23 @@ func UpdateRole(id string, role *Role) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
visited := map[string]struct{}{}
|
||||
|
||||
permissions, err := GetPermissionsByRole(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
removeGroupingPolicies(permission)
|
||||
removePolicies(permission)
|
||||
visited[permission.GetId()] = struct{}{}
|
||||
}
|
||||
|
||||
ancestorRoles, err := GetAncestorRoles(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, r := range ancestorRoles {
|
||||
permissions, err := GetPermissionsByRole(r.GetId())
|
||||
renameRole := name != role.Name
|
||||
oldPermissions := []*Permission{}
|
||||
if renameRole {
|
||||
oldPermissions, err = GetPermissionsByRole(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
permissionId := permission.GetId()
|
||||
if _, ok := visited[permissionId]; !ok {
|
||||
removeGroupingPolicies(permission)
|
||||
visited[permissionId] = struct{}{}
|
||||
for _, permission := range oldPermissions {
|
||||
err = removePolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if name != role.Name {
|
||||
if renameRole {
|
||||
err := roleChangeTrigger(name, role.Name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -143,47 +126,16 @@ func UpdateRole(id string, role *Role) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
visited = map[string]struct{}{}
|
||||
newRoleID := role.GetId()
|
||||
permissions, err = GetPermissionsByRole(newRoleID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
err = addGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = addPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
visited[permission.GetId()] = struct{}{}
|
||||
}
|
||||
|
||||
ancestorRoles, err = GetAncestorRoles(newRoleID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, r := range ancestorRoles {
|
||||
permissions, err := GetPermissionsByRole(r.GetId())
|
||||
if renameRole && affected != 0 {
|
||||
permissions, err := GetPermissionsByRole(role.GetId())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
permissionId := permission.GetId()
|
||||
if _, ok := visited[permissionId]; !ok {
|
||||
err = addGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
visited[permissionId] = struct{}{}
|
||||
err = addPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user