Repository: privacyidea/privacyidea

Issue:
machineresolverconfig table is not cleaned up
When a MachineResolver is deleted in the webui, the corresponding entries in the machineresolverconfig table are not removed, their resolver_id will be null'd, but the rows themselves are never removed.


Discussion:
We have the same problem with resolverconfig. And probably a lot of others.

I am wondering if this is due to this change: 60282189525d920cc1c1b4ba3f6fa81c9a12fef1
Where we removed "probably" double relationsships, but these might have been necessary for deleting objects and its *related* entries.
As I think this affects "all" parameter tables, I changed this to ``Prio:High``

Resolving patch:
diff --git a/privacyidea/models.py b/privacyidea/models.py
--- a/privacyidea/models.py
+++ b/privacyidea/models.py
@@ -838,12 +838,12 @@ def __init__(self, name, catype):
 
     def delete(self):
         ret = self.id
-        db.session.delete(self)
         # delete all CAConnectorConfig
-        # FIXME: Sometimes not all entries are deleted.
         db.session.query(CAConnectorConfig)\
                   .filter(CAConnectorConfig.caconnector_id == ret)\
                   .delete()
+        # Delete the CA itself
+        db.session.delete(self)
         db.session.commit()
         return ret
 
@@ -936,11 +936,12 @@ def __init__(self, name, rtype):
         
     def delete(self):
         ret = self.id
-        db.session.delete(self)
         # delete all ResolverConfig
         db.session.query(ResolverConfig)\
                   .filter(ResolverConfig.resolver_id == ret)\
                   .delete()
+        # delete the Resolver itself
+        db.session.delete(self)
         save_config_timestamp()
         db.session.commit()
         return ret
@@ -1766,7 +1767,6 @@ def save(self):
 
     def delete(self):
         ret = self.id
-        db.session.delete(self)
         # delete all EventHandlerOptions
         db.session.query(EventHandlerOption) \
             .filter(EventHandlerOption.eventhandler_id == ret) \
@@ -1775,6 +1775,8 @@ def delete(self):
         db.session.query(EventHandlerCondition) \
             .filter(EventHandlerCondition.eventhandler_id == ret) \
             .delete()
+        # delete the event handler itself
+        db.session.delete(self)
         save_config_timestamp()
         db.session.commit()
         return ret
@@ -1925,11 +1927,12 @@ def __init__(self, name, rtype):
 
     def delete(self):
         ret = self.id
-        db.session.delete(self)
         # delete all MachineResolverConfig
         db.session.query(MachineResolverConfig)\
                   .filter(MachineResolverConfig.resolver_id == ret)\
                   .delete()
+        # delete the MachineResolver itself
+        db.session.delete(self)
         db.session.commit()
         return ret
 
@@ -2107,11 +2110,12 @@ def delete(self):
         :return:
         """
         ret = self.id
-        db.session.delete(self)
         # delete all SMSGatewayOptions
         db.session.query(SMSGatewayOption)\
                   .filter(SMSGatewayOption.gateway_id == ret)\
                   .delete()
+        # delete the SMSGateway itself
+        db.session.delete(self)
         db.session.commit()
         return ret
 


Test patch:
diff --git a/tests/test_db_model.py b/tests/test_db_model.py
--- a/tests/test_db_model.py
+++ b/tests/test_db_model.py
@@ -940,3 +940,31 @@ def test_27_monitoring_stats(self):
         MonitoringStats.query.delete()
         self.assertEqual(MonitoringStats.query.filter_by(stats_key=key1).count(), 0)
         self.assertEqual(MonitoringStats.query.filter_by(stats_key=key2).count(), 0)
+
+
+class TokenModelTestCaseDeleting(MyTestCase):
+
+    def test_01_create_and_delete_resolver(self):
+        r = Resolver("try_delete", "passwdresolver")
+        rid = r.save()
+        self.assertTrue(r.name is not None, r.name)
+        self.assertTrue(r.rtype is not None, r.rtype)
+        # Add configuration to the resolver
+        conf = ResolverConfig(r.id, "fileName", "this_very_specific_file_try_delete")
+        conf.save()
+
+        # Read Resolver
+        r1 = Resolver.query.filter_by(name="try_delete").first()
+        self.assertTrue(r1.rtype == "passwdresolver", r1.rtype)
+        self.assertEqual(rid, r1.id)
+
+        # Get the option
+        rc = ResolverConfig.query.filter_by(Value="this_very_specific_file_try_delete").first()
+        self.assertTrue(rc)
+        self.assertEqual(rc.resolver_id, rid)
+
+        # Now delete the Resolver and check, if there is no config left
+        reso = Resolver.query.filter_by(name="try_delete").first()
+        reso.delete()
+        rc = ResolverConfig.query.filter_by(Value="this_very_specific_file_try_delete").first()
+        self.assertIsNone(rc)