Who Am I?
As we probably know, the most popular activity involving the Active Directory for the developers amongst us is to check for authorization. We get a username from the context/environment/browser, and now we wanna check if the user is allowed to do whatever. We may use the role-based security solutions provided by COM+ or .NET or whatever solution we want, and they are nice and fine and good, but there comes a time in every programmer's life when he must lay down the line and say “That's it. I'm going in there. Just me and System.DirectoryServices. If I'm not out in 30 minutes, contact my administrator”.
So first, a word of warning - System.DirectoryServices is a COM wrapper. It is NOT managed code. Windows has a lovely library for accessing the AD and other LDAP providers, and had it for years - ADSI in its various version and incarnations. A nice API, all in all. DirectoryServices in the BCL is just a set of wrappers over ADSI, not a brand-new implementation of LDAP. This means that there is a lot of COM interop going on in the background. This shouldn't be too worrisome, since COM interop isn't a bad thing, but it is important to dispose what you instantiate and make sure you don't leave things lying around.
There, having established that, we will quickly learn to create a DirectoryEntry for our user (either directly or using the DirectorySearcher - no need to elaborate there (though I can, if people ask me to)) and we can now check the properties for the “memberOf” property and see what groups he is a member of. This works well, except that we only get the groups we are DIRECT members of. If we want a complete list of nested groups, we have to start recursing and iterating, and this takes a LONG time. Especially when we have a lot of groups.
So what we do? We use a little-known and barely-documented feature of our Windows authentication architecture.
You see, when we try to access a network resource, for instance, the resource has to know whether we are authorized or not. It does this by receiving from the client (our user) a set of security credentials that contain the Security IDs (SIDs) of all the groups we belong to, whether directly on indirectly - we can't have Windows recurse through the AD whenever we try to access some folder, right?
So we'll use the same mechanism. Among the many properties available for a DirectoryEntry object in the ActiveDirectory is a property called “tokenGroups” which contains a list of all SIDs of all security groups the user belongs to. This list is flat, since it is used for direct authorization only, not for complex rules inherent in our organizational hierarchy. This list of SIDs can then be translated into group names for our enumerating convenience, or perhaps we should just get the SID for the group we are checking and see if it is the list.
A few points of interest:
1) Since the tokenGroups property is relatively heavy, it is not automatically loaded when we create a new DirectoryEntry. To do so, we will call the entry's RefreshCache() method to explicitly load that property:
DirectoryEntry de = new DirectoryEntry(my LDAP path);
de.RefreshCache(new string[] {“tokenGroups“});
2) Once we loaded the property, it is represented as an array of byte arrays, so we will get it like this:
PropertyValueCollection tg = de.Properties["tokenGroups"];
foreach (byte[] SID in (Array)tg.Value)
{
// Translate the SID, or whatever.
}
1) To get a group name from a SID (which is basically an array of bytes), we'll have to do a little Win32 interop using the LookupAccountSid function.
enum SID_Types
{
SidTypeUser = 1,
SidTypeGroup,
SidTypeDomain,
SidTypeAlias,
SidTypeWellKnownGroup,
SidTypeDeletedAccount,
SidTypeInvalid,
SidTypeUnknown,
SidTypeComputer
}
[DllImport("advapi32.dll")]
private static extern bool LookupAccountSid(
string lpSystemName,
byte[] lpSid,
StringBuilder lpName,
ref int cchName,
StringBuilder lpReferencedDomainName,
ref int cchReferencedDomainName,
ref SID_Types peUse
);
private string getNameFromSID (byte[] SID)
{
int
int DomainLength = 256;
StringBuilder Groupname = new StringBuilder(GroupLength);
StringBuilder Domainname = new StringBuilder(DomainLength);
SID_Types AccountType = SID_Types.SidTypeUnknown;
LookupAccountSid(null, SID, Groupname, ref GroupLength, Domainname, ref DomainLength, ref AccountType);
return Domainname.ToString() + @”\” + Groupname.ToString();
}
1 Comment
Comments have been disabled for this content.
Cameron said
The list is incomplete. I have a user whom is a member of 20+ groups. I run that logic againist this user and get a grand total of 6 groups. Do you know of any way to get a flat list of _ALL_ of the groups?