UserPrincipal.GetAuthorizationGroups()…oh my!
The problem
There is a really convenient method that can be used to retrieve a flat list of all the groups (event nested) given a known UserPrincipal, e.g. the current user, the really amazing thing is that the method saves you from doing a group recursion to retrieve the groups relationships in order to detect that a user belongs to a group A that belongs to a group B.
The above method utilizes, as far as I know, the security tokens attached to a given user principal and then resolves the group names given the group SID.
The problem is that that method can fail with a really strange exception “NoMatchingPrincipalException” (the group cannot be found) that is typically related to 2 different scenarios:
- One, or more, of the group has SID History enabled;
- In a complex domain topology one of the DC is not available at the moment of the query;
To make a long story short: we simply cannot rely on that method, period.
The solution
IEnumerable<String> GetGroups( String samAccountName )
{
var userNestedMembership = new List<string>();
var domainConnection = new DirectoryEntry();
domainConnection.AuthenticationType = System.DirectoryServices.AuthenticationTypes.Secure;
var samSearcher = new DirectorySearcher();
samSearcher.SearchRoot = domainConnection;
samSearcher.Filter = "(samAccountName=" + samAccountName + ")";
samSearcher.PropertiesToLoad.Add( "displayName" );
var samResult = samSearcher.FindOne();
if ( samResult != null )
{
var theUser = samResult.GetDirectoryEntry();
theUser.RefreshCache( new string[] { "tokenGroups" } );
foreach ( byte[] resultBytes in theUser.Properties[ "tokenGroups" ] )
{
var SID = new SecurityIdentifier( resultBytes, 0 );
var sidSearcher = new DirectorySearcher();
sidSearcher.SearchRoot = domainConnection;
sidSearcher.Filter = "(objectSid=" + SID.Value + ")";
sidSearcher.PropertiesToLoad.Add( "name" );
var sidResult = sidSearcher.FindOne();
if ( sidResult != null )
{
userNestedMembership.Add( ( string )sidResult.Properties[ "name" ][ 0 ] );
}
}
}
return userNestedMembership;
}
A bunch of old good directory services related code :-) where the really really really, have I said really?, important piece is the following:
theUser.RefreshCache( new string[] { "tokenGroups" } );
That asks to the underlying system to refresh the cache of the groups security tokens attached to the given user.
.m