The memberOf LDAP attribute is an attribute used for grouping of user entries. Typically, when you add a user as a member of a group, you add a “member” attribute that contains the DN of the user to a “groupOfUniqueNames” group entry. This approach makes it easy to get a list of all users who belong to a specific group by searching against the specific group you are interested in for all “member” attributes. If you are interested in knowing all groups that a specific user is a member of, things get more complex on the client side. To eliminate this complexity being pushed onto the client application, the “memberOf” attribute can be used. In addition to adding a “member” attribute to a group entry, you would add a “memberOf” attribute to the user entry that point to the group. This allows you to do a simple search against a specific user entry to find all groups that the user is a member of.
If the “memberOf” and “member” attributes are both being used, you run the risk of having errors in consistency. These inconsistency issues can arise in the case where either the group or user entry is modified, but the other side is not. This would result in different membership results when you check via the group entry versus checking the user entry. To avoid these inconsistency issues, the “memberOf” attribute should be automatically managed by the Directory Server when a change is made to a group entry that affects membership.
This feature will be implemented as a SLAPI plug-in that can be optionally enabled. A SLAPI plug-in implementing automatically maintained “memberOf” attribute functionality has been written as part of the FreeIPA project. This plug-in can be used as a starting point, but development work will still be needed.
The FreeIPA memberOf SLAPI plug-in is currently implemented as a post-operation plug-in. The plug-in takes action when a change is made that affects group membership. The plug-in will then update the “memberOf” attribute where necessary, which is stored just like any other attribute in the database.
The plug-in’s initialization function (ipamo_postop_init) registers the following plug-in callbacks with Directory Server:
static int ipamo_postop_del(Slapi_PBlock *pb ); static int ipamo_postop_modrdn(Slapi_PBlock *pb ); static int ipamo_postop_modify(Slapi_PBlock *pb ); static int ipamo_postop_add(Slapi_PBlock *pb ); static int ipamo_postop_start(Slapi_PBlock *pb); static int ipamo_postop_close(Slapi_PBlock *pb);
The first four callbacks deal with the core of this feature; that is they appropriately update the “memberOf” attribute for incoming DEL, MODRDN, MOD, and ADD LDAP operations. The ipamo_postop_start and ipamo_postop_close functions deal with startup and cleanup of the plug-in. An important thing to note about the ipamo_postop_start function is that it registers a task handler callback via the (not entirely exposed) SLAPI task interface. This task handler callback is:
int ipamo_task_add(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Entry *eAfter, int *returncode, char *returntext, void *arg)
This task handler allows an LDAP task entry to be added to the Directory Server to check for a grouping inconsistency and fix it. If everything is working correctly in the plug-in, there would never be a consistency issue to resolve. This task would still be useful if someone directly modified a “memberOf” attribute instead of allowing the plug-in to maintain it, which could cause an inconsistency problem. It would also be useful if you already have an existing database that is not using “memberOf” yet, but you want to populate the “memberOf” attribute.
The basic operation of the plug-in is as follows:
The below four sections will describe how the memberOf plug-in deals with specific incoming operations.
The plugin provides a SLAPI task that may be started by administrative clients and that creates the initial memberOf list for imported entries and/or fixes the memberOf list of existing entries that have inconsistent state (for example, if the memberOf attribute was incorrectly edited directly).
To start the memberof task add an entry like:
dn: cn=memberof task 2, cn=memberof task, cn=tasks, cn=config objectClass: top objectClass: extensibleObject cn: sample task basedn: dc=example, dc=com filter: (uid=test4)
The “basedn” attribute is required and refers to the top most node to perform the task on, and where “filter” is an optional attribute that provides a filter describing the entries to be worked on.
The current architecture is pretty sound overall. As with any feature, there are trade-offs to be made with different approaches. The items below are some things that we could change with the architecture, but we need to determine if the trade-offs are acceptable.
The current architecture implements “memberOf” as a normal attribute that is stored in the database. This will result in good performance when performing searches to determine membership, but will have a performance impact when changes are performed that alter group membership. Using a virtual attribute computed on the fly would improve performance for changes affecting group membership, but have a negative impact on searches that check membership. This negative performance impact could be reduced by somehow indexing or caching the virtual “memberOf” attribute values, but this added complexity may not be worth the effort. It’s also not determined if the performance impact of the current architecture is unacceptable.
It would be nice to not allow circular groupings to be added in the first place since I don’t think there is ever a valid reason to have them. We could check if a modification creates a circular grouping at the pre-operation stage and reject it with DSA_UNWILLING_TO_PERFORM. There are a few problems with doing this though. The first one is that it will impact performance to do this check, especially with complex grouping cases. The other problem is that there is a windows between the preop and postop stage where another modification can come in that would create a circular grouping, but it would be too late to reject it since we’ve already passed the preop stage for both operations. Multi-supplier replication would also have the potential of two changes on different suppliers creating a circular grouping when combined, which could cause replication to stop if we reject the replicated operation. For these reasons, I’m suggesting that we don’t perform this preop check and just allow the circular grouping to be created. There’s no real harm in this as long as the plug-in detects the recursion and doesn’t try to process it endlessly.
The attribute used for group members should be configurable. We should default to “member”, but allow a different attribute to be used instead if it is configured. The plug-in will expect this attribute to contain the DN of the member, not some other value such as their “uid”. This means the plug-in would work fine for attributes such as “uniqueMember”, but not for “memberUID”.
We really should make this work with MMR. This means we either have to replicate the “memberOf” attribute, or figure out a way to exclude it from being replicated when using MMR (**note by
A script should be added that performs the fix-up task creation for you. This simply needs to be a perl script that invokes the proper ldapmodify command as we do for other tasks. It may be nice to have an option to check the status of a running fix-up task in this script.