web-dev-qa-db-fra.com

Rechercher une appartenance à un groupe récursif (Active Directory) à l'aide de C #

Je souhaite obtenir une liste de tous les groupes dont un utilisateur est membre dans Active Directory, à la fois explicitement répertoriés dans la liste de propriétés memberOf et implicitement via l'appartenance à un groupe imbriqué. Par exemple, si j'examine UserA et que UserA fait partie de GroupA et de GroupB, je souhaite également répertorier GroupC si GroupB est membre de GroupC.

Pour vous donner un peu plus de perspicacité dans mon application, je le ferai sur une base limitée. Fondamentalement, je veux de temps en temps un contrôle de sécurité qui listera ces adhésions supplémentaires. Je vais vouloir différencier les deux mais cela ne devrait pas être difficile.

Mon problème est que je n'ai pas trouvé de moyen efficace de faire fonctionner cette requête. Le texte standard sur Active Directory ( Cet article CodeProject ) montre une façon de procéder qui consiste essentiellement en une recherche récursive. Cela semble terriblement inefficace. Même dans mon petit domaine, un utilisateur peut appartenir à plus de 30 membres. Cela signifie plus de 30 appels à Active Directory pour un utilisateur.

J'ai examiné le code LDAP suivant pour obtenir toutes les entrées memberOf à la fois:

(memberOf:1.2.840.113556.1.4.1941:={0})

où {0} serait mon chemin LDAP (ex: CN = UtilisateurA, OU = Utilisateurs, DC = toto, DC = org). Cependant, il ne renvoie aucun enregistrement. L'inconvénient de cette méthode, même si cela fonctionnait, serait que je ne saurais pas quel groupe était explicite et quel groupe était implicite.

C'est ce que j'ai jusqu'à présent. J'aimerais savoir s'il existe un meilleur moyen que l'article de CodeProject et, le cas échéant, comment cela pourrait être accompli (le code réel serait merveilleux). J'utilise .NET 4.0 et C #. Mon Active Directory est au niveau fonctionnel de Windows 2008 (ce n'est pas encore R2).

31
IAmTimCorey

Soif merci pour cette question intéressante.

Ensuite, juste une correction, vous dites:

J'ai examiné le code LDAP suivant pour obtenir toutes les entrées memberOf à la fois:

(memberOf:1.2.840.113556.1.4.1941:={0})

Vous ne le faites pas fonctionner. Je me souviens que je l'ai fait fonctionner lorsque j'ai appris son existence, mais c'était dans un filtre LDIFDE.EXE. Je l’applique donc à ADSI en C # et cela fonctionne toujours. Il y avait trop de parenthèses dans l'échantillon que j'ai pris auprès de Microsoft, mais cela fonctionnait ( source dans Syntaxe de filtre de recherche AD ). 

Selon votre remarque concernant le fait que nous ne savons pas si un utilisateur appartient explicitement au groupe, j'ajoute une demande supplémentaire. Je sais que ce n’est pas très bon, mais c’est le mieux que je puisse faire.

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

Dans mon arbre de test ceci donne:

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

(édité) '1.2.840.113556.1.4.1941' ne fonctionne pas dans W2K3 SP1, il commence à fonctionner avec SP2. Je présume que c'est la même chose avec W2K3 R2. C'est supposé fonctionner sur W2K8. Je teste ici avec W2K8R2. Je vais bientôt pouvoir tester ceci sur W2K8.

24
JPBlanc

S'il n'y a pas d'autre moyen que des appels récursifs (et je ne le crois pas), vous pouvez au moins laisser le framework faire le travail pour vous: voir la méthode UserPrincipal.GetAuthorizationGroups (dans l'espace de noms System.DirectoryServices.AccountManagement et présenté dans .Net 3.5)

Cette méthode recherche tous les groupes De manière récursive et renvoie les groupes dans Dont l'utilisateur est membre. L'ensemble Renvoyé peut également inclure Des groupes supplémentaires que ce système Considérerait l'utilisateur comme un membre de À des fins d'autorisation.

Comparez avec les résultats de GetGroups ("Retourne une collection d'objets groupe qui spécifient les groupes dont le principal actuel est membre") pour voir si l'appartenance est explicite ou implicite.

7
stuartd

vous pouvez utiliser les propriétés tokenGroups et tokenGroupsGlobalAndUniversal si vous êtes sur le serveur Exchange. tokenGroups vous donnera tous les groupes de sécurité auxquels cet utilisateur appartient, y compris les groupes imbriqués et les utilisateurs du domaine, les utilisateurs, etc.

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

Utilisez le filtre LDAP de manière récursive, mais interrogez tous les groupes renvoyés après chaque interrogation pour réduire le nombre d'allers-retours.

Ex: 

  1. Obtenir tous les groupes dont l'utilisateur est membre
  2. Obtenir tous les groupes où les groupes de l'étape 1 sont membres
  3. Obtenir tous les groupes où les groupes de l'étape 2 sont membres
  4. ...

D'après mon expérience, il y en a rarement plus de 5 mais devrait être nettement inférieur à 30.

Également:

  • Assurez-vous de ne tirer que les propriétés Dont vous aurez besoin. 
  • Les résultats de la mise en cache peuvent considérablement améliorer les performances de Mais ont rendu mon code beaucoup plus complexe . 
  • Assurez-vous d'utiliser le regroupement de connexions.
  • Le groupe primaire doit être traité séparément
2
BellBat

Si vous utilisez .NET 3.5 ou supérieur, vous pouvez utiliser l’espace de nom System.DirectoryServices.AccountManagement, ce qui facilite grandement les choses. 

Voir la réponse associée ici: Groupes imbriqués Active Directory

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
Dimitre Chtilianov