Retrieving Nested Groups for a User in Active Directory
A short time ago I had a situation come up where I needed to determine the Active Directory groups a user had membership in, up to and including the inner-most nested group. Here's the situation:
User A belongs to Group X
Group X is in Group Y
Group Y is in Group Z
and so on...
To determine which groups User A has immediate membership in is trivial, but, from what I could find, there is not a simple, one-liner LDAP query that will return all group memberships including nested groups. A typical LDAP query will return Group X when I really need to know that User A is a member of Group Z. My solution was to recursively query Active Directory until we've parsed the user's entire group membership tree or gone X-levels deep.
/// <summary>
/// This method gets all groups that a user belongs to.
/// </summary>
/// <param name="path">The LDAP path to the Active Directory server.</param>
/// <param name="username">The username of the account for which all groups will be fetched.</param>
/// <param name="accountName">Name of the account performing the search. This should be a service account with the appropriate permissions.</param>
/// <param name="accountPassword">The password to the account performing the search.</param>
/// <param name="iterations">The the number of iterations to perform before exiting a branch.</param>
/// <returns>
/// Returns returns a list of groups to which the user belongs.
/// </returns>
public List<string> GetGroups(string path, string username, string accountName, string accountPassword, int iterations)
{
DirectoryEntry entry = new DirectoryEntry(path, accountName, accountPassword);
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.PropertiesToLoad.Add("memberOf");
List<string> groups = new List<string>();
GetGroupsRecursively(searcher, username, groups, iterations);
return groups;
}
/// <summary>
/// Recursively gets the groups to which a user belongs.
/// </summary>
/// <param name="searcher">The DirectorySearcher performing the search.</param>
/// <param name="filter">The username or group to search for.</param>
/// <param name="groups">The list of groups found.</param>
/// <param name="iteration">The number of iterations left before exiting.</param>
/// <exception cref="System.Exception">Error obtaining group names.</exception>
private void GetGroupsRecursively(DirectorySearcher searcher, string filter, List<string> groups, int iteration)
{
// Exit if the max number of iterations have been performed
if (iteration > 0)
{
try
{
// Perform an Abiguous Name Resolution search
searcher.Filter = "(aNR=" + filter + ")";
SearchResult result = searcher.FindOne();
// Check for failed search
if (result == null)
{
return;
}
for (int i = 0; i < result.Properties["memberOf"].Count; i++)
{
string dn = (string)result.Properties["memberOf"][i];
int equalsIndex = dn.IndexOf("=", 1);
int commaIndex = dn.IndexOf(",", 1);
if (equalsIndex != -1)
{
string role = dn.Substring((equalsIndex + 1), (commaIndex - equalsIndex) - 1);
// Don't add or iterate over a group that has already been found
if (!groups.Contains(role))
{
groups.Add(role);
// Now find all groups that this group belongs to
GetGroupsRecursively(searcher, role, groups, iteration - 1);
}
}
}
}
catch (Exception ex)
{
throw new Exception("Error obtaining group names.", ex);
}
}
}