Repository: privacyidea/privacyidea

Issue:
Verify enrollment does not work with indexed secret token
customer care: 4114

Like in #3809 the indexed secret is created new when using the "verify enrollment" policy.
Enforcing a user-attribute as the secret with the user policy `indexedsecret_force_attribute` should prevent this. This won't work for user-provided secrets.

We should add integration tests for all tokens with `can_verify_enrollment = True` if the verification **and** a following `/validate/check` succeeds.

The specific observation is, that the indexed secret token that is created via verify enroll has a different length than the requested positions: Like: During enrollment the user enters 10 characters but the length is 40.

Could this be sth with the hexlify and encoding?



Discussion:
The test is performed here:

https://github.com/privacyidea/privacyidea/blob/2dc453f24e12ef6d8d3264bc8bc465aee0ba7028/tests/test_api_token.py#L2732

This is strange, it looks like the otplen is set wrong or is not set at all. The `lib.tokens.indexedsecrettoken.py` does not ever set the otplen. This obviously did not cause any issue, since the indexedsecret was always used as a C/R token anyway.
The problem is, that in the 2nd step of the verify enrollment a call to /token/init is made with the parameters: "verify" but not with the parameters "otpkey" and no "genkey". 

Thus we trigger this condition:
https://github.com/privacyidea/privacyidea/blob/662774047d37fdfe335fcf523892eff11ca5f881/privacyidea/lib/tokens/indexedsecrettoken.py#L173




Resolving patch:
diff --git a/privacyidea/lib/tokens/indexedsecrettoken.py b/privacyidea/lib/tokens/indexedsecrettoken.py
--- a/privacyidea/lib/tokens/indexedsecrettoken.py
+++ b/privacyidea/lib/tokens/indexedsecrettoken.py
@@ -170,10 +170,11 @@ def update(self, param, reset_failcount=True):
         :return: nothing
 
         """
-        if 'genkey' not in param and 'otpkey' not in param:
+        if 'verify' not in param and 'genkey' not in param and 'otpkey' not in param:
             param['genkey'] = 1
 
         TokenClass.update(self, param, reset_failcount)
+
         return
 
     @log_with(log)
@@ -291,7 +292,7 @@ def check_challenge_response(self, user=None, passw=None, options=None):
                             # increase the received_count
                             challengeobject.set_otp_status()
                     else:
-                        log.debug("Length of password does not match the requested number of positions.")
+                        log.info("Length of password does not match the requested number of positions.")
                         # increase the received_count
                         challengeobject.set_otp_status()
 


Test patch:
diff --git a/tests/test_api_token.py b/tests/test_api_token.py
--- a/tests/test_api_token.py
+++ b/tests/test_api_token.py
@@ -2746,13 +2746,15 @@ def test_43_init_verify_index_token(self):
             self.assertTrue(result.get("status"))
             self.assertTrue(result.get("value"))
             self.assertEqual(SECRET, secret)
-            self.assertEqual(detail.get("rollout_state"), ROLLOUTSTATE.VERIFYPENDING)
+            self.assertEqual(ROLLOUTSTATE.VERIFYPENDING, detail.get("rollout_state"))
             message = detail.get("verify").get("message")
             self.assertTrue(message.startswith("Please enter the positions"))
             serial = detail.get("serial")
             tokenobj_list = get_tokens(serial=serial)
             # Check the token rollout state
-            self.assertEqual(tokenobj_list[0].token.rollout_state, ROLLOUTSTATE.VERIFYPENDING)
+            self.assertEqual(ROLLOUTSTATE.VERIFYPENDING, tokenobj_list[0].token.rollout_state)
+            # Check the default otplen
+            self.assertEqual(6, tokenobj_list[0].token.otplen)
             s_pos = message.strip("Please enter the positions ").strip(" from your secret.")
             positions = [int(x) for x in s_pos.split(",")]
 
@@ -2775,6 +2777,8 @@ def test_43_init_verify_index_token(self):
             tokenobj_list = get_tokens(serial=serial)
             # Check the token rollout state, it is empty now.
             self.assertEqual(ROLLOUTSTATE.ENROLLED, tokenobj_list[0].token.rollout_state)
+            # Check the default otplen of the token
+            self.assertEqual(6, tokenobj_list[0].token.otplen)
 
         delete_policy("verify_toks1")