Wednesday, March 09, 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.

Sunday, August 14, 2005 4:45:35 PM (Eastern Daylight Time, UTC-04:00)
Extemporaneous Mumblings
Sunday, August 14, 2005 4:45:35 PM (Eastern Daylight Time, UTC-04:00)
Shame the code doesn't work. Had to add the following:
<br>ds.Filter = String.Format(&quot;(&amp;(objectCategory=person)(sAMAccountName={0}))&quot;, sAMAccountName); --- sAMAccountName was missing
<br>//if (_groups.Length != 0) --- commented out because it prevents the code from running at all because _groups is never populated until this block.
<br>Otherwise it works well, thanks.
Sunday, August 14, 2005 4:45:35 PM (Eastern Daylight Time, UTC-04:00)
Thanks for pointing this out - I had lost the source and tried to recreate at some point and just did a bad job of it (it used to work, I swear!). Anyhow, I updated the post and fixed it.
Sunday, August 14, 2005 4:45:35 PM (Eastern Daylight Time, UTC-04:00)
Damn. That's some hot code right thur.
Sunday, August 14, 2005 4:45:36 PM (Eastern Daylight Time, UTC-04:00)
Any way to search for all org. units?
Tuesday, October 31, 2006 10:08:52 AM (Eastern Standard Time, UTC-05:00)
I am trying to use this in a web app. I am using windows authentication and not the anonymous setting and I set the impersonate=true in web config.
It gives an "An operations error occurred" exception in the FindOne method at the line: "using (SearchResultCollection src = ds.FindAll())"

If you actually log into the box, the code works flawlessly but always gives the error when logging remotely.
Tuesday, October 31, 2006 10:41:51 AM (Eastern Standard Time, UTC-05:00)
Ray, this is an indication that delegation is not setup or it is failing. You have two choices for web apps here, 1.) use a trusted subsystem to run this code. App Pools in IIS6 make this a snap and my recommended approach. Or 2.) setup delegation - which I will post how to do on the blog I think. It is more complicated and probably not necessary.
Comments are closed.