Repository: casbin/casbin

Issue:
Limitation of the GetImplicitUsersForResource()
**Want to prioritize this issue? Try:**

[![issuehunt-to-marktext](https://github.com/BoostIO/issuehunt-materials/raw/master/v1/issuehunt-button-v1.svg)](https://issuehunt.io/r/casbin/casbin)

------

**What's your scenario? What do you want to achieve?**
Your answer here

**Your model:**

```ini
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _
g2 = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
```

**Your policy:**

```
p, admin_group, admin_data, *
g, admin, admin_group
g2, app, admin_data
```

**Your request(s):**

```go
users, err := r.enforcer.GetImplicitUsersForResource("app")
```
Can not find user for resource for **app**. is there any other elegant way to get users？

Wrote my opinion in the comments below
```go
func (e *Enforcer) GetImplicitUsersForResource(resource string) ([][]string, error) {
	permissions := make([][]string, 0)
	subjectIndex, _ := e.GetFieldIndex("p", "sub")
	objectIndex, _ := e.GetFieldIndex("p", "obj")
	rm := e.GetRoleManager()

	isRole := make(map[string]bool)
	for _, role := range e.GetAllRoles() {
		isRole[role] = true
	}

	for _, rule := range e.model["p"]["p"].Policy {
		obj := rule[objectIndex]
		if obj != resource {  // The role of resource is not processed.
			continue
		}

		sub := rule[subjectIndex]

		if !isRole[sub] {
			permissions = append(permissions, rule)
		} else {
			users, err := rm.GetUsers(sub)
			if err != nil {
				return nil, err
			}

			for _, user := range users {
				implicitUserRule := deepCopyPolicy(rule)
				implicitUserRule[subjectIndex] = user
				permissions = append(permissions, implicitUserRule)
			}
		}
	}

	res := removeDuplicatePermissions(permissions)
	return res, nil
}

```



Discussion:
@casbin-bot:  @tangyang9464 @JalinWang@hsluoyz: @PokIsemaine @PokIsemaine: @JK-97 Thanks for your feedback, GetImplicitUsersForResource does not currently support matching obj and resource based on g type, and we will update it in a later release

Solution patch:
diff --git a/rbac_api.go b/rbac_api.go
index c1dd81f..3cf4b2d 100644
--- a/rbac_api.go
+++ b/rbac_api.go
@@ -533,6 +533,17 @@ func removeDuplicatePermissions(permissions [][]string) [][]string {
 // GetImplicitUsersForResource("data1") will return [[alice data1 read]]
 // Note: only users will be returned, roles (2nd arg in "g") will be excluded.
 func (e *Enforcer) GetImplicitUsersForResource(resource string) ([][]string, error) {
+	return e.GetNamedImplicitUsersForResource("g", resource)
+}
+
+// GetNamedImplicitUsersForResource return implicit user based on resource with named policy support.
+// This function handles resource role relationships through named policies (e.g., g2, g3, etc.).
+// for example:
+// p, admin_group, admin_data, *
+// g, admin, admin_group
+// g2, app, admin_data
+// GetNamedImplicitUsersForResource("g2", "app") will return users who have access to admin_data through g2 relationship.
+func (e *Enforcer) GetNamedImplicitUsersForResource(ptype string, resource string) ([][]string, error) {
 	permissions := make([][]string, 0)
 	subjectIndex, _ := e.GetFieldIndex("p", "sub")
 	objectIndex, _ := e.GetFieldIndex("p", "obj")
@@ -550,26 +561,35 @@ func (e *Enforcer) GetImplicitUsersForResource(resource string) ([][]string, err
 		isRole[role] = true
 	}
 
-	for _, rule := range e.model["p"]["p"].Policy {
-		obj := rule[objectIndex]
-		if obj != resource {
-			continue
+	// Get all resource types that the resource can access through ptype (e.g., g2)
+	ptypePolicies, _ := e.GetNamedGroupingPolicy(ptype)
+	resourceAccessibleResourceTypes := make(map[string]bool)
+
+	for _, ptypePolicy := range ptypePolicies {
+		if ptypePolicy[0] == resource { // ptypePolicy[0] is the resource
+			resourceAccessibleResourceTypes[ptypePolicy[1]] = true // ptypePolicy[1] is the resource type it can access
 		}
+	}
 
+	for _, rule := range e.model["p"]["p"].Policy {
+		obj := rule[objectIndex]
 		sub := rule[subjectIndex]
 
-		if !isRole[sub] {
-			permissions = append(permissions, rule)
-		} else {
-			users, err := rm.GetUsers(sub)
-			if err != nil {
-				return nil, err
-			}
+		// Check if this policy is directly for the resource OR for a resource type the resource can access
+		if obj == resource || resourceAccessibleResourceTypes[obj] {
+			if !isRole[sub] {
+				permissions = append(permissions, rule)
+			} else {
+				users, err := rm.GetUsers(sub)
+				if err != nil {
+					continue
+				}
 
-			for _, user := range users {
-				implicitUserRule := deepCopyPolicy(rule)
-				implicitUserRule[subjectIndex] = user
-				permissions = append(permissions, implicitUserRule)
+				for _, user := range users {
+					implicitUserRule := deepCopyPolicy(rule)
+					implicitUserRule[subjectIndex] = user
+					permissions = append(permissions, implicitUserRule)
+				}
 			}
 		}
 	}



Test patch:
diff --git a/rbac_api_test.go b/rbac_api_test.go
index c129d73..8ec1bf3 100644
--- a/rbac_api_test.go
+++ b/rbac_api_test.go
@@ -635,6 +635,43 @@ func TestGetImplicitUsersForResource(t *testing.T) {
 		{"alice", "data2", "write"}}, "data2")
 }
 
+func TestGetImplicitUsersForResourceWithResourceRoles(t *testing.T) {
+	e, _ := NewEnforcer("examples/rbac_with_resource_roles_model.conf", "examples/rbac_with_resource_roles_policy.csv")
+
+	// Test data1 resource - should return users who have access through g2 relationships
+	data1Users, err := e.GetNamedImplicitUsersForResource("g2", "data1")
+	if err != nil {
+		t.Fatalf("GetNamedImplicitUsersForResource failed: %v", err)
+	}
+
+	expectedData1Users := 2 // [alice data1 read] + [alice data_group write]
+	if len(data1Users) != expectedData1Users {
+		t.Errorf("Expected %d users for data1 resource, got %d: %v", expectedData1Users, len(data1Users), data1Users)
+	}
+
+	// Test data2 resource - should return users who have access through g2 relationships
+	data2Users, err := e.GetNamedImplicitUsersForResource("g2", "data2")
+	if err != nil {
+		t.Fatalf("GetNamedImplicitUsersForResource failed: %v", err)
+	}
+
+	expectedData2Users := 2 // [bob data2 write] + [alice data_group write]
+	if len(data2Users) != expectedData2Users {
+		t.Errorf("Expected %d users for data2 resource, got %d: %v", expectedData2Users, len(data2Users), data2Users)
+	}
+
+	// Test with "g" policy type - should return users who have access through g relationships
+	data1UsersG, err := e.GetNamedImplicitUsersForResource("g", "data1")
+	if err != nil {
+		t.Fatalf("GetNamedImplicitUsersForResource with g failed: %v", err)
+	}
+
+	expectedData1UsersG := 1 // [alice data1 read] only
+	if len(data1UsersG) != expectedData1UsersG {
+		t.Errorf("Expected %d users for data1 resource with g policy, got %d: %v", expectedData1UsersG, len(data1UsersG), data1UsersG)
+	}
+}
+
 func testGetImplicitUsersForResourceByDomain(t *testing.T, e *Enforcer, res [][]string, resource string, domain string) {
 	t.Helper()
 	myRes, err := e.GetImplicitUsersForResourceByDomain(resource, domain)