2011-06-06 51 views
31

我正在寻找获取用户属于Active Directory成员的所有组的列表,这两个组都明确列在memberOf属性列表中,以及隐式地通过嵌套组会员资格。例如,如果我检查UserA并且UserA是GroupA和GroupB的一部分,如果GroupB是GroupC的成员,我也想列出GroupC。使用C#查找递归组成员资格(活动目录)

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

我的问题是,我还没有找到一个有效的方式来使这个查询工作。 Active Directory上的标准文本(This CodeProject Article)显示了一种基本上是递归查找的方法。这似乎非常低效。即使在我的小领域,用户也可能拥有30多个团体成员。这意味着为一个用户拨打30多个Active Directory电话。

我看着下面的LDAP代码来获取所有的memberOf条目中的一次:

(memberOf:1.2.840.113556.1.4.1941:={0}) 

{0}部分将是我的LDAP路径(例如:CN =用户A,OU =用户, DC = FOO,DC = ORG)。但是,它不返回任何记录。这种方法的缺点,即使它的工作,将是我不知道哪个组是明确的,哪些是隐含的。

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

回答

22

渴望感谢这个有趣的问题。

接下来,只是修正,你说:

我看着下面的LDAP代码来获取所有的memberOf条目中的一次:

(memberOf:1.2.840.113556.1.4.1941:={0}) 

你不让它工作。我记得当我知道它的存在时,我就开始工作,但它在LDIFDE.EXE过滤器中。所以我将它应用于C#中的ADSI,它仍然在运行。我从微软拿到的样本中有太多的括号,但它正在工作(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

感谢分享。这看起来很有希望。我远离我可以马上测试的地方(几乎没有互联网),但我会尽快测试,并让你知道我找到了什么。再次感谢。 – IAmTimCorey 2011-06-10 03:02:35

+0

已编辑:我做了一些更多的调查,并添加了微软开始支持这些递归控件的细节。我很奇怪,信息不在根DSE中。 – JPBlanc 2011-06-10 04:51:35

+0

所以。你测试它吗? – JPBlanc 2011-06-15 16:13:45

6

如果除了递归调用没有办法(我不相信有),那么至少您可以让框架为您完成工作:请参阅UserPrincipal.GetAuthorizationGroups method(位于System.DirectoryServices.AccountManagement命名空间并在.Net中引入) 3.5)

该方法搜索所有组 递归并返回组 其中用户是一个成员。 返回的集合还可能包含 其他组,该系统将 认为该用户是授权目的的成员。

GetGroups结果(“返回指定的当前主体是其成员的组组对象的集合”)比较看会员是否是显式或隐式的。

+0

感谢您的回复。在测试此方法时遇到了一些问题。首先,这会得到一些通常不列为组的组(例如“中等强制等级”或与之相近的组)。其次,这只会得到安全组(而不是通讯组)。不幸的是,我需要这两者,尽管如果有类似的方法来获得分发组,那会很好,因为它可能更加细化。 – IAmTimCorey 2011-06-06 14:21:22

2

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

例:

  1. 获取所有基团,其中用户是其成员
  2. 获取其中步骤1组是成员
  3. 获取所有组中的所有基团,其中步骤2组是成员
  4. ...

根据我的经验,很少有更多的5,但应该肯定远低于30.

另外:

  • 确保只有拉 你将需要回去的属性。
  • 缓存结果可以显着地帮助 的性能,但使我的代码更复杂的 。
  • 确保使用连接池。
  • 主组必须另外办理
+0

这是个好主意。谢谢您的帮助。 – IAmTimCorey 2011-06-16 01:11:24

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]); 
    } 
2

您可以利用tokenGroups和性能的tokenGroupsGlobalAndUniversal如果你是Exchange服务器上。 tokenGroups会给你所有该用户所属的安全组,包括嵌套组和域用户,用户等 如果您使用的是.NET 3.5或更高版本的tokenGroupsGlobalAndUniversal将包括一切从tokenGroups和通讯组

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 
        } 
       } 
      } 
     } 
    }