Repository: privacyidea/privacyidea

Issue:
registration token is not deleted?
In the current development master branch for 3.4 it looks like as if the registration token is not deleted anymore after a successful authentication.

It could be, that the multi challenge implementation changed s.th.

Anyways, we should have a test with /validate/check of the registration token which then actually checks if the token is removed.
(I thought we had one)

Please verify.


Discussion:
This only happens when in the system config the "do not use authcounter per token" is checked.

The token is deleted here https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/tokens/registrationtoken.py#L177 in ``inc_count_auth_success``.

This is called in the ``check_token_list`` here, https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/token.py#L2208

But only id "do nit use authcounter per token" is set, 
which is read from the [system config](https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/token.py#L2107) 

I am not sure, yet, how we would want to solve this.
Maybe we even create a *post success auth method* per token type, because there could be other tokens, that do sth. after a successful authentication and delete the reg token in there, like:

~~~~python
            if token_obj.check_all(message_list):
                if increase_auth_counters:
                    token_obj.inc_count_auth_success()

                # The token is active and the auth counters are ok.
                res = True
                if not reply_dict.get("type"):
                    reply_dict["type"] = token_obj.token.tokentype
                if reply_dict["type"] != token_obj.token.tokentype:
                    reply_dict["type"] = "undetermined"
                # reset the failcounter of valid token
                try:
                token_obj.reset()
                token_obj.post_success()  # This line added, where the reg token deletes itself and other tokens do other stuff.
~~~~

Imho this needs a bit of thinking and I would like to move this to 3.4.1 or 3.5.

## Mitigation

An admin can still circumvent this issue, by creating an event handler at ``validate_check``  with a true result_value and a token type "registration", that deletes the token.



Resolving patch:
diff --git a/privacyidea/lib/token.py b/privacyidea/lib/token.py
--- a/privacyidea/lib/token.py
+++ b/privacyidea/lib/token.py
@@ -2215,13 +2215,9 @@ def check_token_list(tokenobject_list, passw, user=None, options=None, allow_res
                 if reply_dict["type"] != token_obj.token.tokentype:
                     reply_dict["type"] = "undetermined"
                 # reset the failcounter of valid token
-                try:
-                    token_obj.reset()
-                except Exception as _e:
-                    # In some cases (Registration Token) the token does not
-                    # exist anymore. So this would bail an exception!
-                    log.debug("registration token does not exist anymore and "
-                              "cannot be reset.")
+                token_obj.reset()
+                # Run the token post method. e.g. registration token deletes itself.
+                token_obj.post_success()
         if len(valid_token_list) == 1:
             # If only one token was found, we add the serial number,
             # the token type and the OTP length
@@ -2284,6 +2280,7 @@ def check_token_list(tokenobject_list, passw, user=None, options=None, allow_res
                         # This was the last successful challenge, so
                         # reset the fail counter of the challenge response token
                         tokenobject.reset()
+                        tokenobject.post_success()
 
                     # clean up all challenges from this and other tokens. I.e.
                     # all challenges with this very transaction_id!
diff --git a/privacyidea/lib/tokenclass.py b/privacyidea/lib/tokenclass.py
--- a/privacyidea/lib/tokenclass.py
+++ b/privacyidea/lib/tokenclass.py
@@ -1054,6 +1054,13 @@ def inc_count_auth_success(self):
                              "count_auth": int(auth_counter)})
         return succcess_counter
 
+    @check_token_locked
+    def post_success(self):
+        """
+        Run anything after a token was used for successful authentication
+        """
+        return
+
     @check_token_locked
     def inc_count_auth(self):
         """
diff --git a/privacyidea/lib/tokens/registrationtoken.py b/privacyidea/lib/tokens/registrationtoken.py
--- a/privacyidea/lib/tokens/registrationtoken.py
+++ b/privacyidea/lib/tokens/registrationtoken.py
@@ -169,13 +169,11 @@ def update(self, param):
 
     @log_with(log, log_entry=False)
     @check_token_locked
-    def inc_count_auth_success(self):
+    def post_success(self):
         """
-        Increase the counter, that counts successful authentications
-        In case of successful authentication the token does needs to be deleted.
+        Delete the registration token after successful authentication
         """
         self.delete_token()
-        return 1
 
     @log_with(log)
     def get_init_detail(self, params=None, user=None):


Test patch:
diff --git a/tests/test_lib_token.py b/tests/test_lib_token.py
--- a/tests/test_lib_token.py
+++ b/tests/test_lib_token.py
@@ -1527,6 +1527,22 @@ def test_0057_check_invalid_serial(self):
                           {"serial": "invalid/chars",
                            "genkey": 1})
 
+    def test_57_registration_token_no_auth_counter(self):
+        # Test, that a registration token is deleted even if no_auth_counter is used.
+        from privacyidea.lib.config import set_privacyidea_config, delete_privacyidea_config
+        set_privacyidea_config("no_auth_counter", 1)
+        tok = init_token({"type": "registration"})
+        serial = tok.token.serial
+        detail = tok.get_init_detail()
+        reg_password = detail.get("registrationcode")
+        r, rdetail = check_token_list([tok], reg_password)
+        self.assertTrue(r)
+        self.assertEqual(rdetail["message"], "matching 1 tokens")
+        delete_privacyidea_config("no_auth_counter")
+        # Check that the token is deleted
+        toks = get_tokens(serial=serial)
+        self.assertEqual(len(toks), 0)
+
 
 class TokenOutOfBandTestCase(MyTestCase):
 
diff --git a/tests/test_lib_tokens_registration.py b/tests/test_lib_tokens_registration.py
--- a/tests/test_lib_tokens_registration.py
+++ b/tests/test_lib_tokens_registration.py
@@ -51,8 +51,8 @@ def test_03_check_password(self):
         db_token = Token.query.filter(Token.serial == self.serial1).first()
         self.assertNotEqual(db_token, None)
 
-        # check if the token is deleted after inc_success
-        token.inc_count_auth_success()
+        # check if the token is deleted after post_success
+        token.post_success()
         db_token = Token.query.filter(Token.serial == self.serial1).first()
         self.assertEqual(db_token, None)