Help! I\'ve been trying to write a function that will confirm a user\'s membership in an Active Directory group, and while it works if the member happens to be in the group,
A minor modification top the code from marc_s, I have:
using (var ctx = new PrincipalContext(ContextType.Domain))
using (var userPrincipal = UserPrincipal.FindByIdentity(ctx, user))
using (var groupPrincipal = GroupPrincipal.FindByIdentity(ctx, group))
{
if (userPrincipal == null) return false;
if (groupPrincipal == null) return false;
PrincipalSearchResult<Principal> result = userPrincipal.GetGroups();
Principal grp = result.Where(g => g.Sid == groupPrincipal.Sid).FirstOrDefault();
return (grp != null);
}
Comparing Sid seems to work more reliably than comparing SamAccountName.
We had a bit of a poison group in our setup which caused this to fail for some users but not others. The "FirstOrDefault" logic in the other suggested answers MIGHT have helped us dodge the poison group, but that is no guarantee.
We have two suggestions for others with this problem. First check if you have any groups with a forward slash in the group name (the actual group name, not the "pre-windows 2000" name which will replace it with an underscore). If you can rename all such groups that might fix your problem ... it worked for us.
This workaround also was working for us:
/// <summary>
/// This does a recursive group search for the given user or computer principal.
/// </summary>
public IEnumerable<Principal> GetGroups(Principal principal)
{
return GetGroups(null, principal);
}
private IEnumerable<Principal> GetGroups(HashSet<SecurityIdentifier> ancestorPrincipalSids, Principal parentPrincipal)
{
try
{
//enumerate this here so errors are thrown now and not later
//if the current group name has a forward-slash, I think this
//will always error here
var groups = parentPrincipal.GetGroups().ToArray();
if (groups == null)
{
return Enumerable.Empty<Principal>();
}
//keep track of all ancestors in the group hierarchy to this point
//so that we can handle circular references below
var newAncestors = new HashSet<SecurityIdentifier>(ancestorPrincipalSids ?? Enumerable.Empty<SecurityIdentifier>());
newAncestors.Add(parentPrincipal.Sid);
return groups
.Concat(groups
.Where(g => !newAncestors.Contains(g.Sid)) //handle circular references
.SelectMany(g => GetGroups(newAncestors, g)));
}
catch
{
return Enumerable.Empty<Principal>();
}
}
I think you could simplify things a bit:
private bool IsUserMemberOfGroup(string user, string group)
{
using (var ctx = new PrincipalContext(ContextType.Domain))
using (var userPrincipal = UserPrincipal.FindByIdentity(ctx, user))
{
PrincipalSearchResult<Principal> result = userPrincipal.GetGroups();
GroupPrincipal groupPrincipal =
result.Where(g => g.SamAccountName == groupName).FirstOrDefault();
return (groupPrincipal != null);
}
}
The userPrincipal.GetGroups()
will give you a definitive list of all group memberships (including primary group and nested group memberships) for that user; then search that list for the group you're interested in, e.g. by samACcountName or some other property.
If you find the group you're looking for in the PrincipalSearchResult<Principal>
returned by GetGroups()
, then your user is a member of that group.
You can save yourself at least one "FindByIdentity" call with this.