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

4

24

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?

Sporocarp answered 15/3, 2011 at 13:52 Comment(0)
T
34

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();  

        }  
    }  
}  
Tag answered 1/12, 2011 at 20:26 Comment(7)
I chose to implement Workaround #2. YMMV with Workaround #1.Tag
Good post. Workaround #1 fails on iterGroup.MoveNext() with the same error "There is no such object on the server".Arieariel
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.Eroto
@RonenFestinger Any Workaround #3 ?Iphagenia
@Iphagenia & @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: #9363224Tag
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 domainTag
How do you hug someone on this site!!! Workaround number 2 is crazy efficient!!! You just saved my entire day!Katar
C
15

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.

Castro answered 15/3, 2011 at 14:19 Comment(3)
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.Sporocarp
UserPrincipal.GetAuthorizationGroups() gets choked up if there are deleted groups that the user is still a member ofAgreement
Just check if the group.Name is not null in that case @AgreementAimeeaimil
E
7

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;
    }
}
Entomo answered 17/2, 2016 at 17:46 Comment(0)
W
0

The efficient way is to do a single AD query by having the right DirectorySearcher filter for e.g.

public bool CheckMemberShip(string userName)
    {

        bool membership = false;
        string connection = "LDAP://"+YOURDOMAIN;
        DirectoryEntry entry = new DirectoryEntry(connection);
        DirectorySearcher mySearcher = new DirectorySearcher(entry);
        mySearcher.Filter = "(&(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=cn=GROUPNAME,OU=Groups,OU=ABC,OU=ABC,OU=IND,DC=ad,DC=COMPANY,DC=com)(|(sAMAccountName=" + userName + ")))";
        SearchResult result = mySearcher.FindOne();

        // No search result, hence no membership
        if (result == null)
        {
            membership = false;
        }

        entry.Close();
        entry.Dispose();
        mySearcher.Dispose();

        membership = true;
        return membership;
    }

You need to replace YOURDOMAIN and GROUPNAME with right values from your AD.

Source : How to Recursively Get the Group Membership of a User in Active Directory using .NET/C# and LDAP (without just 2 hits to Active Directory)

Need to include using System.DirectoryServices;

Worm answered 30/7, 2015 at 13:28 Comment(1)
This answer as is will always return true (given that there's a membership=true the line before the return)Felting

© 2022 - 2024 — McMap. All rights reserved.