Repository: casbin/pycasbin

Issue:
Bringing pycasbin in track with casbin-core.
I am sure there must be some features or functionalities which are not in track with casbin-core, why should we not track those features and bring them here and implement it.

Comments:

Patch:
diff --git a/README.md b/README.md
index 13ab133..4e329fd 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ production-ready | production-ready | production-ready | production-ready
 [![python](https://casbin.org/img/langs/python.png)](https://github.com/casbin/pycasbin) | [![dotnet](https://casbin.org/img/langs/dotnet.png)](https://github.com/casbin-net/Casbin.NET) | [![delphi](https://casbin.org/img/langs/delphi.png)](https://github.com/casbin4d/Casbin4D) | [![rust](https://casbin.org/img/langs/rust.png)](https://github.com/casbin/casbin-rs)
 ----|----|----|----
 [PyCasbin](https://github.com/casbin/pycasbin) | [Casbin.NET](https://github.com/casbin-net/Casbin.NET) | [Casbin4D](https://github.com/casbin4d/Casbin4D) | [Casbin-RS](https://github.com/casbin/casbin-rs)
-production-ready | production-ready | experimental | WIP
+production-ready | production-ready | experimental | production-ready
 
 ## Table of contents
 
diff --git a/casbin/enforcer.py b/casbin/enforcer.py
index deec151..ee8d0e7 100644
--- a/casbin/enforcer.py
+++ b/casbin/enforcer.py
@@ -1,11 +1,9 @@
-import copy
 from casbin.management_enforcer import ManagementEnforcer
-from functools import reduce
-
+from casbin.util import join_slice, set_subtract
 
 class Enforcer(ManagementEnforcer):
     """
-        Enforcer = ManagementEnforcer + RBAC
+        Enforcer = ManagementEnforcer + RBAC_API + RBAC_WITH_DOMAIN_API
     """
 
     """creates an enforcer via file or DB.
@@ -17,142 +15,215 @@ class Enforcer(ManagementEnforcer):
             e = casbin.Enforcer("path/to/basic_model.conf", a)
     """
 
-    def get_roles_for_user(self, user):
-        """gets the roles that a user has."""
-        return self.model.model['g']['g'].rm.get_roles(user)
-
-    def get_roles_for_user_in_domain(self, name, domain):
-        """gets the roles that a user has inside a domain."""
-        return self.model.model['g']['g'].rm.get_roles(name, domain)
+    def get_roles_for_user(self, name):
+        """ gets the roles that a user has. """
+        return self.model.model["g"]["g"].rm.get_roles(name)
 
-    def get_users_for_role(self, role):
-        """gets the users that has a role."""
-        return self.model.model['g']['g'].rm.get_users(role)
+    def get_users_for_role(self, name):
+        """ gets the users that has a role. """
+        return self.model.model["g"]["g"].rm.get_users(name)
 
-    def get_users_for_role_in_domain(self, name, domain):
-        """gets the users that has a role inside a domain."""
-        return self.model.model['g']['g'].rm.get_users(name, domain)
+    def has_role_for_user(self, name, role):
+        """ determines whether a user has a role. """
+        roles = self.get_roles_for_user(name)
 
-    def has_role_for_user(self, user, role):
-        """determines whether a user has a role."""
-        roles = self.get_roles_for_user(user)
+        hasRole = False
+        for r in roles:
+            if r == role:
+                hasRole = True
+                break
 
-        return role in roles
+        return hasRole
 
     def add_role_for_user(self, user, role):
-        """adds a role for a user."""
-        """Returns false if the user already has the role (aka not affected)."""
+        """
+        adds a role for a user.
+        Returns false if the user already has the role (aka not affected).
+        """
         return self.add_grouping_policy(user, role)
 
-    def add_role_for_user_in_domain(self, user, role, domain):
-        """adds a role for a user inside a domain."""
-        """Returns false if the user already has the role (aka not affected)."""
-        return self.add_grouping_policy(user, role, domain)
-
     def delete_role_for_user(self, user, role):
-        """deletes a role for a user."""
-        """Returns false if the user does not have the role (aka not affected)."""
+        """
+        deletes a role for a user.
+        Returns false if the user does not have the role (aka not affected).
+        """
         return self.remove_grouping_policy(user, role)
 
     def delete_roles_for_user(self, user):
-        """deletes all roles for a user."""
-        """Returns false if the user does not have any roles (aka not affected)."""
+        """
+        deletes all roles for a user.
+        Returns false if the user does not have any roles (aka not affected).
+        """
         return self.remove_filtered_grouping_policy(0, user)
 
-    def delete_roles_for_user_in_domain(self, user, role, domain):
-        """deletes a role for a user inside a domain."""
-        """Returns false if the user does not have any roles (aka not affected)."""
-        return self.remove_filtered_grouping_policy(0, user, role, domain)
-
     def delete_user(self, user):
-        """deletes a user."""
-        """Returns false if the user does not exist (aka not affected)."""
-        return self.remove_filtered_grouping_policy(0, user)
+        """
+        deletes a user.
+        Returns false if the user does not exist (aka not affected).
+        """
+        res1 = self.remove_filtered_grouping_policy(0, user)
+
+        res2 = self.remove_filtered_policy(0, user)
+        return res1 or res2
 
     def delete_role(self, role):
-        """deletes a role."""
-        self.remove_filtered_grouping_policy(1, role)
-        self.remove_filtered_policy(0, role)
+        """
+        deletes a role.
+        Returns false if the role does not exist (aka not affected).
+        """
+        res1 = self.remove_filtered_grouping_policy(1, role)
+
+        res2 = self.remove_filtered_policy(0, role)
+        return res1 or res2
 
     def delete_permission(self, *permission):
-        """deletes a permission."""
-        """Returns false if the permission does not exist (aka not affected)."""
+        """
+        deletes a permission.
+        Returns false if the permission does not exist (aka not affected).
+        """
         return self.remove_filtered_policy(1, *permission)
 
     def add_permission_for_user(self, user, *permission):
-        """adds a permission for a user or role."""
-        """Returns false if the user or role already has the permission (aka not affected)."""
-        params = [user]
-        params.extend(permission)
-
-        return self.add_policy(*params)
+        """
+        adds a permission for a user or role.
+        Returns false if the user or role already has the permission (aka not affected).
+        """
+        return self.add_policy(join_slice(user, *permission))
 
     def delete_permission_for_user(self, user, *permission):
-        """adds a permission for a user or role."""
-        """Returns false if the user or role already has the permission (aka not affected)."""
-        params = [user]
-        params.extend(permission)
-
-        return self.remove_policy(*params)
+        """
+        deletes a permission for a user or role.
+        Returns false if the user or role does not have the permission (aka not affected).
+        """
+        return self.remove_policy(join_slice(user, *permission))
 
     def delete_permissions_for_user(self, user):
-        """deletes permissions for a user or role."""
-        """Returns false if the user or role does not have any permissions (aka not affected)."""
+        """
+        deletes permissions for a user or role.
+        Returns false if the user or role does not have any permissions (aka not affected).
+        """
         return self.remove_filtered_policy(0, user)
 
     def get_permissions_for_user(self, user):
-        """gets permissions for a user or role."""
+        """
+        gets permissions for a user or role.
+        """
         return self.get_filtered_policy(0, user)
 
-    def get_permissions_for_user_in_domain(self, user, domain):
-        """gets permissions for a user or role inside domain."""
-        return self.get_filtered_policy(0, user, domain)
-
     def has_permission_for_user(self, user, *permission):
-        """determines whether a user has a permission."""
-        params = [user]
-        params.extend(permission)
-
-        return self.has_policy(*params)
-
-    def get_implicit_roles_for_user(self, user, domain=None):
         """
-            get_implicit_roles_for_user gets implicit roles that a user has.
-            Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles.
-            For example:
-                g, alice, role:admin
-                g, role:admin, role:user
+        determines whether a user has a permission.
+        """
+        return self.has_policy(join_slice(user, *permission))
 
-                get_roles_for_user("alice") can only get: ["role:admin"].
-                But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"].
+    def get_implicit_roles_for_user(self, name, *domain):
         """
-        roles = self.get_roles_for_user_in_domain(user, domain) if domain else self.get_roles_for_user(user)
-        res = copy.copy(roles)
-        for r in roles:
-            _roles = self.get_roles_for_user_in_domain(r, domain) if domain else self.get_roles_for_user(r)
-            res.extend(_roles)
+        gets implicit roles that a user has.
+        Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles.
+        For example:
+        g, alice, role:admin
+        g, role:admin, role:user
+
+        get_roles_for_user("alice") can only get: ["role:admin"].
+        But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"].
+        """
+        res = list()
+        roleSet = dict()
+        roleSet[name] = True
+
+        q = list()
+        q.append(name)
+
+        while len(q) > 0:
+            name = q[0]
+            q = q[1:]
+
+            roles = self.rm.get_roles(name, *domain)
+            for r in roles:
+                if  r not in roleSet:
+                    res.append(r)
+                    q.append(r)
+                    roleSet[r] = True
+
         return res
 
-    def get_implicit_permissions_for_user(self, user, domain=None):
+    def get_implicit_permissions_for_user(self, user, *domain):
         """
-             gets implicit permissions for a user or role.
-            Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles.
-            For example:
-            p, admin, data1, read
-            p, alice, data2, read
-            g, alice, admin
-
-            get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]].
-            But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]].
+        gets implicit permissions for a user or role.
+        Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles.
+        For example:
+        p, admin, data1, read
+        p, alice, data2, read
+        g, alice, admin
+
+        get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]].
+        But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]].
         """
-        roles = self.get_implicit_roles_for_user(user, domain)
-        permissions = self.get_permissions_for_user_in_domain(user,
-                                                              domain) if domain else self.get_permissions_for_user(user)
+        roles = self.get_implicit_roles_for_user(user, *domain)
+
+        roles.insert(0, user)
+
+        withDomain = False
+        if len(domain) == 1:
+            withDomain = True
+        elif len(domain) > 1:
+            return None
+
+        res = []
+        permissions = [list()]
         for role in roles:
-            _permissions = self.get_permissions_for_user_in_domain(role,
-                                                                   domain) if domain else self.get_permissions_for_user(
-                role)
-            for item in _permissions:
-                if item not in permissions:
-                    permissions.append(item)
-        return permissions
+            if withDomain:
+                permissions = self.get_permissions_for_user_in_domain(role, domain[0])
+            else:
+                permissions = self.get_permissions_for_user(role)
+            res.extend(permissions)
+
+        return res
+
+    def get_implicit_users_for_permission(self, *permission):
+        """
+        gets implicit users for a permission.
+        For example:
+        p, admin, data1, read
+        p, bob, data1, read
+        g, alice, admin
+
+        get_implicit_users_for_permission("data1", "read") will get: ["alice", "bob"].
+        Note: only users will be returned, roles (2nd arg in "g") will be excluded.
+        """
+        subjects = self.get_all_subjects()
+        roles = self.get_all_roles()
+
+        users = set_subtract(subjects, roles)
+
+        res = list()
+        for user in users:
+            req = join_slice(user, *permission)
+            allowed = self.enforce(*req)
+
+            if allowed:
+                res.append(user)
+
+        return res
+
+    def get_roles_for_user_in_domain(self, name, domain):
+        """gets the roles that a user has inside a domain."""
+        return self.model.model['g']['g'].rm.get_roles(name, domain)
+
+    def get_users_for_role_in_domain(self, name, domain):
+        """gets the users that has a role inside a domain."""
+        return self.model.model['g']['g'].rm.get_users(name, domain)
+
+    def add_role_for_user_in_domain(self, user, role, domain):
+        """adds a role for a user inside a domain."""
+        """Returns false if the user already has the role (aka not affected)."""
+        return self.add_grouping_policy(user, role, domain)
+
+    def delete_roles_for_user_in_domain(self, user, role, domain):
+        """deletes a role for a user inside a domain."""
+        """Returns false if the user does not have any roles (aka not affected)."""
+        return self.remove_filtered_grouping_policy(0, user, role, domain)
+
+    def get_permissions_for_user_in_domain(self, user, domain):
+        """gets permissions for a user or role inside domain."""
+        return self.get_filtered_policy(0, user, domain)
\ No newline at end of file
diff --git a/casbin/util/util.py b/casbin/util/util.py
index eb001b2..96aefc5 100644
--- a/casbin/util/util.py
+++ b/casbin/util/util.py
@@ -35,3 +35,25 @@ def params_to_string(*s):
     """gets a printable string for variable number of parameters."""
 
     return ", ".join(s)
+
+def join_slice(a, *b):
+    ''' joins a string and a slice into a new slice.'''
+    res = [a]
+
+    res.extend(b)
+
+    return res
+
+def set_subtract(a, b):
+    ''' returns the elements in `a` that aren't in `b`. '''
+    mb = dict()
+
+    for x in b:
+        mb[x] = True
+
+    diff = list()
+    for x in a:
+        if x not in mb:
+            diff.append(x)
+
+    return diff


Test patch:
diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py
index 5a82984..90bdf9f 100644
--- a/tests/test_rbac_api.py
+++ b/tests/test_rbac_api.py
@@ -205,3 +205,11 @@ class TestRbacApi(TestCase):
         self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain2'), ['admin'])
         self.assertEqual(e.get_roles_for_user_in_domain('admin', 'domain2'), [])
         self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain2'), [])
+
+    def test_implicit_user_api(self):
+        e = get_enforcer(get_examples("rbac_model.conf"),get_examples("rbac_with_hierarchy_policy.csv"))
+
+        self.assertEqual(["alice"], e.get_implicit_users_for_permission("data1", "read"))
+        self.assertEqual(["alice"], e.get_implicit_users_for_permission("data1", "write"))
+        self.assertEqual(["alice"], e.get_implicit_users_for_permission("data2", "read"))
+        self.assertEqual(["alice", "bob"], e.get_implicit_users_for_permission("data2", "write"))
\ No newline at end of file