Repository: casbin/casbin
Pull request #798 (2021-05-20 13:50:39)

feat: add method GetImplicitResourcesForUser
Signed-off-by: closetool <4closetool3@gmail.com>

Fix: #796 

closetool: @hsluoyz This method just loop rmMap ,that might not right https://github.com/casbin/casbin/blob/5bdbb063721697233f9128e459a68399888f7bcb/rbac_api.go#L188
hsluoyz: @closetool we don't need new function. We just need to support g2 in GetImplicitPermissionsForUser().
closetool: @hsluoyz Supporting `g2` could not satisfy customer's requirement 
https://github.com/casbin/jcasbin/issues/174#issuecomment-822528302
maybe I'll make a pr later to support `g2` in `GetPermissionsForUser`
hsluoyz: @closetool rename `GetImplicitPoliciesForUser()` to `GetImplicitResourcesForUser()`. Also plz add some tests.
closetool: Fixed @hsluoyz 
hsluoyz: @closetool commit and PR name

Patch:
diff --git a/enforcer.go b/enforcer.go
index 4a9ca6b..558f47a 100644
--- a/enforcer.go
+++ b/enforcer.go
@@ -271,34 +271,21 @@ func (e *Enforcer) ClearPolicy() {
 
 // LoadPolicy reloads the policy from file/database.
 func (e *Enforcer) LoadPolicy() error {
-	oldModel := e.model
-	e.model = model.NewModel()
-	e.model.SetLogger(oldModel.GetLogger())
-
-	var err error
-	defer func() {
-		if err != nil {
-			e.model = oldModel
-		}
-	}()
-
-	if err = e.model.LoadModelFromText(oldModel.ToText()); err != nil {
-		return err
-	}
-	if err = e.adapter.LoadPolicy(e.model); err != nil && err.Error() != "invalid file path, file path cannot be empty" {
+	e.model.ClearPolicy()
+	if err := e.adapter.LoadPolicy(e.model); err != nil && err.Error() != "invalid file path, file path cannot be empty" {
 		return err
 	}
 
-	if err = e.model.SortPoliciesByPriority(); err != nil {
+	if err := e.model.SortPoliciesByPriority(); err != nil {
 		return err
 	}
 
-	if err = e.clearRmMap(); err != nil {
+	if err := e.clearRmMap(); err != nil {
 		return err
 	}
 
 	if e.autoBuildRoleLinks {
-		err = e.BuildRoleLinks()
+		err := e.BuildRoleLinks()
 		if err != nil {
 			return err
 		}
diff --git a/rbac/default-role-manager/role_manager.go b/rbac/default-role-manager/role_manager.go
index 9c47547..c8bdde1 100644
--- a/rbac/default-role-manager/role_manager.go
+++ b/rbac/default-role-manager/role_manager.go
@@ -268,7 +268,7 @@ func (rm *RoleManager) GetUsers(name string, domain ...string) ([]string, error)
 		var names []string
 		for _, domain := range patternDomain {
 			nameWithDomain := getNameWithDomain(domain, name)
-			if !rm.roles.hasRole(domain, name, rm.matchingFunc) {
+			if !rm.roles.hasRole(domain, name, rm.domainMatchingFunc) {
 				return nil, errors.ERR_NAME_NOT_FOUND
 			}
 
diff --git a/rbac_api.go b/rbac_api.go
index 8d48b81..b32d9ff 100644
--- a/rbac_api.go
+++ b/rbac_api.go
@@ -15,6 +15,8 @@
 package casbin
 
 import (
+	"fmt"
+
 	"github.com/casbin/casbin/v2/errors"
 	"github.com/casbin/casbin/v2/util"
 )
@@ -146,11 +148,10 @@ func (e *Enforcer) GetPermissionsForUser(user string, domain ...string) [][]stri
 	for ptype, assertion := range e.model["p"] {
 		args := make([]string, len(assertion.Tokens))
 		args[0] = user
-
-		if len(domain) > 0 {
-			index := e.getDomainIndex(ptype)
-			if index < len(assertion.Tokens) {
-				args[index] = domain[0]
+		for i, token := range assertion.Tokens {
+			if token == fmt.Sprintf("%s_dom", ptype) {
+				args[i] = domain[0]
+				break
 			}
 		}
 		perm := e.GetFilteredPolicy(0, args...)
@@ -202,6 +203,37 @@ func (e *Enforcer) GetImplicitRolesForUser(name string, domain ...string) ([]str
 	return res, nil
 }
 
+// GetImplicitUsersForRole gets implicit users for a role.
+func (e *Enforcer) GetImplicitUsersForRole(name string, domain ...string) ([]string, error) {
+	res := []string{}
+	roleSet := make(map[string]bool)
+	roleSet[name] = true
+
+	q := make([]string, 0)
+	q = append(q, name)
+
+	for len(q) > 0 {
+		name := q[0]
+		q = q[1:]
+
+		for _, rm := range e.rmMap {
+			roles, err := rm.GetUsers(name, domain...)
+			if err != nil && err.Error() != "error: name does not exist" {
+				return nil, err
+			}
+			for _, r := range roles {
+				if _, ok := roleSet[r]; !ok {
+					res = append(res, r)
+					q = append(q, r)
+					roleSet[r] = true
+				}
+			}
+		}
+	}
+
+	return res, nil
+}
+
 // GetImplicitPermissionsForUser gets implicit permissions for a user or role.
 // Compared to GetPermissionsForUser(), this function retrieves permissions for inherited roles.
 // For example:
@@ -276,3 +308,42 @@ func (e *Enforcer) GetDomainsForUser(user string) ([]string, error) {
 	}
 	return domains, nil
 }
+
+// GetImplicitResourcesForUser returns all policies that user obtaining in domain
+func (e *Enforcer) GetImplicitResourcesForUser(user string, domain ...string) ([][]string, error) {
+	permissions, err := e.GetImplicitPermissionsForUser(user, domain...)
+	if err != nil {
+		return nil, err
+	}
+	res := make([][]string, 0)
+	for _, permission := range permissions {
+		if permission[0] == user {
+			res = append(res, permission)
+			continue
+		}
+		resLocal := [][]string{{user}}
+		tokensLength := len(permission)
+		t := make([][]string, 1, tokensLength)
+		for _, token := range permission[1:] {
+			tokens, err := e.GetImplicitUsersForRole(token, domain...)
+			if err != nil {
+				return nil, err
+			}
+			tokens = append(tokens, token)
+			t = append(t, tokens)
+		}
+		for i := 1; i < tokensLength; i++ {
+			n := make([][]string, 0)
+			for _, tokens := range t[i] {
+				for _, policy := range resLocal {
+					t := append([]string(nil), policy...)
+					t = append(t, tokens)
+					n = append(n, t)
+				}
+			}
+			resLocal = n
+		}
+		res = append(res, resLocal...)
+	}
+	return res, nil
+}


Test patch:
diff --git a/rbac_api_test.go b/rbac_api_test.go
index c438b9a..adeb9bf 100644
--- a/rbac_api_test.go
+++ b/rbac_api_test.go
@@ -355,3 +355,73 @@ func TestImplicitUserAPI(t *testing.T) {
 	_, _ = e.AddGroupingPolicy("alice", "admin")
 	testGetImplicitUsers(t, e, []string{"alice", "bob"}, "data1", "read")
 }
+
+func testGetImplicitResourcesForUser(t *testing.T, e *Enforcer, res [][]string, user string, domain ...string) {
+	t.Helper()
+	myRes, _ := e.GetImplicitResourcesForUser(user, domain...)
+	t.Log("Implicit resources for user: ", user, ": ", myRes)
+
+	lessFunc := func(arr [][]string) func(int, int) bool {
+		return func(i, j int) bool {
+			policy1, policy2 := arr[i], arr[j]
+			for k := range policy1 {
+				if policy1[k] == policy2[k] {
+					continue
+				}
+				return policy1[k] < policy2[k]
+			}
+			return true
+		}
+	}
+
+	sort.Slice(res, lessFunc(res))
+	sort.Slice(myRes, lessFunc(myRes))
+
+	if !util.Array2DEquals(res, myRes) {
+		t.Error("Implicit resources for user: ", user, ": ", myRes, ", supposed to be ", res)
+	}
+}
+
+func TestGetImplicitResourcesForUser(t *testing.T) {
+	e, _ := NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
+	testGetImplicitResourcesForUser(t, e, [][]string{
+		{"alice", "/pen/1", "GET"},
+		{"alice", "/pen2/1", "GET"},
+		{"alice", "/book/:id", "GET"},
+		{"alice", "/book2/{id}", "GET"},
+		{"alice", "/book/*", "GET"},
+		{"alice", "book_group", "GET"},
+	}, "alice")
+	testGetImplicitResourcesForUser(t, e, [][]string{
+		{"bob", "pen_group", "GET"},
+		{"bob", "/pen/:id", "GET"},
+		{"bob", "/pen2/{id}", "GET"},
+	}, "bob")
+	testGetImplicitResourcesForUser(t, e, [][]string{
+		{"cathy", "pen_group", "GET"},
+		{"cathy", "/pen/:id", "GET"},
+		{"cathy", "/pen2/{id}", "GET"},
+	}, "cathy")
+}
+
+func TestImplicitUsersForRole(t *testing.T) {
+	e, _ := NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
+
+	testGetImplicitUsersForRole(t, e, "book_admin", []string{"alice"})
+	testGetImplicitUsersForRole(t, e, "pen_admin", []string{"cathy", "bob"})
+
+	testGetImplicitUsersForRole(t, e, "book_group", []string{"/book/*", "/book/:id", "/book2/{id}"})
+	testGetImplicitUsersForRole(t, e, "pen_group", []string{"/pen/:id", "/pen2/{id}"})
+}
+
+func testGetImplicitUsersForRole(t *testing.T, e *Enforcer, name string, res []string) {
+	t.Helper()
+	myRes, _ := e.GetImplicitUsersForRole(name)
+	t.Log("Implicit users for ", name, ": ", myRes)
+	sort.Strings(res)
+	sort.Strings(myRes)
+
+	if !util.ArrayEquals(res, myRes) {
+		t.Error("Implicit users for ", name, ": ", myRes, ", supposed to be ", res)
+	}
+}