Wednesday, March 9, 2005

Enumerating Token Groups (tokenGroups) in .NET

The 'tokenGroups' attribute is a calculated attribute (we must use .RefreshCache() to get it) that exists for all users in Active Directory.  It contains a collection of SIDs for each security group that the user is a member of.  The advantage of this collection is that it only contains security groups, and it contains all security groups including nested and primary groups.  The disadvantage is that it is a little bit more complicated to do anything with this attribute.

There are two methods of enumerating the tokenGroups and returning the security groups for a user.  The first method is to use DsCrackNames on collection of SIDs and have the Win32 api return the groups in your choice of name formats.  This is a powerful and fast method, but you will need to rely on p/invoke and setting up some structures.  The other method is to build an LDAP query filter and then use the DirectorySearcher to find all the groups.  This method returns a SearchResult for each group which means you could additionally retrieve more information about the group as well as does not require any p/invoke code, so it is usually more palatable for users.

Here are the steps we would take to enumerate the groups:

1.Create a DirectoryEntry to serve as the SearchRoot for our DirectorySearcher
2.Bind to our user object with another DirectoryEntry and pull the 'tokenGroups' attribute
3.Iterate over each SID in the tokenGroup and build the LDAP filter (formatting the SID bytes correctly)
4.Search the Directory with the constructed filter
5.Iterate each returned SearchResult for your information.

Here is a sample VS.NET solution in C# that demonstrates this:  Enumerate Token Groups

The code for how to do this using DsCrackNames I will leave for another post. (UPDATESee here for DsCrackNames)

Updated 3/16/2005 - I realize that not everyone feels like digging around to find their GUID to use this sample (I initially created it for someone that only had their GUID), so I revisited this so it also accepts the user's login name as well. Download the updated example

Updated 7/13/2005 – Reader “Daniel” was kind enough to point out some errors that I had in my code.  I seem to have deleted my original code at some point and was recreating it from scratch… so I apparently forgot some very key things in the code.  My bad!  I should have tested it better.  These errors have been corrected.