Repository: privacyidea/privacyidea

Issue:
Remove tokenowner entry when a registration token is used.
If a user is using his registration token, the token is deleted but an entry in the `tokenowner` table remains with the `token_id=NULL`.

When automatically deleting the registration token we also need to remove the owner entry.

# Implementation

We need to clean up the token-deletion.

The deletion of all references (tokenowner, tokenrealm, challenges) is put in the DB token model delete method and the delete token calls are cleaned up.
This way we implicitly achieve, that tokenowners are removed, when the registration token is deleted.


Discussion:

Resolving patch:
diff --git a/privacyidea/api/register.py b/privacyidea/api/register.py
--- a/privacyidea/api/register.py
+++ b/privacyidea/api/register.py
@@ -168,7 +168,7 @@ def register_post():
     if not email_sent:
         log.warning("Failed to send registration email to {0!r}".format(email))
         # delete registration token
-        token.delete()
+        token.delete_token()
         # delete user
         user.delete()
         raise RegistrationError("Failed to send email!")
diff --git a/privacyidea/lib/token.py b/privacyidea/lib/token.py
--- a/privacyidea/lib/token.py
+++ b/privacyidea/lib/token.py
@@ -1130,24 +1130,7 @@ def remove_token(serial=None, user=None):
 
     # Delete challenges of such a token
     for tokenobject in tokenobject_list:
-        # delete the challenge
-        Challenge.query.filter(Challenge.serial == tokenobject.get_serial(
-
-        )).delete()
-
-        # due to legacy SQLAlchemy it could happen that the
-        # foreign key relation could not be deleted
-        # so we do this manualy
-
-        # delete references to client machines
-        MachineToken.query.filter(MachineToken.token_id ==
-                                  tokenobject.token.id).delete()
-        TokenRealm.query.filter(TokenRealm.token_id ==
-                                tokenobject.token.id).delete()
-        TokenOwner.query.filter(TokenOwner.token_id ==
-                                tokenobject.token.id).delete()
-
-        tokenobject.token.delete()
+        tokenobject.delete_token()
 
     return token_count
 
diff --git a/privacyidea/models.py b/privacyidea/models.py
--- a/privacyidea/models.py
+++ b/privacyidea/models.py
@@ -260,6 +260,15 @@ def delete(self):
         db.session.query(TokenRealm)\
                   .filter(TokenRealm.token_id == self.id)\
                   .delete()
+        db.session.query(TokenOwner)\
+                  .filter(TokenOwner.token_id == self.id)\
+                  .delete()
+        db.session.query(MachineToken)\
+                  .filter(MachineToken.token_id == self.id)\
+                  .delete()
+        db.session.query(Challenge)\
+                  .filter(Challenge.serial == self.serial)\
+                  .delete()
         db.session.query(TokenInfo)\
                   .filter(TokenInfo.token_id == self.id)\
                   .delete()


Test patch:
diff --git a/tests/test_api_validate.py b/tests/test_api_validate.py
--- a/tests/test_api_validate.py
+++ b/tests/test_api_validate.py
@@ -7,7 +7,7 @@
 from privacyidea.lib.tokens.totptoken import HotpTokenClass
 from privacyidea.lib.tokens.registrationtoken import RegistrationTokenClass
 from privacyidea.lib.tokenclass import DATE_FORMAT
-from privacyidea.models import (Token, Policy, Challenge, AuthCache, db)
+from privacyidea.models import (Token, Policy, Challenge, AuthCache, db, TokenOwner)
 from privacyidea.lib.authcache import _hash_password
 from privacyidea.lib.config import (set_privacyidea_config,
                                     get_inc_fail_count_on_false_pin,
@@ -1164,7 +1164,7 @@ def test_11a_challenge_response_registration(self):
         serial = "CHALRESP1"
         pin = "chalresp1"
         # create a token and assign to the user
-        db_token = Token(serial, tokentype="registration")
+        db_token = Token(serial, tokentype="registration", otpkey="regcode")
         db_token.save()
         token = RegistrationTokenClass(db_token)
         token.add_user(User("cornelius", self.realm1))
@@ -1211,8 +1211,22 @@ def test_11a_challenge_response_registration(self):
             self.assertEqual(detail.get("message"), _("please enter otp: "))
             transaction_id = detail.get("transaction_id")
 
-        # delete the token
-        remove_token(serial=serial)
+        # use the regcode to authenticate
+        with self.app.test_request_context('/validate/check',
+                                           method='POST',
+                                           data={"user": "cornelius",
+                                                 "transaction_id": transaction_id,
+                                                 "pass": "regcode"}):
+            res = self.app.full_dispatch_request()
+            self.assertTrue(res.status_code == 200, res)
+            result = res.json.get("result")
+            self.assertTrue(result.get("value"))
+
+        # check, that the tokenowner table does not contain a NULL entry
+        r = db.session.query(TokenOwner).filter(TokenOwner.token_id == None).first()
+        self.assertIsNone(r)
+
+        # delete the policy
         delete_policy("pol_chal_resp")
 
     def test_11b_challenge_response_multiple_hotp_failcounters(self):