Access Control Plug-in Order in Backend

Request from IPA:

IPA has a plugin which replaces a known value (ipaUniqueID=autogenerate) with a randomly generated UUID (ipaUniqueID=autogenerate). It currently executes in preoperation and internalpreoperation.

In the case of preoperation, the access controls are evaluated after the UUID plugin makes the change. This means that the access controls are useless for any attribute randomly generated by the UUID plugin. You should be able to create an ACI that restricts access to the known value only.

Here is a real world case. To allow users to create tokens, we have an ACI like this:

aci: (target = "ldap:///ipatokenuniqueid=*,cn=otp,$SUFFIX")(targetfilter
 "(objectClass=ipaToken)")(version 3.0; acl "Users can create self-managed
 tokens"; allow (add) userattr = "ipatokenOwner#SELFDN" and userattr =

The problem is that this permits a user to create an entry with any value. If ACIs were evaluated before the UUID plugin and not afterward, we could use this ACI:

aci: (target = "ldap:///ipatokenuniqueid=autogenerate,cn=otp,$SUFFIX")(targetfilter 
 = "(objectClass=ipaToken)")(version 3.0; acl "Users can create self-managed tokens";
 allow (add) userattr = "ipatokenOwner#SELFDN" and userattr = "managedBy#SELFDN";)

For add operations, ACIs are currently evaluated after bepreop but before betxnpreop. Unfortunately, we cannot change DN/ipaTokenUniqueID in betxnpreop. For modify operations, the ACIs are currently evaluated before bepreop. One possible solution would be to move the add operation behavior to be the same as the modify operation.

See also


389-ds-base code: master on 2015/11/11 nearly equivalent to 389-ds-base-

Order consistency

We would like to have the consistent order for the plug-in hooks and the acl evaluation. Currently, just ldbm_modify has the requested order.

320   rc = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_PRE_ADD_FN);
724   ldap_result_code = plugin_call_acl_plugin(pb, e, NULL, NULL, SLAPI_ACL_ADD, ACLPLUGIN_ACCESS_DEFAULT, &errbuf);

582   if ((ldap_result_code = plugin_call_acl_mods_access( pb, e->ep_entry, mods, &errbuf)) != LDAP_SUCCESS )
601   opreturn = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_PRE_MODIFY_FN);

445   rc = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_PRE_MODRDN_FN);
602   ldap_result_code = plugin_call_acl_plugin(pb, newparententry->ep_entry, NULL, NULL, SLAPI_ACL_MODDN, ACLPLUGIN_ACCESS_DEFAULT, &errbuf);
610   ldap_result_code = plugin_call_acl_plugin (pb, newparententry->ep_entry, NULL, NULL, SLAPI_ACL_ADD, ACLPLUGIN_ACCESS_DEFAULT, &errbuf );
663   ldap_result_code = plugin_call_acl_plugin(pb, ec->ep_entry, NULL /*attr*/, NULL /*value*/, SLAPI_ACL_WRITE, ACLPLUGIN_ACCESS_MODRDN, &errbuf);

307   retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_PRE_DELETE_FN);
440   ldap_result_code = plugin_call_acl_plugin (pb, e->ep_entry, NULL, NULL, SLAPI_ACL_DELETE, ACLPLUGIN_ACCESS_DEFAULT, &errbuf );

Order in ldbm_delete

ldbm_delete has this comment added to plugin_call_acl_plugin.

/* JCMACL - Shouldn't the access check be before the has children check...
 * otherwise we're revealing the fact that an entry exists and has children */

This is considered as a justification of plugin_call_acl_plugin located at the position.
But the test results show the opposites.

Assume “uid=tuser0” has no right on ou=outest,dc=example,dc=com.

With the current order, which reveals the info the entry has descendants:

$ ldapdelete ... -D 'uid=tuser0,ou=People,dc=example,dc=com' -W
ldap_delete: Operation not allowed on non-leaf (66)

If plugin_call_acl_plugin is called prior to the children check and SLAPI_PLUGIN_BE_PRE_DELETE_FN as requested, which does not reveal the children info:

$ ldapdelete ... -D 'uid=tuser0,ou=People,dc=example,dc=com' -W
ldap_delete: Insufficient access (50)
additional info: Insufficient 'delete' privilege to delete the entry 'uid=tuser0,ou=outest,dc=example,dc=com'.

Question: Any other use cases that proves the current order is safer?

Order in ldbm_add and ldbm_modrdn

When the replication is enabled and the operation causes a conflict, the DN in the target entry could be modified in plugin_call_plugins(pb, SLAPI_PLUGIN_BE_PRE_ADD|MODRDN_FN). For instance, “uid=tuser,ou=subsubou,ou=subou,ou=ou,dc=suffix” could be converted to “nsuniqueid=######+uid=tuser,ou=subsubou,nsuniqueid=######+ou=subou,ou=ou,dc=suffix”. The conflict DN does not follow the ACI that the original DN is supposed to. Even if the DN is modified into the conflict DN, it should follow the planned access control.


Relocating “plugin_call_acl_plugin” call prior to BE_PRE plug-in.


The order change does not impact the acceptance test results.

Details after implementation and consideration

Update: 2017-02-13

This has now been implemented in

As per the notes, this does not change the acceptance test results.

We now execute ADD, MODIFY and DELETE with:

ACI Check
Call plugins

This means plugins are privileged and can act on behalf of a user: This is a good thing! It means a user doesn’t need access to other subtrees to create user private groups etc. It also means we exit sooner if an invalid add/mod/delete is provided, before we start work on plugins then having to roll back. This makes failures faster.

Last modified on 18 June 2017