Repository: privacyidea/privacyidea

Issue:
Force PIN policy for Push token
Currently there are only policies to force pin on the smartphone for HOTP and TOTP. There should be one for Push too. The app would then request the PIN/biometrics before the user is able to confirm a push authentication.


Discussion:
Thank you:-)

Resolving patch:
diff --git a/privacyidea/lib/tokens/hotptoken.py b/privacyidea/lib/tokens/hotptoken.py
--- a/privacyidea/lib/tokens/hotptoken.py
+++ b/privacyidea/lib/tokens/hotptoken.py
@@ -247,7 +247,7 @@ def get_init_detail(self, params=None, user=None):
         imageurl = params.get("appimageurl")
         if imageurl:
             extra_data.update({"image": imageurl})
-        force_app_pin = params.get('force_app_pin')
+        force_app_pin = params.get(ACTION.FORCE_APP_PIN)
         if force_app_pin:
             extra_data.update({'pin': True})
         if otpkey:
diff --git a/privacyidea/lib/tokens/pushtoken.py b/privacyidea/lib/tokens/pushtoken.py
--- a/privacyidea/lib/tokens/pushtoken.py
+++ b/privacyidea/lib/tokens/pushtoken.py
@@ -335,6 +335,11 @@ def get_class_info(key=None, ret='all'):
                            'type': 'int',
                            'desc': _("The user may only have this maximum number of active Push tokens assigned."),
                            'group': GROUP.TOKEN
+                       },
+                       'push_' + ACTION.FORCE_APP_PIN: {
+                           'type': 'bool',
+                           'group': "PUSH",
+                           'desc': _('Require to unlock the Smartphone before Push requests can be accepted')
                        }
                    },
                    SCOPE.AUTH: {
@@ -483,6 +488,11 @@ def get_init_detail(self, params=None, user=None):
             extra_data["v"] = 1
             extra_data["serial"] = self.get_serial()
             extra_data["sslverify"] = sslverify
+
+            # enforce App pin
+            if params.get(ACTION.FORCE_APP_PIN):
+                extra_data.update({'pin': True})
+
             # We display this during the first enrollment step!
             qr_url = create_push_token_url(url=registration_url,
                                            user=user.login,
@@ -500,7 +510,7 @@ def get_init_detail(self, params=None, user=None):
 
             response_detail["enrollment_credential"] = self.get_tokeninfo("enrollment_credential")
 
-        elif self.token.rollout_state == "enrolled":
+        elif self.token.rollout_state == ROLLOUTSTATE.ENROLLED:
             # in the second enrollment step we return the public key of the server to the smartphone.
             pubkey = strip_key(self.get_tokeninfo(PUBLIC_KEY_SERVER))
             response_detail["public_key"] = pubkey


Test patch:
diff --git a/tests/test_lib_tokens_push.py b/tests/test_lib_tokens_push.py
--- a/tests/test_lib_tokens_push.py
+++ b/tests/test_lib_tokens_push.py
@@ -12,7 +12,7 @@
                                               DEFAULT_CHALLENGE_TEXT, strip_key,
                                               PUBLIC_KEY_SMARTPHONE, PRIVATE_KEY_SERVER,
                                               PUBLIC_KEY_SERVER,
-                                              PushAllowPolling, POLLING_ALLOWED)
+                                              PushAllowPolling, POLLING_ALLOWED, POLL_ONLY)
 from privacyidea.lib.smsprovider.FirebaseProvider import FIREBASE_CONFIG
 from privacyidea.lib.token import get_tokens, remove_token, init_token
 from privacyidea.lib.challenge import get_challenges
@@ -175,6 +175,7 @@ def test_01_create_token(self):
         self.assertEqual(detail.get("rollout_state"), "clientwait")
         enrollment_credential = detail.get("enrollment_credential")
         self.assertTrue("pushurl" in detail)
+        self.assertNotIn('pin=True', detail['pushurl']['value'])
         self.assertFalse("otpkey" in detail)
 
         # Run enrollment step 2
@@ -208,6 +209,16 @@ def test_01_create_token(self):
         self.assertEqual(parsed_server_pubkey.public_numbers(), parsed_stripped_server_pubkey.public_numbers())
         remove_token(self.serial1)
 
+    def test_01a_enroll_with_app_pin(self):
+        tparams = {'type': 'push', 'genkey': 1}
+        tparams.update(FB_CONFIG_VALS)
+        tok = init_token(param=tparams)
+        detail = tok.get_init_detail(params={PUSH_ACTION.FIREBASE_CONFIG: POLL_ONLY,
+                                             PUSH_ACTION.REGISTRATION_URL: "https://privacyidea.com/enroll",
+                                             ACTION.FORCE_APP_PIN: True})
+        self.assertIn('pin=True', detail['pushurl']['value'])
+        remove_token(tok.get_serial())
+
     def test_02a_lib_enroll(self):
         r = set_smsgateway(self.firebase_config_name,
                            u'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',