Following the first issue here MAC OSX and Role Authorization my investigation brought me further than only role authorization.

As mentioned, the following is available for OS X, and more precisely Mountain Lion server. I haven't tested with any other LDAP setup.

So here is the setup to make sure authorization is working.
Group settings:

  1. Name of Group Object Class: apple-group
  2. Nested groups are used in my LDAP: checked
  3. LDAP Group Entry Attribute Holding User's DN, CN, etc.: apple-group-memberguid
  4. User attribute held in "LDAP Group Entry Attribute Holding...": apple-generateduid

Authorization settings - role & og: the mapping is pretty simple - just map the dns of your groups with your roles, or with node:[nid]:[group-rid]

So these are the basic settings I could define. I tried a couple of other configurations (in the group settings, for example, by using member uid and uid for respectively "LDAP Group Entry Attribute Holding User's DN, CN, etc" and ""User attribute held in "LDAP Group Entry Attribute Holding...") - actually, just like this, none of them worked. But the settings above are in my opinion the optimal configuration that can go with the hack below.

Warning: as I said, I don't know about the side effects with other LDAP setup. This is a lead to enhance the module, either its code (in case there is actually a bug) or its documentation (in case I really missed something...).

So here is the hack: in LdapServer.class.php, change the groupMembershipsFromEntryResursive function with the one below:

   * recurse through all groups, adding parent groups to $all_group_dns array.
   * @param array $current_group_entries of ldap group entries that are starting point.  should include at least 1 entry.
   * @param array $all_group_dns as array of all groups user is a member of.  MIXED CASE VALUES
   * @param array $tested_group_ids as array of tested group dn, cn, uid, etc.  MIXED CASE VALUES
   *   whether these value are dn, cn, uid, etc depends on what attribute members, uniquemember, memberUid contains
   *   whatever attribute is in $this->$tested_group_ids to avoid redundant recursing
   * @param int $level of recursion
   * @param int $max_levels as max recursion allowed
   * @param array $user_ldap_entry as array of user properties from LDAP - new param from the HACK
   * given set of groups entries ($current_group_entries such as it, hr, accounting),
   * find parent groups (such as staff, people, users) and add them to list of group memberships ($all_group_dns)
   * (&(objectClass=[$this->groupObjectClass])(|([$this->groupMembershipsAttr]=groupid1)([$this->groupMembershipsAttr]=groupid2))
   * @return FALSE for error or misconfiguration, otherwise TRUE.  results are passed by reference.

public function groupMembershipsFromEntryResursive($current_group_entries, &$all_group_dns, &$tested_group_ids, $level, $max_levels, $user_ldap_entry) {

    if (!
$this->groupGroupEntryMembershipsConfigured || !is_array($current_group_entries) || count($current_group_entries) == 0) {
    if (isset(
$current_group_entries['count'])) {
$ors = array();
    foreach (
$current_group_entries as $i => $group_entry) {
      if (
$this->groupMembershipsAttrMatchingUserAttr == 'dn') {
$member_id = $group_entry['dn'];
      else {
// maybe cn, uid, etc is held
        //HACK: we use the member id from the new variable.
        //$member_id = ldap_servers_get_first_rdn_value_from_dn($group_entry['dn'], $this->groupMembershipsAttrMatchingUserAttr);
$member_id = $user_ldap_entry['attr'][$this->groupMembershipsAttrMatchingUserAttr][0];
//HACK: we go through nesting by adding a or condition in the ldap query. So we need to define the good condition, and make sure this is executed once per user id
  //    if ($member_id && !in_array($member_id, $tested_group_ids)) {
if (!in_array($group_entry[$this->groupMembershipsAttrMatchingUserAttr][0], $tested_group_ids)) {
//HACK remove the count value

//$tested_group_ids[] = $member_id;
//HACK we want to remember all the user ids
foreach ($group_entry[$this->groupMembershipsAttrMatchingUserAttr] as $key => $uid) {
$tested_group_ids[] = $uid;
//HACK and we want to setup the recursion: the recursion is held by a specific attribute - need to add it to the group setting form?
$ors[] = 'apple-group-nestedgroup='.$uid;

$all_group_dns[] = $group_entry['dn'];
// add $group_id (dn, cn, uid) to query
        //$ors[] =  $this->groupMembershipsAttr . '=' . $member_id;

    if (
count($ors)) {
$count = count($ors);
      for (
$i=0; $i < $count; $i=$i+LDAP_SERVER_LDAP_QUERY_CHUNK) { // only 50 or so per query
$current_ors = array_slice($ors, $i, LDAP_SERVER_LDAP_QUERY_CHUNK);
//dpm("current_ors $i," . LDAP_SERVER_LDAP_QUERY_CHUNK); dpm($current_ors);
$or = '(|(' . join(")(", $current_ors) . '))'// e.g. (|(cn=group1)(cn=group2)) or   (|(dn=cn=group1,ou=blah...)(dn=cn=group2,ou=blah...))
$query_for_parent_groups = '(&(objectClass=' . $this->groupObjectClass . ')' . $or . ')';

// debug('query_for_parent_groups'); debug($query_for_parent_groups);
foreach ($this->basedn as $base_dn) {  // need to search on all basedns one at a time
$group_entries = $this->search($base_dn, $query_for_parent_groups);  // no attributes, just dns needed
if ($group_entries !== FALSE  && $level < $max_levels) {
//HACK call the function with its new parameter
$this->groupMembershipsFromEntryResursive($group_entries, $all_group_dns, $tested_group_ids, $level + 1, $max_levels, $user_ldap_entry);
//HACK actually return a value
return TRUE;

And then, in the only function calling this one, groupUserMembershipsFromEntry, line 1592:

->groupMembershipsFromEntryResursive($group_entries, $all_group_dns, $tested_group_ids, $level, $max_levels, $user_ldap_entry);

I hope it is useful - it was the only way for me to make the authorization work with MAC OS X, OG, roles and nested groups, after one week of hair-pulling.


johnbarclay’s picture

Title:MAC OS X: Authorization & nested groups» LDAP Authorization: MAC OS X: Authorization & nested groups

Appreciate the clarity and research, sorry for your hair pulling. Great to hear someone is testing against the apple ldap. I'll make it a priority to work through this and get whatever changes are needed into the code.

Looks like the apple-group-nestedgroup is the only thing that is different than a traditional openldap or active directory groups except the name of the attributes.

Can you give me some sample ldap entries (groups, nested groups, users) that are in the apple structure so I can add them to the simpletest coverage's fake ldap server? (the tests basically generate the ldap structure for various ldaps on the fly and this will help make sure the apple use case doesn't break down the line)

What is the the Apple ldap called (for documentation and pulldown)?

crystal_alexandre_froger’s picture

new63.47 KB
new50.23 KB
new99.92 KB

Allright, so below some LDAP entries as requested. I will basically describe my simple setup. Note: this is the first time I work with LDAP and its concepts - my apology if something is unclear/mistaken.

Tools used:

User entry:

Group test - parent

Group test - nested child

The attribute apple-group-nestedgroup holds a series of apple-generateduid to implement the nested groups.

For membership, in a group, two ways: either use the uid from user entry (attribute memberuid will contain multiple uid) or the generated values from apple server - maybe safer (attribute apple-group-memberguid will contain multiple apple-generateduid).

The Apple LDAP implementation is based on OpenLDAP, with particularities. They call it "Apple Open Directory" (found the information here:

Hope this helps!

johnbarclay’s picture

Category:bug» feature

So in the configuration, it could look something like:

LDAP Group Attribute Holding User Members: 'apple-group-memberguid'
User attribute held in "LDAP Group Attribute Holding User Members": 'apple-generateduid'

LDAP Group Attribute Holding Group Members: 'apple-group-nestedgroup'
Group attribute held in "LDAP Group Attribute Holding Group Members...": 'apple-generateduid'

crystal_alexandre_froger’s picture

That's it - we need to differentiate the group attribute from the member attribute to take nested groups into account for the Apple Open Directory.

johnbarclay’s picture

great. I'll work this into the UI and server classes. Then we can get your patch in also.

As the UI for servers gets larger and larger, I want to set some defaults/and wizards based on the type of ldap a user selects. How does this look for opendir?:

crystal_alexandre_froger’s picture

The information is accurate for the attributes that are used.
However, this is not for all the OpenLDAP, but only for Apple Open Directory which is, as far as I understood, an extension of the standard implementation of OpenLDAP.

So the name of the class, in my opinion, should be:

class LdapTypeAppleOpenDirectory extends LdapTypeAbstract

as a separate LDAP implementation, at the same level as AD and the genuine OpenLDAP.

gpwarren’s picture

Any more progress on this? I am kicking Apple's Wiki server to the curb this week and have been working on moving everything to a Drupal build on a VM. Not crazy about moving the LDAP server off of Mountain Lion (yet) as we are pretty much an all-Mac company.


johnbarclay’s picture

No progress. But you are correct to watch this thread for progress on this.

kwtobin’s picture

Does anyone know how to get this hack working with the newer versions? It doesn't appear to work and I'm also having issues with authorization using Open Directory.