使用C#查找递归组成员资格(Active Directory)

时间:2011-06-06 13:36:11

标签: c# .net active-directory

我希望得到一个用户在Active Directory中成为其成员的所有组的列表,这两个组都明确列在memberOf属性列表中,也隐式通过嵌套组成员身份列出。例如,如果我检查UserA并且UserA是GroupA和GroupB的一部分,我还想列出GroupC,如果GroupB是GroupC的成员。

为了让您更深入地了解我的应用程序,我将在有限的基础上进行此操作。基本上,我偶尔会想要进行安全检查,以便列出这些额外的会员资格。我想区分这两者,但这应该不难。

我的问题是我没有找到一种有效的方法来使这个查询工作。 Active Directory上的标准文本(This CodeProject Article)显示了一种基本上是递归查找的方法。这似乎非常低效。即使在我的小域中,用户也可能拥有30多个组成员资格。这意味着一个用户可以30多次调用Active Directory。

我查看了以下LDAP代码,以便立即获取所有memberOf条目:

(memberOf:1.2.840.113556.1.4.1941:={0})

其中{0}将是我的LDAP路径(例如:CN = UserA,OU = Users,DC = foo,DC = org)。但是,它不会返回任何记录。这种方法的缺点,即使它起作用,也就是我不知道哪个组是明确的,哪个是隐含的。

这就是我到目前为止所拥有的。我想知道是否有比CodeProject文章更好的方法,如果有的话,如何实现(实际代码将是美好的)。我使用的是.NET 4.0和C#。我的Active Directory处于Windows 2008功能级别(它还不是R2)。

6 个答案:

答案 0 :(得分:24)

感谢这个有趣的问题。

接下来,只需要更正,你说:

我查看了以下LDAP代码,以便立即获取所有memberOf条目:

(memberOf:1.2.840.113556.1.4.1941:={0})

你没有让它发挥作用。我记得当我了解它的存在时它让它工作,但它在LDIFDE.EXE过滤器中。所以我将它应用于C#中的ADSI,它仍在工作。我从Microsoft获取的示例中有太多括号,但它正在运行(source in AD Search Filter Syntax)。

根据您关于我们不知道用户是否明确属于该组这一事实的说法,我再添加一个请求。我知道这不是很好,但这是我做得最好的。

static void Main(string[] args)
{
  /* Connection to Active Directory
   */
  DirectoryEntry deBase = new DirectoryEntry("LDAP://WM2008R2ENT:389/dc=dom,dc=fr");


  /* To find all the groups that "user1" is a member of :
   * Set the base to the groups container DN; for example root DN (dc=dom,dc=fr) 
   * Set the scope to subtree
   * Use the following filter :
   * (member:1.2.840.113556.1.4.1941:=cn=user1,cn=users,DC=x)
   */
  DirectorySearcher dsLookFor = new DirectorySearcher(deBase);
  dsLookFor.Filter = "(member:1.2.840.113556.1.4.1941:=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)";
  dsLookFor.SearchScope = SearchScope.Subtree;
  dsLookFor.PropertiesToLoad.Add("cn");

  SearchResultCollection srcGroups = dsLookFor.FindAll();

  /* Just to know if user is explicitly in group
   */
  foreach (SearchResult srcGroup in srcGroups)
  {
    Console.WriteLine("{0}", srcGroup.Path);

    foreach (string property in srcGroup.Properties.PropertyNames)
    {
      Console.WriteLine("\t{0} : {1} ", property, srcGroup.Properties[property][0]);
    }

    DirectoryEntry aGroup = new DirectoryEntry(srcGroup.Path);
    DirectorySearcher dsLookForAMermber = new DirectorySearcher(aGroup);
    dsLookForAMermber.Filter = "(member=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)";
    dsLookForAMermber.SearchScope = SearchScope.Base;
    dsLookForAMermber.PropertiesToLoad.Add("cn");

    SearchResultCollection memberInGroup = dsLookForAMermber.FindAll();
    Console.WriteLine("Find the user {0}", memberInGroup.Count);

  }

  Console.ReadLine();
}

在我的测试树中,这给出了:

LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr
cn : MonGrpSec
Find the user 1

LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpDis
Find the user 1

LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpPlusSec
Find the user 0

LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpPlusSecUniv
Find the user 0

(编辑)的 '1.2.840.113556.1.4.1941'在W2K3 SP1中不起作用,它开始与SP2一起使用。我认为它与W2K3 R2相同。它应该适用于W2K8。我在这里用W2K8R2测试。我很快就可以在W2K8上进行测试了。

答案 1 :(得分:7)

如果除了递归调用之外没有办法(我不相信),那么至少你可以让框架为你完成工作:见UserPrincipal.GetAuthorizationGroups methodSystem.DirectoryServices.AccountManagement命名空间并在.Net 3.5中引入)

  

此方法搜索所有组   递归并返回组   用户是其成员。该   返回集也可能包括   系统会有的其他组   认为用户是for的成员   授权目的。

GetGroups 的结果进行比较(“返回指定当前主体为成员的组的组对象的集合”)以查看成员资格是否明确或隐式的。

答案 2 :(得分:2)

递归使用ldap过滤器,但查询每次查询后返回的所有组,以减少往返次数。

前:

  1. 获取用户所属的所有群组
  2. 获取步骤1群组成员的所有群组
  3. 获取步骤2组成员的所有组
  4. ...
  5. 根据我的经验,很少有超过5个,但绝对应该远远少于30个。

    此外:

    • 确保只拉动属性 你需要回来。
    • 缓存结果可以显着提供帮助 性能,但我的代码很多 更复杂。
    • 确保使用连接池。
    • 必须单独处理小组

答案 3 :(得分:2)

如果您在Exchange服务器上,则可以使用tokenGroups和tokenGroupsGlobalAndUniversal属性。 tokenGroups将为您提供此用户所属的所有安全组,包括嵌套组和域用户,用户等 tokenGroupsGlobalAndUniversal将包含tokenGroups和distribution groups

中的所有内容
private void DoWorkWithUserGroups(string domain, string user)
    {
        var groupType = "tokenGroupsGlobalAndUniversal"; // use tokenGroups for only security groups

        using (var userContext = new PrincipalContext(ContextType.Domain, domain))
        {
            using (var identity = UserPrincipal.FindByIdentity(userContext, IdentityType.SamAccountName, user))
            {
                if (identity == null)
                    return;

                var userEntry = identity.GetUnderlyingObject() as DirectoryEntry;
                userEntry.RefreshCache(new[] { groupType });
                var sids = from byte[] sid in userEntry.Properties[groupType]
                           select new SecurityIdentifier(sid, 0);

                foreach (var sid in sids)
                {
                    using(var groupIdentity = GroupPrincipal.FindByIdentity(userContext, IdentityType.Sid, sid.ToString()))
                    {
                        if(groupIdentity == null)
                            continue; // this group is not in the domain, probably from sidhistory

                        // extract the info you want from the group
                    }
                }
            }
        }
    }

答案 4 :(得分:1)

如果您使用的是.NET 3.5或更高版本,则可以使用System.DirectoryServices.AccountManagement命名空间,这非常容易。

请参阅此处的相关答案:Active Directory nested groups

答案 5 :(得分:0)

    static List<SearchResult> ad_find_all_members(string a_sSearchRoot, string a_sGroupDN, string[] a_asPropsToLoad)
    {
        using (DirectoryEntry de = new DirectoryEntry(a_sSearchRoot))
            return ad_find_all_members(de, a_sGroupDN, a_asPropsToLoad);
    }

    static List<SearchResult> ad_find_all_members(DirectoryEntry a_SearchRoot, string a_sGroupDN, string[] a_asPropsToLoad)
    {
        string sDN = "distinguishedName";
        string sOC = "objectClass";
        string sOC_GROUP = "group";
        string[] asPropsToLoad = a_asPropsToLoad;
        Array.Sort<string>(asPropsToLoad);
        if (Array.BinarySearch<string>(asPropsToLoad, sDN) < 0)
        {
            Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1);
            asPropsToLoad[asPropsToLoad.Length-1] = sDN;
        }
        if (Array.BinarySearch<string>(asPropsToLoad, sOC) < 0)
        {
            Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1);
            asPropsToLoad[asPropsToLoad.Length-1] = sOC;
        }

        List<SearchResult> lsr = new List<SearchResult>();

        using (DirectorySearcher ds = new DirectorySearcher(a_SearchRoot))
        {
            ds.Filter = "(&(|(objectClass=group)(objectClass=user))(memberOf=" + a_sGroupDN + "))";
            ds.PropertiesToLoad.Clear();
            ds.PropertiesToLoad.AddRange(asPropsToLoad);
            ds.PageSize = 1000;
            ds.SizeLimit = 0;
            foreach (SearchResult sr in ds.FindAll())
                lsr.Add(sr);
        }

        for(int i=0;i<lsr.Count;i++)
            if (lsr[i].Properties.Contains(sOC) && lsr[i].Properties[sOC].Contains(sOC_GROUP))
                lsr.AddRange(ad_find_all_members(a_SearchRoot, (string)lsr[i].Properties[sDN][0], asPropsToLoad));

        return lsr;
    }

    static void Main(string[] args)
    {
    foreach (var sr in ad_find_all_members("LDAP://DC=your-domain,DC=com", "CN=your-group-name,OU=your-group-ou,DC=your-domain,DC=com", new string[] { "sAMAccountName" }))
        Console.WriteLine((string)sr.Properties["distinguishedName"][0] + " : " + (string)sr.Properties["sAMAccountName"][0]);
    }