Repository: casbin/casbin
Pull request #1003 (2022-05-02 06:52:14)

feat: allow permissions management to use additional p-types
synced a new feature from pycasbin
see at  [pycasbin: add named policy](https://github.com/casbin/pycasbin/pull/256)

casbin-bot:  @tangyang9464 @closetool @sagilio please review
hsluoyz: @tangyang9464 
tangyang9464: @Nekotoxin use feat instead of fix
Nekotoxin: > @Nekotoxin use feat instead of fix

changed

Patch:
diff --git a/examples/rbac_with_multiple_policy_model.conf b/examples/rbac_with_multiple_policy_model.conf
new file mode 100644
index 0000000..6a7fb09
--- /dev/null
+++ b/examples/rbac_with_multiple_policy_model.conf
@@ -0,0 +1,16 @@
+[request_definition]
+r = user, thing, action
+
+[policy_definition]
+p = role, thing, action
+p2 = role, action
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+m = g(r.user, p.role) && r.thing == p.thing && r.action == p.action
+m2 = g(r.user, p2.role) && r.action == p.action
+
+[role_definition]
+g = _,_
\ No newline at end of file
diff --git a/examples/rbac_with_multiple_policy_policy.csv b/examples/rbac_with_multiple_policy_policy.csv
new file mode 100644
index 0000000..18e9cbc
--- /dev/null
+++ b/examples/rbac_with_multiple_policy_policy.csv
@@ -0,0 +1,8 @@
+p, user, /data, GET
+p, admin, /data, POST
+
+p2, user, view
+p2, admin, create
+
+g, admin, user
+g, alice, admin
\ No newline at end of file
diff --git a/rbac_api.go b/rbac_api.go
index f5672a1..370af88 100644
--- a/rbac_api.go
+++ b/rbac_api.go
@@ -152,8 +152,16 @@ func (e *Enforcer) DeletePermissionsForUser(user string) (bool, error) {
 
 // GetPermissionsForUser gets permissions for a user or role.
 func (e *Enforcer) GetPermissionsForUser(user string, domain ...string) [][]string {
+	return e.GetNamedPermissionsForUser("p", user, domain...)
+}
+
+// GetNamedPermissionsForUser gets permissions for a user or role by named policy.
+func (e *Enforcer) GetNamedPermissionsForUser(ptype string, user string, domain ...string) [][]string {
 	permission := make([][]string, 0)
-	for ptype, assertion := range e.model["p"] {
+	for pType, assertion := range e.model["p"] {
+		if pType != ptype {
+			continue
+		}
 		args := make([]string, len(assertion.Tokens))
 		args[0] = user
 
@@ -163,7 +171,7 @@ func (e *Enforcer) GetPermissionsForUser(user string, domain ...string) [][]stri
 				args[index] = domain[0]
 			}
 		}
-		perm := e.GetFilteredPolicy(0, args...)
+		perm := e.GetFilteredNamedPolicy(ptype, 0, args...)
 		permission = append(permission, perm...)
 	}
 	return permission
@@ -255,6 +263,19 @@ func (e *Enforcer) GetImplicitUsersForRole(name string, domain ...string) ([]str
 // GetPermissionsForUser("alice") can only get: [["alice", "data2", "read"]].
 // But GetImplicitPermissionsForUser("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]].
 func (e *Enforcer) GetImplicitPermissionsForUser(user string, domain ...string) ([][]string, error) {
+	return e.GetNamedImplicitPermissionsForUser("p", user, domain...)
+}
+
+// GetNamedImplicitPermissionsForUser gets implicit permissions for a user or role by named policy.
+// Compared to GetNamedPermissionsForUser(), this function retrieves permissions for inherited roles.
+// For example:
+// p, admin, data1, read
+// p2, admin, create
+// g, alice, admin
+//
+// GetImplicitPermissionsForUser("alice") can only get: [["admin", "data1", "read"]], whose policy is default policy "p"
+// But you can specify the named policy "p2" to get: [["admin", "create"]] by    GetNamedImplicitPermissionsForUser("p2","alice")
+func (e *Enforcer) GetNamedImplicitPermissionsForUser(ptype string, user string, domain ...string) ([][]string, error) {
 	roles, err := e.GetImplicitRolesForUser(user, domain...)
 	if err != nil {
 		return nil, err
@@ -265,7 +286,7 @@ func (e *Enforcer) GetImplicitPermissionsForUser(user string, domain ...string)
 	var res [][]string
 	var permissions [][]string
 	for _, role := range roles {
-		permissions = e.GetPermissionsForUser(role, domain...)
+		permissions = e.GetNamedPermissionsForUser(ptype, role, domain...)
 
 		res = append(res, permissions...)
 	}
diff --git a/rbac_api_synced.go b/rbac_api_synced.go
index aef005f..22df8bf 100644
--- a/rbac_api_synced.go
+++ b/rbac_api_synced.go
@@ -122,6 +122,13 @@ func (e *SyncedEnforcer) GetPermissionsForUser(user string, domain ...string) []
 	return e.Enforcer.GetPermissionsForUser(user, domain...)
 }
 
+// GetNamedPermissionsForUser gets permissions for a user or role by named policy.
+func (e *SyncedEnforcer) GetNamedPermissionsForUser(ptype string, user string, domain ...string) [][]string {
+	e.m.RLock()
+	defer e.m.RUnlock()
+	return e.Enforcer.GetNamedPermissionsForUser(ptype, user, domain...)
+}
+
 // HasPermissionForUser determines whether a user has a permission.
 func (e *SyncedEnforcer) HasPermissionForUser(user string, permission ...string) bool {
 	e.m.RLock()
@@ -158,6 +165,21 @@ func (e *SyncedEnforcer) GetImplicitPermissionsForUser(user string, domain ...st
 	return e.Enforcer.GetImplicitPermissionsForUser(user, domain...)
 }
 
+// GetNamedImplicitPermissionsForUser gets implicit permissions for a user or role by named policy.
+// Compared to GetNamedPermissionsForUser(), this function retrieves permissions for inherited roles.
+// For example:
+// p, admin, data1, read
+// p2, admin, create
+// g, alice, admin
+//
+// GetImplicitPermissionsForUser("alice") can only get: [["admin", "data1", "read"]], whose policy is default policy "p"
+// But you can specify the named policy "p2" to get: [["admin", "create"]] by    GetNamedImplicitPermissionsForUser("p2","alice")
+func (e *SyncedEnforcer) GetNamedImplicitPermissionsForUser(ptype string, user string, domain ...string) ([][]string, error) {
+	e.m.RLock()
+	defer e.m.RUnlock()
+	return e.Enforcer.GetNamedImplicitPermissionsForUser(ptype, user, domain...)
+}
+
 // GetImplicitUsersForPermission gets implicit users for a permission.
 // For example:
 // p, admin, data1, read


Test patch:
diff --git a/rbac_api_test.go b/rbac_api_test.go
index bad576d..b2ba1b8 100644
--- a/rbac_api_test.go
+++ b/rbac_api_test.go
@@ -209,6 +209,16 @@ func testHasPermission(t *testing.T, e *Enforcer, name string, permission []stri
 	}
 }
 
+func testGetNamedPermissionsForUser(t *testing.T, e *Enforcer, ptype string, name string, res [][]string, domain ...string) {
+	t.Helper()
+	myRes := e.GetNamedPermissionsForUser(ptype, name, domain...)
+	t.Log("Named permissions for ", name, ": ", myRes)
+
+	if !util.Array2DEquals(res, myRes) {
+		t.Error("Named permissions for ", name, ": ", myRes, ", supposed to be ", res)
+	}
+}
+
 func TestPermissionAPI(t *testing.T) {
 	e, _ := NewEnforcer("examples/basic_without_resources_model.conf", "examples/basic_without_resources_policy.csv")
 
@@ -259,6 +269,10 @@ func TestPermissionAPI(t *testing.T) {
 	testEnforceWithoutUsers(t, e, "alice", "write", false)
 	testEnforceWithoutUsers(t, e, "bob", "read", false)
 	testEnforceWithoutUsers(t, e, "bob", "write", false)
+
+	e, _ = NewEnforcer("examples/rbac_with_multiple_policy_model.conf", "examples/rbac_with_multiple_policy_policy.csv")
+	testGetNamedPermissionsForUser(t, e, "p", "user", [][]string{{"user", "/data", "GET"}})
+	testGetNamedPermissionsForUser(t, e, "p2", "user", [][]string{{"user", "view"}})
 }
 
 func testGetImplicitRoles(t *testing.T, e *Enforcer, name string, res []string) {
@@ -320,6 +334,16 @@ func testGetImplicitPermissionsWithDomain(t *testing.T, e *Enforcer, name string
 	}
 }
 
+func testGetNamedImplicitPermissions(t *testing.T, e *Enforcer, ptype string, name string, res [][]string) {
+	t.Helper()
+	myRes, _ := e.GetNamedImplicitPermissionsForUser(ptype, name)
+	t.Log("Named implicit permissions for ", name, ": ", myRes)
+
+	if !util.Set2DEquals(res, myRes) {
+		t.Error("Named implicit permissions for ", name, ": ", myRes, ", supposed to be ", res)
+	}
+}
+
 func TestImplicitPermissionAPI(t *testing.T) {
 	e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_with_hierarchy_policy.csv")
 
@@ -328,6 +352,11 @@ func TestImplicitPermissionAPI(t *testing.T) {
 
 	testGetImplicitPermissions(t, e, "alice", [][]string{{"alice", "data1", "read"}, {"data1_admin", "data1", "read"}, {"data1_admin", "data1", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
 	testGetImplicitPermissions(t, e, "bob", [][]string{{"bob", "data2", "write"}})
+
+	e, _ = NewEnforcer("examples/rbac_with_multiple_policy_model.conf", "examples/rbac_with_multiple_policy_policy.csv")
+
+	testGetNamedImplicitPermissions(t, e, "p", "alice", [][]string{{"user", "/data", "GET"}, {"admin", "/data", "POST"}})
+	testGetNamedImplicitPermissions(t, e, "p2", "alice", [][]string{{"user", "view"}, {"admin", "create"}})
 }
 
 func TestImplicitPermissionAPIWithDomain(t *testing.T) {