Deleted user not removed from group

Hi, I have a problem when I add a user to a group, then delete that user from the control panel and then recreate it, the user pops up in the group (see test below). I see in test_portalmembership that deleting a user is non trivial and although I remove the member from membership and I delete it, it still appears in the group. Is this a bug?

    def testDeleteMember(self):
        self.createMember(
            "barney", "Barney Rubble", "barney@bedrock.com", ["Member"], "2002-01-01"
        )
        self.portal.manage_role("Member", [Permissions.manage_users])
        g = self.groups.getGroupById("foo")
        g.addMember("barney")
        self.assertEqual(len(g.getGroupMembers()), 1)
        self.membership.deleteMembers(["barney"])
        barney = self.membership.getMemberById("barney")
        del barney
        self.assertEqual(len(g.getGroupMembers()), 0)
        self.createMember(
            "barney", "Barney Rubble", "barney@bedrock.com", ["Member"], "2002-01-01"
        )
        self.assertEqual(len(g.getGroupMembers()), 0)

Mike Metcalfe via Plone Community wrote at 2022-11-23 09:22 +0000:

... delete user does not remove the user from groups ...
Is this a bug?

Not necessarily:
Products.PluggableAuthService (underlying Products.PlonePAS) has
been designed to be modular. Its plugins try by purpose to be independent
of one another and each focus on very few methods.
Unfortunately, this brings the risk of less integration
and therefore more work for the administrator.

In your case: deleting a user is indeed complex.
Not only, you must delete the user but also:

  • remove him from any group he belongs to
  • remove all "local role"s associated with this user
  • handle creator references to this user in all content objects
  • potentially (if the user had sufficient priviledges)
    fix "executable ownership" for all executable objects
    (scripts, templates) created by that user.

The easiest thing is to avoid deleting a user
but instead disable him (such that he can no longer log in).
If you must delete data associated with the user for legal reasons,
delete the data but keep the user object (appropriately disabled).

This is known and historically somehow by intend. Also ownership and local roles on items will be "transferred" to the new user. To avoid this behavior it is recommended to use UUIDs as userids internally:

seems all ok, it is a btree and things are removed. deleteMembers just remove the user but not the user from the group.

You've to explicitly remove it form the group before deleting the user.

Thanks guys, I think I'll go with Dieter and instruct the administrators to disable rather than delete users.

How would you disable a user?

If users are content (dexterity.membrane) you could do this with workflow. But how do you do this for normal users?

One sneaky way I can think of:

I was thinking of just removing the Member role.

As non-Member they might still have access to some areas with local roles. And they might have access simply because they are authenticated. But it helps.

It could be a nice addition for Plone to have an extra user property active. Site Administrators could uncheck this and the user would no longer be able to login: the authentication plugins would check for this property and deny authentication. Is there no add-on that does this?

Maurits van Rees via Plone Community wrote at 2022-11-25 12:03 +0000:

As non-Member they might still have access to some areas with local roles. And they might have access simply because they are authenticated. But it helps.

It could be a nice addition for Plone to have an extra user property active. Site Administrators could uncheck this and the user would no longer be able to login: the authentication plugins would check for this property and deny authentication. Is there no add-on that does this?

Products.PlonePAS notifies the UserLoggedInEvent event;
one could implement denying logic in a corresponding subscriber.

1 Like

For pas.plugins.ldap, the underlying node.ext.ldap UGM implementation supports expiring accounts and so pas.plugins.ldap does. Expired accounts are prevented to authenticate.

This can be enabled in the settings by a checkbox. Once enabled two values are expected: The unit, to check if its days or seconds since epoch and the attribute to check for the timestamp in the given unit. On each authenticate the values are checked and if the current timestamp is larger than the stored authentication is prevented.

As it is LDAP there are different attributes used. AD uses AccountExpires while edirectory uses LoginExpirationTime and there is also PwdEndTime somewhere used.

Overall I like the idea to add such a date based feature to Plone internal users itself. This is more flexible than a boolean and by setting the date to now an immediate lock would work too.

I found this while looking into whether or not users not removed from groups was a "bug", but there is some interesting discussion on deactivating users here. My team is actually planning on this for unrelated reasons. My plan was to add a member field 'active' and then use our existing SSO plugin's authenticateCredentials method to check that property's value. If they don't have it checked, they aren't authenticated.

What looks more complicated is adapting the Users Overview interface UX. I can add another column for active status, no problem. But I can see an admin wanting to search for just active (or inactive) users and I don't see how you add more options to that search form without creating a mess.