Repository: ckan/ckan

Issue:
Users are not removed in related tables (i.e. Institution users) if the main user entry is deleted
### CKAN Version if known (or site URL)

2.4
### Please describe the expected behaviour

An user assigned to different organisations / roles should be removed from each group / role if the user is deleted. If not, zombie user entries persist in the system.
### Please describe the actual behaviour

The users are not deleted from groups / roles if the main user entry is deleted.
### What steps can be taken to reproduce the issue?

Check the relations between main user tables and the related tables. In detail, is the relation a real relational one or not (is the delete flag set or not)?



Discussion:

Resolving patch:
diff --git a/ckan/lib/cli.py b/ckan/lib/cli.py
--- a/ckan/lib/cli.py
+++ b/ckan/lib/cli.py
@@ -861,12 +861,9 @@ def remove(self):
             return
         username = self.args[1]
 
-        user = model.User.by_name(unicode(username))
-        if not user:
-            print 'Error: user "%s" not found!' % username
-            return
-        user.delete()
-        model.repo.commit_and_remove()
+        p.toolkit.get_action('user_delete')(
+            {'model': model, 'ignore_auth': True},
+            {'id': username})
         print('Deleted user: %s' % username)
 
 
diff --git a/ckan/logic/action/delete.py b/ckan/logic/action/delete.py
--- a/ckan/logic/action/delete.py
+++ b/ckan/logic/action/delete.py
@@ -56,7 +56,7 @@ def user_delete(context, data_dict):
     user.delete()
 
     user_memberships = model.Session.query(model.Member).filter(
-        model.Member.table_id == user_id).all()
+        model.Member.table_id == user.id).all()
 
     for membership in user_memberships:
         membership.delete()


Test patch:
diff --git a/ckan/tests/logic/action/test_delete.py b/ckan/tests/logic/action/test_delete.py
--- a/ckan/tests/logic/action/test_delete.py
+++ b/ckan/tests/logic/action/test_delete.py
@@ -491,6 +491,74 @@ def test_bad_id_returns_404(self):
                       helpers.call_action, 'dataset_purge', id='123')
 
 
+class TestUserDelete(object):
+    def setup(self):
+        helpers.reset_db()
+
+    def test_user_delete(self):
+        user = factories.User()
+        context = {}
+        params = {u'id': user[u'id']}
+
+        helpers.call_action(u'user_delete', context, **params)
+
+        # It is still there but with state=deleted
+        user_obj = model.User.get(user[u'id'])
+        assert_equals(user_obj.state, u'deleted')
+
+    def test_user_delete_removes_memberships(self):
+        user = factories.User()
+        factories.Organization(
+            users=[{u'name': user[u'id'], u'capacity': u'admin'}])
+
+        factories.Group(
+            users=[{u'name': user[u'id'], u'capacity': u'admin'}])
+
+        user_memberships = model.Session.query(model.Member).filter(
+            model.Member.table_id == user[u'id']).all()
+
+        assert_equals(len(user_memberships), 2)
+
+        assert_equals([m.state for m in user_memberships],
+                      [u'active', u'active'])
+
+        context = {}
+        params = {u'id': user[u'id']}
+
+        helpers.call_action(u'user_delete', context, **params)
+
+        user_memberships = model.Session.query(model.Member).filter(
+            model.Member.table_id == user[u'id']).all()
+
+        # Member objects are still there, but flagged as deleted
+        assert_equals(len(user_memberships), 2)
+
+        assert_equals([m.state for m in user_memberships],
+                      [u'deleted', u'deleted'])
+
+    def test_user_delete_removes_memberships_when_using_name(self):
+        user = factories.User()
+        factories.Organization(
+            users=[{u'name': user[u'id'], u'capacity': u'admin'}])
+
+        factories.Group(
+            users=[{u'name': user[u'id'], u'capacity': u'admin'}])
+
+        context = {}
+        params = {u'id': user[u'name']}
+
+        helpers.call_action(u'user_delete', context, **params)
+
+        user_memberships = model.Session.query(model.Member).filter(
+            model.Member.table_id == user[u'id']).all()
+
+        # Member objects are still there, but flagged as deleted
+        assert_equals(len(user_memberships), 2)
+
+        assert_equals([m.state for m in user_memberships],
+                      [u'deleted', u'deleted'])
+
+
 class TestJobClear(helpers.FunctionalRQTestBase):
 
     def test_all_queues(self):