How to determine all the groups a user belongs to (including nested groups) in ActiveDirectory and .NET 3.5

31,837

Solution 1

Workaround #1

This bug is reported here at Microsoft Connect along with the following code that works around this issue by manually iterating through the PrincipalSearchResult<Principal> returned objects, catching this exception, and continuing on:

PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();
var iterGroup = groups.GetEnumerator();
using (iterGroup)
{
    while (iterGroup.MoveNext())
    {
        try
        {
            Principal p = iterGroup.Current;
            Console.WriteLine(p.Name);
        }
        catch (NoMatchingPrincipalException pex)
        {
            continue;
        }
    }
}

Workaround #2

Another workaround found here avoids the AccountManagement class, and uses the System.DirectoryServices API instead:

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.DirectoryServices;  

namespace GetGroupsForADUser  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            String username = "Gabriel";  

            List<string> userNestedMembership = new List<string>();  

            DirectoryEntry domainConnection = new DirectoryEntry(); // Use this to query the default domain
            //DirectoryEntry domainConnection = new DirectoryEntry("LDAP://example.com", "username", "password"); // Use this to query a remote domain

            DirectorySearcher samSearcher = new DirectorySearcher();  

            samSearcher.SearchRoot = domainConnection;  
            samSearcher.Filter = "(samAccountName=" + username + ")";  
            samSearcher.PropertiesToLoad.Add("displayName");  

            SearchResult samResult = samSearcher.FindOne();  

            if (samResult != null)  
            {  
                DirectoryEntry theUser = samResult.GetDirectoryEntry();  
                theUser.RefreshCache(new string[] { "tokenGroups" });  

                foreach (byte[] resultBytes in theUser.Properties["tokenGroups"])  
                {  
                    System.Security.Principal.SecurityIdentifier mySID = new System.Security.Principal.SecurityIdentifier(resultBytes, 0);  

                    DirectorySearcher sidSearcher = new DirectorySearcher();  

                    sidSearcher.SearchRoot = domainConnection;  
                    sidSearcher.Filter = "(objectSid=" + mySID.Value + ")";  
                    sidSearcher.PropertiesToLoad.Add("distinguishedName");  

                    SearchResult sidResult = sidSearcher.FindOne();  

                    if (sidResult != null)  
                    {  
                        userNestedMembership.Add((string)sidResult.Properties["distinguishedName"][0]);  
                    }  
                }  

                foreach (string myEntry in userNestedMembership)  
                {  
                    Console.WriteLine(myEntry);  
                }  

            }  
            else 
            {  
                Console.WriteLine("The user doesn't exist");  
            }  

            Console.ReadKey();  

        }  
    }  
}  

Solution 2

Use UserPrincipal.GetAuthorizationGroups() instead - from its MSDN docs:

This method searches all groups recursively and returns the groups in which the user is a member. The returned set may also include additional groups that system would consider the user a member of for authorization purposes.

The groups that are returned by this method may include groups from a different scope and store than the principal. For example, if the principal is an AD DS object that has a DN of "CN=SpecialGroups,DC=Fabrikam,DC=com, the returned set can contain groups that belong to the "CN=NormalGroups,DC=Fabrikam,DC=com.

Solution 3

I know this is an old thread, but it's the top result on Google, so in case this helps anyone, here's what I came up with that uses the AccountManagement stuff, but makes this particular query much easier.

public static class AccountManagementExtensions
{
    public static bool IsNestedMemberOf(this Principal principal, GroupPrincipal group)
    {
        // LDAP Query for memberOf Nested 
        var filter = String.Format("(&(sAMAccountName={0})(memberOf:1.2.840.113556.1.4.1941:={1}))",
                principal.SamAccountName,
                group.DistinguishedName
            );

        var searcher = new DirectorySearcher(filter);

        var result = searcher.FindOne();

        return result != null;
    }
}
Share:
31,837

Related videos on Youtube

Sergi Papaseit
Author by

Sergi Papaseit

Code for a living, code as a hobby. Spreading .NET and Agile love around the world at CodeNamed.

Updated on February 18, 2020

Comments

  • Sergi Papaseit
    Sergi Papaseit about 4 years

    I have an application that uses ActiveDirecotry authorisation and it has been decided that it needs to support nested AD groups, e.g.:

    MAIN_AD_GROUP
         |
         |-> SUB_GROUP
                  | 
                  |-> User
    

    So, the user in not directly a member of MAIN_AD_GROUP. I'd like to be able to look for the user recursively, searching the groups nested in MAIN_AD_GROUP.

    The main problem is that I'm using .NET 3.5 and there is a bug in System.DirectoryServices.AccountManagement in .NET 3.5 whereby the method UserPrincipal.IsMemberOf() will not work for groups with more than 1500 users. So I can't use UserPrincipal.IsMemberOf() and no, I can't switch to .NET 4 either.

    I've worked around this last problem with the following function:

    private bool IsMember(Principal userPrincipal, Principal groupPrincipal)
    {
        using (var groups = userPrincipal.GetGroups())
        {
            var isMember = groups.Any(g => 
                g.DistinguishedName == groupPrincipal.DistinguishedName);
            return isMember;
        }
    }
    

    But userPrincipal.GetGroups() only returns the groups of which the user is a direct member.

    How can I get this to work with nested groups?

  • Sergi Papaseit
    Sergi Papaseit about 13 years
    Thanks, but unfortunately this throws an PrincipalOperationException exception with the message "There is no such object on the server." The UserPrincipal definitely exists, as my method above does return the proper authorisations for top-level groups.
  • Tim Lewis
    Tim Lewis over 12 years
    I chose to implement Workaround #2. YMMV with Workaround #1.
  • Kjensen
    Kjensen almost 12 years
    Good post. Workaround #1 fails on iterGroup.MoveNext() with the same error "There is no such object on the server".
  • Ronen Festinger
    Ronen Festinger over 10 years
    Workaround 2 can work only if the program runs from a computer that is logged on to the domain. It can't work if you are querying the ldap from a different domain.
  • Kiquenet
    Kiquenet over 9 years
    @RonenFestinger Any Workaround #3 ?
  • Tim Lewis
    Tim Lewis over 9 years
    @Kiquenet & @Ronen it looks like you can use another constructor for DirectoryEntry domainConnection to pass in a username and password that is valid on the different domain. See: stackoverflow.com/questions/9362724/…
  • Tim Lewis
    Tim Lewis over 9 years
    I edited the answer to include a line of code that can be used for connecting to a remote domain: DirectoryEntry domainConnection = new DirectoryEntry("LDAP://example.com", "username", "password"); // Use this to query a remote domain
  • jproch
    jproch about 9 years
    UserPrincipal.GetAuthorizationGroups() gets choked up if there are deleted groups that the user is still a member of
  • Elger Mensonides
    Elger Mensonides about 8 years
    Just check if the group.Name is not null in that case @jproch
  • Jack
    Jack over 7 years
    How do you hug someone on this site!!! Workaround number 2 is crazy efficient!!! You just saved my entire day!
  • wojtow
    wojtow over 3 years
    This answer as is will always return true (given that there's a membership=true the line before the return)