Recursively querying LDAP group membership

2019-02-17 18:15发布

问题:

I'm writing an MVC-based (.NET 4.0) website that requires login credentials from my corporate LDAP server. What my code requires is to allow only the users that are part of a certain group. As an example, I could be looking for users that are part of the "Corporate IT" group. My credentials could be part of the "System Admins" group which is a subgroup of "Corporate IT". I'm using Forms Authentication.

How would I recursively check what group a user is under when they log in?

回答1:

For anybody else coming here from a search for this type of query, here is how I did it in my application:

The key is 1.2.840.113556.1.4.1941 extended search filter. Since this particular filter works with DNs only, I first get hold of DN of the user I want to check and then query groups to see if this particular user is a member of any of groups in chain.

internal const string UserNameSearchFilter = "(&(objectCategory=user)(objectClass=user)(|(userPrincipalName={0})(samAccountName={0})))";
internal const string MembershipFilter = "(&(objectCategory=group)(objectClass=group)(cn=MyGroup)(member:1.2.840.113556.1.4.1941:={0}))";

using (var de = new DirectoryEntry(AppSettings.LDAPRootContainer, AppSettings.AdminUser, AppSettings.AdminPassword, AuthenticationTypes.FastBind))
using (var ds = new DirectorySearcher(de) { Filter = string.Format(UserNameSearchFilter, username) })
{

    ds.PropertiesToLoad.AddRange(new[] { "distinguishedName" });

    var user = ds.FindOne();

    if (user != null)
        using (var gds = new DirectorySearcher(de) { PropertyNamesOnly = true, Filter = string.Format(MembershipFilter, user.Properties["distinguishedName"][0] as string) })
        {
             gds.PropertiesToLoad.AddRange(new[] { "objectGuid" });
             return gds.FindOne() != null;
        }
}


回答2:

If you want to check for the membership of a specific user, bind to the AD object in question and retrieve the tokenGroups attribute. It contains all direct and indirect group memberships in binary form - it's an array of byte arrays. Each byte array can be passed ot the constructor of the SecurityIdentifier class and subsequently being converted to an NTAccount which contains the name of the group in cleartext.

var sids = new IdentityReferenceCollection();
foreach (byte[] group in tokenGroups)
{
    sids.Add(new SecurityIdentifier(group, 0));
}
var accounts = sids.Translate(typeof(NTAccount));


回答3:

Here's a completely different solution. Tested and working on my domain. A few notes: you will have to get your DirectorySearcher.Filter correct. Add multiple OUs for your AD hierarchy (in reverse order, bottom up). Also note, to be safe, I'm disposing of a few object in "using" statements, since they implement the System.ComponentModel.Component, which in turn implements IDisposable... so, better safe than sorry.

public bool IsUserMemberOfGroup(string groupName)
    {
        // CN is your distro group name. OU is the object(s) in your AD hierarchy. DC is for your domain and domain suffix (e.g., yourDomain.local)
        string searchFilter = String.Format(@"(&(objectcategory=user)(sAMAccountName=markp)(memberof=CN={0},OU=System Admins,OU=USA,DC=yourDomain,DC=local))", groupName);
        SearchResultCollection searchResult;

        using (var dirEntry = new DirectoryEntry("LDAP://dc=yourDomain,dc=local"))
        {
            using (var dirSearch = new DirectorySearcher(dirEntry))
            {
                dirSearch.SearchScope = SearchScope.Subtree;
                dirSearch.Filter = searchFilter;
                searchResult = dirSearch.FindAll();
            }
        }
        if (searchResult.Count <= 0 || searchResult == null) {
            return false; // not in group
        }
        else {
            return true; // in group
        }
    }