2011-06-06 50 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 
        } 
       } 
      } 
     } 
    }