Recursive Active Directory group membership using System.DirectoryServices in .NET 3.5

When coding for an organization that uses Active Directory, you will eventually come across the business case for using groups within groups, i.e. a  user is part of say, the HR-Administration group, and that group is itself a member of the HR-Department group. You may then need to secure your application to only allow members of HR-Department to access certain functionality within it,  and so you need an IsUserInGroup function, but one that looks recursively at groups that are members of the requested group.

Over the years I’ve had to come up with such a function in various technologies: VB6 using ADSI, PHP using the php_ldap extension, and System.DirectoryServices in .NET 2.0 with a custom function for recursing.

A few weeks ago I was using .NET 3.5 and decided to look around again to see if there was an easy alternative. It seems the 3.5 release comes with a revamped System.DirectoryServices which makes things much easier for common use cases. And although there is no function to achieve what we need, there is one that helps us do it: group.GetMembers(true).  The parameter when set to true triggers a recursive search of all groups that are members of the current group, and grabs their members too. So it becomes trivial to achieve an IsUserInGroup function:

using System.DirectoryServices.AccountManagement;

...

public static bool IsUserInGroup(string username, string groupname)
{
    var foundUser = false;
    using (var context = new PrincipalContext(ContextType.Domain, "microsoft.com"))
    using (var group = GroupPrincipal.FindByIdentity(context, groupname))
    {
        if (group == null)
        {
            throw new ArgumentException("Group could not be found: " + groupname);
        }

        // GetMembers(true) is recursive (groups-within-groups)
        foreach (var member in group.GetMembers(true))
        {
            if (member.SamAccountName.Equals(username))
            {
                foundUser = true;
                break;
            }
        }
    }
    return foundUser;
}
This entry was posted in Technology. Bookmark the permalink. Both comments and trackbacks are currently closed.

3 Comments

  1. Richard
    Posted 2010/11/01 at 3:57 pm | Permalink

    “group.Dispose();
    context.Dispose();”

    Ever heard of the “using” block?

    using (var context = new PrincipalContext(...))
    using (var group = GroupPrincipal.FindByIdentity(...))
    {
    ...
    }

    “throw new Exception(…)”

    NOOOOOO! You should *never* catch or throw System.Exception; you should always use a more suitable derived type. In this case, an ArgumentException would be a sensible option. If you “throw new Exception(…)”, the calling code has no way of differentiating a “group not found” error from a “stack overflow”, “out of memory” or “thread aborted” error.

  2. Posted 2011/01/22 at 2:43 pm | Permalink

    Thanks Richard, I updated the example code snippet as per your suggestions.

  3. Varin
    Posted 2011/02/25 at 1:45 am | Permalink

    Nice and clean code. I think that enumerating through users in a group might take a long time if the group has thousands of users. Is there a way to use context reference to find a user similar to finding the group as shown in your code?