wake-up-neo.com

Rekursive Gruppenmitgliedschaft (Active Directory) mithilfe von C # suchen

Ich suche nach einer Liste aller Gruppen, denen ein Benutzer in Active Directory angehört, sowohl explizit in der memberOf-Eigenschaftsliste als auch implizit durch verschachtelte Gruppenmitgliedschaft aufgeführt. Wenn ich beispielsweise untersuche, dass UserA und UserA Teil von GroupA und GroupB sind, möchte ich GroupC auch auflisten, wenn GroupB ein Mitglied von GroupC ist.

Um Ihnen einen besseren Einblick in meine Bewerbung zu geben, werde ich dies auf begrenzter Basis tun. Grundsätzlich möchte ich gelegentlich eine Sicherheitsüberprüfung, die diese zusätzlichen Mitgliedschaften aufführt. Ich möchte die beiden unterscheiden, aber das sollte nicht schwer sein.

Mein Problem ist, dass ich keinen effizienten Weg gefunden habe, um diese Abfrage zu funktionieren. Der Standardtext in Active Directory ( Dieser CodeProject-Artikel ) zeigt eine Möglichkeit, dies im Wesentlichen rekursiv nachzuschlagen. Das scheint furchtbar ineffizient zu sein. Selbst in meiner kleinen Domäne kann ein Benutzer mehr als 30 Gruppenmitgliedschaften haben. Dies bedeutet, dass mindestens 30 Anrufe für einen Benutzer in Active Directory ausgeführt werden.

Ich habe mir den folgenden LDAP-Code angesehen, um alle memberOf-Einträge auf einmal zu erhalten:

(memberOf:1.2.840.113556.1.4.1941:={0})

dabei wäre {0} mein LDAP-Pfad (z. B. CN = UserA, OU = Users, DC = foo, DC = org). Es werden jedoch keine Datensätze zurückgegeben. Der Nachteil dieser Methode wäre, selbst wenn sie funktioniert hätte, dass ich nicht wissen würde, welche Gruppe explizit und welche implizit war.

Das habe ich bis jetzt. Ich würde gerne wissen, ob es einen besseren Weg gibt als der CodeProject-Artikel und wenn ja, wie dies erreicht werden könnte (der eigentliche Code wäre wunderbar). Ich verwende .NET 4.0 und C #. Mein Active Directory hat eine Funktionsebene von Windows 2008 (es ist noch nicht R2.).

31
IAmTimCorey

Vielen Dank dafür eine interessante Frage.

Als nächstes, nur eine Korrektur, sagen Sie:

Ich habe mir den folgenden LDAP-Code angesehen, um alle memberOf-Einträge auf einmal zu erhalten:

(memberOf:1.2.840.113556.1.4.1941:={0})

Du machst es nicht zum Laufen. Ich erinnere mich, dass ich es funktionieren lasse, als ich von seiner Existenz erfuhr, aber es war in einem LDIFDE.EXE-Filter. Also wende ich es in C # auf ADSI an und es funktioniert immer noch. Das von Microsoft entnommene Beispiel enthielt zu viele Klammern, aber es funktionierte ( source in AD-Suchfiltersyntax ). 

Entsprechend Ihrer Bemerkung, dass wir nicht wissen, ob ein Benutzer explizit zur Gruppe gehört, füge ich eine weitere Anfrage hinzu. Ich weiß, das ist nicht sehr gut, aber es ist das Beste, was ich tun kann.

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();
}

In meinem Testbaum geben diese:

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

(bearbeitet) '1.2.840.113556.1.4.1941' funktioniert nicht in W2K3 SP1, es beginnt mit SP2 zu arbeiten. Ich gehe davon aus, dass es mit W2K3 R2 genauso ist. Es soll auf W2K8 funktionieren. Ich teste hier mit W2K8R2. Ich werde das bald auf W2K8 testen können.

24
JPBlanc

Wenn es keine andere Möglichkeit gibt als rekursive Aufrufe (und ich glaube nicht, dass es solche gibt), können Sie zumindest zulassen, dass das Framework die Arbeit für Sie erledigt: siehe die UserPrincipal.GetAuthorizationGroups-Methode (im System.DirectoryServices.AccountManagement-Namespace und eingeführt in .Net 3.5)

Diese Methode durchsucht alle Gruppen rekursiv und gibt die Gruppen in .__ zurück. welcher der Benutzer Mitglied ist. Das Das zurückgegebene Set kann auch .__ enthalten. zusätzliche Gruppen, die das System Betrachten Sie den Benutzer als Mitglied für Genehmigungszwecke.

Vergleichen Sie mit den Ergebnissen von GetGroups("Gibt eine Auflistung von Gruppenobjekten zurück, die die Gruppen angeben, deren aktueller Principal ein Mitglied ist"), um zu sehen, ob die Mitgliedschaft explizit oder implizit ist.

7
stuartd

sie können die Eigenschaften tokenGroups und tokenGroupsGlobalAndUniversal verwenden, wenn Sie sich auf einem Exchange-Server befinden. tokenGroups gibt Ihnen alle Sicherheitsgruppen, denen dieser Benutzer angehört, einschließlich verschachtelter Gruppen und Domänenbenutzer, Benutzer usw. tokenGroupsGlobalAndUniversal enthält alles von tokenGroups UND Verteilergruppen

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
                    }
                }
            }
        }
    }
2
Terry Tsay

Verwenden Sie den ldap-Filter rekursiv, aber fragen Sie nach jeder Abfrage nach allen Gruppen, um die Anzahl der Roundtrips zu reduzieren.

Ex: 

  1. Rufen Sie alle Gruppen ab, in denen der Benutzer Mitglied ist
  2. Rufen Sie alle Gruppen ab, in denen Gruppen von Schritt 1 Mitglieder sind
  3. Rufen Sie alle Gruppen ab, in denen die Gruppen von Schritt 2 Mitglieder sind
  4. ...

Meiner Erfahrung nach gibt es selten mehr als fünf, sollte aber auf jeden Fall weit unter 30 liegen.

Ebenfalls:

  • Stellen Sie sicher, dass Sie nur die Eigenschaften ziehen 
  • Das Zwischenspeichern von Ergebnissen kann die Leistung von __ wesentlich verbessern, machte meinen Code jedoch __ komplizierter. 
  • Stellen Sie sicher, dass Sie das Verbindungspooling verwenden.
  • Primärgruppe muss separat behandelt werden
2
BellBat

Wenn Sie .NET 3.5 oder höher verwenden, können Sie den System.DirectoryServices.AccountManagement-Namespace verwenden, was dies wirklich einfach macht. 

Siehe die zugehörige Antwort hier: geschachtelte Active Directory-Gruppen

1
dave
    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]);
    }
0