Use PHP and LDAP to get a user’s group membership, including the primary group

This function queries a user’s memberOf attribute to get a list of groups. It then uses user’s primaryGroupID to retrieve the distinguishedName of the corresponding primary group. The results are returned in a single, neat, array. This function can have many applications including use with a login system.

The Function

Example Output

19 Comments

  1. Eric

    Awesome! Works perfect.

    How do I get it to so only the group and not the rest of the containers?

    Example:

    Domain Users

    instead of the full result like below:

    CN=Domain Users,CN=Users,DC=company,DC=com

  2. Eric

    Thanks! For anyone looking to see how I did that here is what I did:

  3. Sven

    Hi there,

    is there a possibility to insert the groups into a mysql database?
    I was thinking of something like

    id / displayName / group1 / group2 / group3 / and so on

    can you help me there?

  4. Firdi

    works:

    before:// Connect to AD
    $ldap = ldap_connect($ldap_host) or die(“Could not connect to LDAP”);

    ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
    ldap_bind($ldap,$query_user,$password) or die(“Could not bind to LDAP”);

    // Search AD
    $results = ldap_search($ldap,$ldap_dn,”(samaccountname=$user)”,array(“memberof”,”primarygroupid”));
    $entries = ldap_get_entries($ldap, $results);

    after:

  5. leosqual

    Hello, thanks for your contributions and thanks in advance for your help:

    I have this case below with active directory and 3 questions :

    Group A: user1…user2……..
    GroupB: user11, user22, user1
    GroupC: user55, user44, GroupA, GroupB
    GroupD: GroupA, GroupB

    1- Notice that there is a limit of 1000 entries for Active directory (how can i solve this problem)

    2- How to get members with one parameter as $group ? for subgroup i need to display the subgroup name and the parent group.

    3 – How can i get just for question 2, active users ?

  6. sajjad

    I’m facing error on output

    ( ! ) Warning: ldap_search(): Partial search results returned: Sizelimit exceeded in C:\wamp\www\sn.php on line 33
    Call Stack
    # Time Memory Function Location
    1 0.0005 138352 {main}( ) ..\sn.php:0
    2 0.0006 138448 get_groups( ) ..\sn.php:54
    3 0.0075 142648 ldap_search ( ) ..\sn.php:33
    Array ( [0] => CN=Allow BLB UAT,OU=JSBL Internet,OU=JSBL Users,DC=jsbl,DC=com [1] => CN=KLOperators,OU=Services Account,DC=jsbl,DC=com [2] => CN=KLAdmins,OU=Services Account,DC=jsbl,DC=com [3] => CN=Local Admin,OU=JSBL Internet,OU=JSBL Users,DC=jsbl,DC=com [4] => CN=Allow Search Engine,OU=JSBL Internet,OU=JSBL Users,DC=jsbl,DC=com [5] => CN=Allow External Mails,OU=JSBL Internet,OU=JSBL Users,DC=jsbl,DC=com [6] => CN=khfhadc-all,OU=Faysal House IT,OU=Karachi,OU=South,OU=JSBL Branches,OU=JSBL Users,DC=jsbl,DC=com [7] => CN=Domain Admins,CN=Users,DC=jsbl,DC=com )
    ( ! ) Warning: ldap_search(): Partial search results returned: Sizelimit exceeded in C:\wamp\www\sn.php on line 33
    Call Stack
    # Time Memory Function Location
    1 0.0005 138352 {main}( ) ..\sn.php:0
    2 0.1653 138720 get_groups( ) ..\sn.php:55
    3 0.1718 141432 ldap_search ( ) ..\sn.php:33
    Array ( [0] => CN=Allow Search Engine,OU=JSBL Internet,OU=JSBL Users,DC=jsbl,DC=com )

  7. Jon

    Do you know what would be required to find the nested group membership? I’ve been trying to change my search to use 1.2.840.113556.1.4.1941 AKA the LDAP_MATCHING_RULE_IN_CHAIN, but I can’t seem to get any output.

    In my code, I’ve added a user search to get attributes, and I’ve created a variable that stores the user’s distinguishedname –
    around line 20, added
    $namesearch = ldap_search($ldap,$ldap_dn,”(samaccountname=$user)”, array(“distinguishedname”,”givenname”,”sn”,”mail”));
    $names = ldap_get_entries($ldap,$namesearch);
    $userDN = $names[0][‘distinguishedname’][0];

    and then changed the group query to

    $groupsearch = ldap_search($ldap,$ldap_dn,”(member:1.2.840.113556.1.4.1941:=$userDN)”);

    But it’s not returning any groups.

      • Jon

        Doesn’t seem to make a difference; both member and memberOf return 0 results.

        Here is the entire function that I’ve written, based on your code above.

        /*
        * function authenticate($user,$pass)
        * takes username and password as inputs
        * Attempts to authenticate user against AD and gets user information
        * Returns an array of user information
        */

        function authenticate($user,$pass)
        {
        // Don't bother authenticating if user or password is blank
        if(empty($user) || empty($pass))
        {
        echo "Cannot authenticate: empty username or password";
        return false;
        }

        // Gather AD details

        $adserver = 'ldaps://dc1.sandbox.local';
        $adport = '636';
        $domain = 'sandbox.local';
        $ldap_dn = "CN=Users,Dc=sandbox,DC=local";

        $conn = ldap_connect($adserver,$adport);
        // Set a few LDAP Options
        ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($conn, LDAP_OPT_REFERRALS, 0);

        //Should try to sanitize these:
        //$user = mysqli_real_escape_string($user);
        //$pass = mysqli_real_escape_string($pass);

        $username = $user . "@" . $domain;
        $password = $pass;

        //Try to authenticate
        $bind = ldap_bind($conn,$username,$password);

        if($bind)
        {
        // Look up user and get first name, middle initial, last name, and email address
        $namesearch = ldap_search($conn,$ldap_dn,"(samaccountname=$user)");
        var_dump($namesearch);
        var_dump(ldap_error($conn));
        var_dump(ldap_errno($conn));
        $names = ldap_get_entries($conn,$namesearch);
        if($names['count'] == 1)
        {
        for ($i = 0; $i<$names["count"]; $i++)
        {
        $userDN = $names[$i]['distinguishedname'][0];
        $fname = $names[$i]["givenname"][0];
        if(ISSET($names[$i]["initials"][0]))
        {
        $initial = $names[$i]["initials"][0];
        }
        else
        {
        $initial = "";
        }
        $lname = $names[$i]["sn"][0];
        $email = $names[$i]["mail"][0];
        }
        }
        // Look up user groups and create groups array
        //$groupsearch = ldap_search($conn,$ldap_dn,"(samaccountname=$user)",array("memberof","primarygroupid")); // <-- works, ruturns user groups
        //$groupsearch = ldap_search($conn,$ldap_dn,"(memberOf:1.2.840.113556.1.4.1941:=" . $userDN . ")"); // <-- doesn't return anything
        $groupsearch = ldap_search($conn,$ldap_dn,"(member:1.2.840.113556.1.4.1941:=" . $userDN . ")"); // <-- doesn't return anything

        $entries = ldap_get_entries($conn,$groupsearch);
        if($entries['count'] == 0)
        {
        $usergroups[] = 'NoGroups';
        }
        else
        {
        // Get Groups and primay group token
        $output = $entries[0]['memberof'];
        $token = $entries[0]['primarygroupid'][0];
        // Remove extraneous first entry
        array_shift($output);
        // Look up the primary group, get list of all groups
        $results2 = ldap_search($conn,$ldap_dn,"(objectcategory=group)",array("distinguishedname","primarygrouptoken"));
        $entries2 = ldap_get_entries($conn,$results2);
        // Remove extraneous first entry
        array_shift($entries2);
        // Loop through all groups and find primary group token
        foreach($entries2 as $e)
        {
        if($e['primarygrouptoken'][0] == $token)
        {
        // Primary group found, add to output array
        $output[] = $e['distinguishedname'][0];
        // Break loop
        break;
        }
        }
        //Loop through output to break off DN and just return group name
        foreach($output as $o)
        {
        $dn = explode(",",$o,2);
        $gn = explode("=",$dn[0]);
        $usergroups[] = $gn[1];
        }
        }

        //Close connection
        ldap_close($conn);

        //Create an array to return to caller
        $userstring[0] = true;
        $userstring[1] = $user;
        $userstring[2] = $username;
        $userstring[3] = $fname;
        $userstring[4] = $initial;
        $userstring[5] = $lname;
        $userstring[6] = $email;
        $userstring[7] = $usergroups;

        //return array
        return $userstring;
        }
        // If bind fails, return false
        return false;
        }

        • Jon

          As a note, if I change my query to memberOf, and provide the full CN of a known group, it DOES output the nested group members….

          $groupsearch = ldap_search($conn,$ldap_dn,"(member:1.2.840.113556.1.4.1941:=" . $userDN . ")"); <-- returns 0 results

          $groupsearch = ldap_search($conn,$ldap_dn,"(memberOf:1.2.840.113556.1.4.1941:=" . {knowngroupCN} . ")"); <-- returns all the users, including those nested in other groups.

          So, I’m wondering if this is an issue with how my Domain Controller is set up… I verified that it does have a global catalog… I’m really stumped, based on Microsoft’s documentation at https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx I am doing everything right… but still, no luck.

    • Jon

      I figured this out. Here is my correctly working code, which gets all groups a user is a member of, directly or indirectly, except their primary group. I’m sure I could add the primary group search stuff back, but for the moment, I’m beat.

      And if you are stuck on php prior to 5.6, you’ll need to add the ldap_escape function:

      http://stackoverflow.com/questions/8560874/php-ldap-add-function-to-escape-ldap-special-characters-in-dn-syntax/8561604#8561604


      *
      * function authenticate($user,$pass)
      * takes username and password as inputs
      * Attempts to authenticate user against AD and gets user information
      * Returns an array of user information
      */

      function authenticate($user,$pass)
      {

      // Don't bother authenticating if user or password is blank
      if(empty($user) || empty($pass))
      {
      echo "Cannot authenticate: empty username or password";
      return false;
      }

      // Gather AD details

      $adserver = 'ldaps://dc1.sandbox.local';
      $adport = '636';
      $domain = 'sandbox.local';
      $ldap_dn = "DC=sandbox,DC=local";

      $conn = ldap_connect($adserver,$adport);
      // Set a few LDAP Options
      ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
      ldap_set_option($conn, LDAP_OPT_REFERRALS, 0);

      //Should try to sanitize these:
      //$user = mysqli_real_escape_string($user);
      //$pass = mysqli_real_escape_string($pass);

      $username = $user . "@" . $domain;
      $password = $pass;

      //Try to authenticate
      $bind = ldap_bind($conn,$username,$password);

      if($bind)
      {
      // Look up user and get first name, middle initial, last name, and email address
      $namesearch = ldap_search($conn,$ldap_dn,"(samaccountname=$user)",array("givenname","initials","sn","mail"));
      $names = ldap_get_entries($conn,$namesearch);

      //var_dump($namesearch);
      //var_dump(ldap_error($conn));
      //var_dump(ldap_errno($conn));
      //var_dump($names);

      if($names['count'] == 1)
      {
      $userDN = $names[0]['dn'];
      $fname = $names[0]["givenname"][0];
      if(ISSET($names[0]["initials"][0]))
      {
      $initial = $names[0]["initials"][0];
      }
      else
      {
      $initial = "";
      }
      $lname = $names[0]["sn"][0];
      $email = $names[0]["mail"][0];
      }
      // Look up user groups and create groups array
      $userDN = ldap_escape($userDN,null,LDAP_ESCAPE_DN);
      $filter = '(member:1.2.840.113556.1.4.1941:=' . $userDN . ')';
      $attribs = array('samaccountname');
      $groupsearch = ldap_search($conn,$ldap_dn,$filter,$attribs);

      $entries = ldap_get_entries($conn,$groupsearch);

      //var_dump($userDN);
      //var_dump($filter);
      //var_dump($groupsearch);
      //var_dump(ldap_errno($conn));
      //var_dump(ldap_error($conn));
      //var_dump($entries);

      if($entries['count'] == 0)
      {
      $usergroups[] = 'NoGroups';
      }
      else
      {
      // Remove extraneous first entry
      array_shift($entries);
      foreach($entries as $e)
      {
      $usergroups[] = $e['samaccountname'][0];
      }
      }

      //var_dump($usergroups);
      //Close connection
      ldap_close($conn);

      //Create an array to return to caller
      $userstring[0] = true;
      $userstring[1] = $user;
      $userstring[2] = $username;
      $userstring[3] = $fname;
      $userstring[4] = $initial;
      $userstring[5] = $lname;
      $userstring[6] = $email;
      $userstring[7] = $usergroups;

      //return array
      return $userstring;
      }
      // If bind fails, return false
      return false;
      }

      • Jon

        Okay, took a break, had a drink, added the primary group logic back.

        /*
        * function authenticate($user,$pass)
        * takes username and password as inputs
        * Attempts to authenticate user against AD and gets user information
        * Returns an array of user information
        */

        function authenticate($user,$pass)
        {

        // Don't bother authenticating if user or password is blank
        if(empty($user) || empty($pass))
        {
        echo "Cannot authenticate: empty username or password";
        return false;
        }

        // Gather AD details

        $adserver = 'ldaps://dc1.sandbox.local';
        $adport = '636';
        $domain = 'sandbox.local';
        $ldap_dn = "DC=sandbox,DC=local";

        $conn = ldap_connect($adserver,$adport);
        // Set a few LDAP Options
        ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($conn, LDAP_OPT_REFERRALS, 0);

        //Should try to sanitize these:
        //$user = mysqli_real_escape_string($user);
        //$pass = mysqli_real_escape_string($pass);

        $username = $user . "@" . $domain;
        $password = $pass;

        //Try to authenticate
        $bind = ldap_bind($conn,$username,$password);

        if($bind)
        {
        // Look up user and get first name, middle initial, last name, and email address
        $namesearch = ldap_search($conn,$ldap_dn,"(samaccountname=$user)",array("givenname","initials","sn","mail","primarygroupid","memberof"));
        $names = ldap_get_entries($conn,$namesearch);

        if($names['count'] == 1)
        {
        $priGID = $names[0]["primarygroupid"][0];
        $userDN = $names[0]['dn'];
        $fname = $names[0]["givenname"][0];
        if(ISSET($names[0]["initials"][0]))
        {
        $initial = $names[0]["initials"][0];
        }
        else
        {
        $initial = "";
        }
        $lname = $names[0]["sn"][0];
        $email = $names[0]["mail"][0];
        }
        // Look up user groups and create groups array
        $userDN = ldap_escape($userDN,null,LDAP_ESCAPE_DN);
        $filter = '(member:1.2.840.113556.1.4.1941:=' . $userDN . ')';
        $attribs = array('samaccountname');
        $groupsearch = ldap_search($conn,$ldap_dn,$filter,$attribs);

        $entries = ldap_get_entries($conn,$groupsearch);

        // Look up the primary group
        $results2 = ldap_search($conn,$ldap_dn,"(objectcategory=group)",array("distinguishedname","primarygrouptoken"));
        $entries2 = ldap_get_entries($conn,$results2);
        foreach($entries2 as $e)
        {
        if($e['primarygrouptoken'][0] == $priGID)
        {
        // Primary group found, add to output array
        $output = $e['distinguishedname'][0];
        $dn = explode(",",$output,2);
        $gn = explode("=",$dn[0]);
        $usergroups[] = $gn[1];
        // Break loop
        break;
        }
        }

        if($entries['count'] > 0)
        {
        // Remove extraneous first entry
        array_shift($entries);
        foreach($entries as $e)
        {
        $usergroups[] = $e['samaccountname'][0];
        }
        }

        //Close connection
        ldap_close($conn);

        //Create an array to return to caller
        $userstring[0] = true;
        $userstring[1] = $user;
        $userstring[2] = $username;
        $userstring[3] = $fname;
        $userstring[4] = $initial;
        $userstring[5] = $lname;
        $userstring[6] = $email;
        $userstring[7] = $usergroups;

        //return array
        return $userstring;
        }
        // If bind fails, return false
        return false;
        }

        • sam

          Thanks for sharing Jon, I didn’t know about the LDAP escape

          Also gotta love how Microsoft randomly requires senseless syntax like member:1.2.840.113556.1.4.1941

  8. matt free

    Wow, this post saved me a huge headache. had to make some small modifications to fit my environment but kudos. Easy to use and modify, appreciate this post so much.

    Just for anyone else who stumbles upon this, I needed to create a way for our intranet site to verify group membership on certain pages, since that would be easier than creating some verified list for usernames. So I took Eric’s comment and modified it slight to loop through the edited array for memberOf.

    May not be the most elegant solution but I spent a good week trying to figure this out on my own and searching through a million other sites / forums with no luck.

    foreach($output as $a){
    $r = explode(“,”,$a,2);
    $g = explode(“=”,$r[0]);
    if($g[1]== “WT-Role System Administrators”){
    $membership = 1;
    break;
    } else {
    $membership = 0;
    }
    }

    if($membership == 1){
    echo “member of WT sys_admin”;
    } else {
    echo “not a member of WT sys_admin”;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *