Repository: privacyidea/privacyidea

Issue:
use Match.generic in get_webui_settings
In ``api/lib/postpolicy:get_webui_settings`` we use a lot of ``Match.realm``. This seems to have historic reasons, because at this point *after* ``/auth`` we know the logged in user, so we could also match based on logged_in_user, username and resolver. Maybe we can also use ``Match.admin_or_user``.

We should change this, since currently these webui settings are only evaluated based on the realm.

see: https://github.com/privacyidea/privacyidea/blob/master/privacyidea/api/lib/postpolicy.py#L486


Discussion:

Resolving patch:
diff --git a/privacyidea/api/lib/postpolicy.py b/privacyidea/api/lib/postpolicy.py
--- a/privacyidea/api/lib/postpolicy.py
+++ b/privacyidea/api/lib/postpolicy.py
@@ -484,20 +484,21 @@ def get_webui_settings(request, response):
     if content.get("result").get("status") is True:
         role = content.get("result").get("value").get("role")
         loginname = content.get("result").get("value").get("username")
-        realm = content.get("result").get("value").get("realm")
-        realm = realm or get_default_realm()
-
-        logout_time_pol = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.LOGOUTTIME,
-                                      realm=realm).action_values(unique=True)
-        timeout_action_pol = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.TIMEOUT_ACTION,
-                                         realm=realm).action_values(unique=True)
-        token_page_size_pol = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.TOKENPAGESIZE,
-                                          realm=realm).action_values(unique=True)
-        user_page_size_pol = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.USERPAGESIZE,
-                                         realm=realm).action_values(unique=True)
+        realm = content.get("result").get("value").get("realm") or get_default_realm()
+
+        # At this point the logged in user is not necessarily a user object. It can
+        # also be a local admin.
+        logout_time_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.LOGOUTTIME,
+                                        user=loginname, realm=realm).action_values(unique=True)
+        timeout_action_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.TIMEOUT_ACTION,
+                                           user=loginname, realm=realm).action_values(unique=True)
+        token_page_size_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.TOKENPAGESIZE,
+                                            user=loginname, realm=realm).action_values(unique=True)
+        user_page_size_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.USERPAGESIZE,
+                                           user=loginname, realm=realm).action_values(unique=True)
         token_wizard_2nd = (role == ROLE.USER and
-                            Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.TOKENWIZARD2ND,
-                                        realm=realm).policies())
+                            Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.TOKENWIZARD2ND,
+                                          user=loginname, realm=realm).policies())
         token_wizard = False
         dialog_no_token = False
         if role == ROLE.USER:
@@ -512,24 +513,24 @@ def get_webui_settings(request, response):
             dialog_no_token_pol = Match.user(g, scope=SCOPE.WEBUI, action=ACTION.DIALOG_NO_TOKEN,
                                              user_object=user_obj).any()
             dialog_no_token = dialog_no_token_pol and (user_token_num == 0)
-        user_details_pol = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.USERDETAILS,
-                                       realm=realm).policies()
-        search_on_enter = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.SEARCH_ON_ENTER,
-                                      realm=realm).policies()
-        hide_welcome = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_WELCOME,
-                                   realm=realm).any()
-        hide_buttons = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_BUTTONS,
-                                   realm=realm).any()
-        default_tokentype_pol = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.DEFAULT_TOKENTYPE,
-                                            realm=realm).action_values(unique=True)
-        show_seed = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_SEED,
-                                realm=realm).any()
-        qr_ios_authenticator = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_IOS_AUTHENTICATOR,
-                                           realm=realm).any()
-        qr_android_authenticator = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_ANDROID_AUTHENTICATOR,
-                                               realm=realm).any()
-        qr_custom_authenticator_url = Match.realm(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_CUSTOM_AUTHENTICATOR,
-                                                  realm=realm).action_values(unique=True)
+        user_details_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.USERDETAILS,
+                                         user=loginname, realm=realm).policies()
+        search_on_enter = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SEARCH_ON_ENTER,
+                                        user=loginname, realm=realm).policies()
+        hide_welcome = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_WELCOME,
+                                     user=loginname, realm=realm).any()
+        hide_buttons = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.HIDE_BUTTONS,
+                                     user=loginname, realm=realm).any()
+        default_tokentype_pol = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.DEFAULT_TOKENTYPE,
+                                              user=loginname, realm=realm).action_values(unique=True)
+        show_seed = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_SEED,
+                                  user=loginname, realm=realm).any()
+        qr_ios_authenticator = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_IOS_AUTHENTICATOR,
+                                             user=loginname, realm=realm).any()
+        qr_android_authenticator = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_ANDROID_AUTHENTICATOR,
+                                                 user=loginname, realm=realm).any()
+        qr_custom_authenticator_url = Match.generic(g, scope=SCOPE.WEBUI, action=ACTION.SHOW_CUSTOM_AUTHENTICATOR,
+                                                    user=loginname, realm=realm).action_values(unique=True)
 
         qr_image_android = create_img(DEFAULT_ANDROID_APP_URL) if qr_android_authenticator else None
         qr_image_ios = create_img(DEFAULT_IOS_APP_URL) if qr_ios_authenticator else None


Test patch:
diff --git a/tests/test_api_lib_policy.py b/tests/test_api_lib_policy.py
--- a/tests/test_api_lib_policy.py
+++ b/tests/test_api_lib_policy.py
@@ -3378,6 +3378,61 @@ def test_08_get_webui_settings(self):
         delete_policy("pol_qr2")
         delete_policy("pol_qr3")
 
+    def test_09_get_webui_settings(self):
+        # Test that policies like tokenpagesize are also user dependent
+        self.setUp_user_realms()
+
+        # The request with an OTP value and a PIN of a user, who has not
+        # token assigned
+        builder = EnvironBuilder(method='POST',
+                                 data={},
+                                 headers={})
+        env = builder.get_environ()
+        env["REMOTE_ADDR"] = "192.168.0.1"
+        g.client_ip = env["REMOTE_ADDR"]
+        req = Request(env)
+        req.all_data = {"user": "cornelius",
+                        "pass": "offline287082"}
+
+        res = {"jsonrpc": "2.0",
+               "result": {"status": True,
+                          "value": {"role": "user",
+                                    "username": "cornelius"}},
+               "version": "privacyIDEA test",
+               "id": 1}
+        resp = jsonify(res)
+
+        new_response = get_webui_settings(req, resp)
+        jresult = new_response.json
+        self.assertEqual(jresult.get("result").get("value").get(
+            "token_wizard"), False)
+
+        # Set a policy. User has not token, so "token_wizard" will be True
+        set_policy(name="pol_pagesize",
+                   scope=SCOPE.WEBUI,
+                   realm=self.realm1,
+                   action="{0!s}=177".format(ACTION.TOKENPAGESIZE))
+        g.policy_object = PolicyClass()
+        new_response = get_webui_settings(req, resp)
+        jresult = new_response.json
+        self.assertEqual(jresult.get("result").get("value").get(
+            ACTION.TOKENPAGESIZE), 177)
+
+        # Now we change the policy pol_pagesize this way, that it is only valid for the user "root"
+        set_policy(name="pol_pagesize",
+                   scope=SCOPE.WEBUI,
+                   realm=self.realm1,
+                   user="root",
+                   action="{0!s}=177".format(ACTION.TOKENPAGESIZE))
+        # This way the user "cornelius" gets the default pagesize again
+        g.policy_object = PolicyClass()
+        new_response = get_webui_settings(req, resp)
+        jresult = new_response.json
+        self.assertEqual(jresult.get("result").get("value").get(
+            ACTION.TOKENPAGESIZE), 15)
+
+        delete_policy("pol_pagesize")
+
     def test_16_init_token_defaults(self):
         g.logged_in_user = {"username": "cornelius",
                             "realm": "",