Repository: privacyidea/privacyidea

Issue:
UI rights do not check for user resolver filters
# What did you try to do?

Display UI rights correctly according to policies and their filters.
For example:
In the same realm realmA, there are 2 resolvers resolverX and resolverY. In policies setup, resolverX has assign action enabled, whereas resolverY does not have assign action enabled.

# What outcome did you expect?
When logging to the UI, users in resolverX should see assign option in the menu. Users in resolverY should not see assign option in the UI.

# What outcome did you experience?

Users in both resolvers of realmA see the assign option. Although when actually self-assigning a token, it will fail for user in resolverY.

# Configuration

* privacyIDEA Version: 2.23.3

* Installation method: from source


## more details: 

* OS: Amazon Linux

* Webserver: Nginx

* Tokendatabase: PostgreSQL


# Log file

N/A

# Side notes:

I checked this method and it seems that we do not pass the resolver info when checking for UI rights, and may result in the described outcome?

```
# privacyidea/privacyidea/lib/policy.py
def ui_get_rights(self, scope, realm, username, client=None):
[...]
```
This is not a very critical bug. However, it affects user experience and causes some confusion. It would be nice to have it adjusted!

Cheers,

Quynh Nguyen


Discussion:
Well, works as programmed! ;-)
Honestly there have been several ocasions, where we skipped the resolvers. Especially the login can be a bit tricky, since the user is not available before he authenticated.

But in this case you are right!
It happens here: https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/policy.py#L761

We should change it like this:

~~~~python
        if scope == SCOPE.ADMIN:
            adminrealm = realm
            logged_in_user["role"] = ROLE.ADMIN
            if adminrealm: # internal admins can not be resolved
                user = User(username, adminrealm)
                resolver = user.resolver
        elif scope == SCOPE.USER:
            userrealm = realm
            logged_in_user["role"] = ROLE.USER
            resolver = User(username, userrealm).resolver
~~~~
Then we can use the ``resolver`` in the get_policies.


Resolving patch:
diff --git a/privacyidea/lib/policy.py b/privacyidea/lib/policy.py
--- a/privacyidea/lib/policy.py
+++ b/privacyidea/lib/policy.py
@@ -750,6 +750,7 @@ def ui_get_rights(self, scope, realm, username, client=None):
         rights = set()
         userrealm = None
         adminrealm = None
+        resolver = None
         logged_in_user = {"username": username,
                           "realm": realm}
         if scope == SCOPE.ADMIN:
@@ -758,9 +759,11 @@ def ui_get_rights(self, scope, realm, username, client=None):
         elif scope == SCOPE.USER:
             userrealm = realm
             logged_in_user["role"] = ROLE.USER
+            resolver = User(username, userrealm).resolver
         pols = self.get_policies(scope=scope,
                                  adminrealm=adminrealm,
                                  realm=userrealm,
+                                 resolver=resolver,
                                  user=username, active=True,
                                  client=client)
         for pol in pols:


Test patch:
diff --git a/tests/test_lib_policy.py b/tests/test_lib_policy.py
--- a/tests/test_lib_policy.py
+++ b/tests/test_lib_policy.py
@@ -19,7 +19,9 @@
 from privacyidea.lib.error import ParameterError
 from privacyidea.lib.user import User
 import datetime
-PWFILE = "tests/testdata/passwords"
+from .base import PWFILE as FILE_PASSWORDS
+from .base import PWFILE2 as FILE_PASSWD
+
 
 
 def _check_policy_name(polname, policies):
@@ -538,6 +540,45 @@ def test_17_ui_get_rights(self):
         delete_policy("polAdminA")
         delete_policy("polAdminB")
 
+    def test_17b_ui_rights_users_in_different_resolvers(self):
+        # Create a realm with two resolvers
+
+        rid = save_resolver({"resolver": "passwd",
+                             "type": "passwdresolver",
+                             "fileName": FILE_PASSWD})
+        self.assertTrue(rid > 0, rid)
+
+        rid = save_resolver({"resolver": "passwords",
+                             "type": "passwdresolver",
+                             "fileName": FILE_PASSWORDS})
+        self.assertTrue(rid > 0, rid)
+
+        # create user realm
+        (added, failed) = set_realm("realm4",
+                                    ["passwd", "passwords"])
+        self.assertTrue(len(failed) == 0)
+        self.assertTrue(len(added) == 2)
+
+        # A user may do something else...
+        set_policy(name="userpol41", scope=SCOPE.USER, action="enable",
+                   realm="realm4", resolver="passwd")
+        set_policy(name="userpol42", scope=SCOPE.USER, action="remove",
+                   realm="realm4", resolver="passwords")
+        P = PolicyClass()
+
+        # The two users are in different resolvers and get different rights
+        rights = P.ui_get_rights(SCOPE.USER, "realm4", "postfix")
+        self.assertEqual(set(rights), {"enable", "disable"})
+
+        rights = P.ui_get_rights(SCOPE.USER, "realm4", "usernotoken")
+        self.assertEqual(set(rights), {"disable", "remove"})
+
+        delete_policy("userpol41")
+        delete_policy("userpol42")
+        delete_realm("realm4")
+        delete_resolver("passwords")
+        delete_resolver("passwd")
+
     def test_18_policy_with_time(self):
         set_policy(name="time1", scope=SCOPE.AUTHZ,
                    action="tokentype=hotp totp, enroll",
@@ -661,7 +702,7 @@ def test_21_check_all_resolver(self):
         for reso in ["reso1", "resoX", "resoA"]:
             rid = save_resolver({"resolver": reso,
                                  "type": "passwdresolver",
-                                 "fileName": PWFILE})
+                                 "fileName": FILE_PASSWORDS})
             self.assertTrue(rid > 0, rid)
 
         # create a realm with reso1 being the resolver with the highest priority