Comment obtenir les groupes d'un utilisateur dans Active Directory? (c #, asp.net)

109

J'utilise ce code pour obtenir les groupes de l'utilisateur actuel. Mais je veux donner manuellement à l'utilisateur, puis obtenir ses groupes. Comment puis-je faire ceci?

using System.Security.Principal;

public ArrayList Groups()
{
    ArrayList groups = new ArrayList();

    foreach (IdentityReference group in System.Web.HttpContext.Current.Request.LogonUserIdentity.Groups)
    {
        groups.Add(group.Translate(typeof(NTAccount)).ToString());
    }

    return groups;
}
Tassisto
la source

Réponses:

163

Si vous utilisez .NET 3.5 ou une version ultérieure, vous pouvez utiliser le nouvel System.DirectoryServices.AccountManagementespace de noms (S.DS.AM), ce qui rend cela beaucoup plus facile qu'auparavant.

Pour en savoir plus, cliquez ici: Gestion des principaux de sécurité d'annuaire dans .NET Framework 3.5

Mise à jour: les anciens articles du magazine MSDN ne sont plus en ligne, malheureusement - vous devrez télécharger le CHM pour le magazine MSDN de janvier 2008 de Microsoft et lire l'article qui s'y trouve.

Fondamentalement, vous devez avoir un "contexte principal" (généralement votre domaine), un principal utilisateur, puis vous obtenez ses groupes très facilement:

public List<GroupPrincipal> GetGroups(string userName)
{
   List<GroupPrincipal> result = new List<GroupPrincipal>();

   // establish domain context
   PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);

   // find your user
   UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, userName);

   // if found - grab its groups
   if(user != null)
   {
      PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();

      // iterate over all groups
      foreach(Principal p in groups)
      {
         // make sure to add only group principals
         if(p is GroupPrincipal)
         {
             result.Add((GroupPrincipal)p);
         }
      }
   }

   return result;
}

et c'est tout ce qu'il y a! Vous avez maintenant un résultat (une liste) des groupes d'autorisation auxquels l'utilisateur appartient - parcourez-les, imprimez leurs noms ou tout ce que vous devez faire.

Mise à jour: pour accéder à certaines propriétés, qui ne sont pas visibles sur l' UserPrincipalobjet, vous devez creuser dans le sous-jacent DirectoryEntry:

public string GetDepartment(Principal principal)
{
    string result = string.Empty;

    DirectoryEntry de = (principal.GetUnderlyingObject() as DirectoryEntry);

    if (de != null)
    {
       if (de.Properties.Contains("department"))
       {
          result = de.Properties["department"][0].ToString();
       }
    }

    return result;
}

Mise à jour n ° 2: semble ne devrait pas être trop difficile de mettre ces deux extraits de code ensemble ... mais ok - voilà:

public string GetDepartment(string username)
{
    string result = string.Empty;

    // if you do repeated domain access, you might want to do this *once* outside this method, 
    // and pass it in as a second parameter!
    PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);

    // find the user
    UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, username);

    // if user is found
    if(user != null)
    {
       // get DirectoryEntry underlying it
       DirectoryEntry de = (user.GetUnderlyingObject() as DirectoryEntry);

       if (de != null)
       {
          if (de.Properties.Contains("department"))
          {
             result = de.Properties["department"][0].ToString();
          }
       }
    }

    return result;
}
marc_s
la source
@Tassisto: malheureusement, cette propriété n'est pas disponible directement sur le UserPrincipal- voir ma réponse mise à jour pour savoir comment y accéder.
marc_s
J'ai besoin de donner le nom d'utilisateur pour obtenir la valeur de son département-field
Tassisto
@Tassito: eh bien 1) créer un contexte de domaine, 2) trouver cet utilisateur par son nom et 3) utiliser mon extrait de code pour obtenir son département
marc_s
1
La méthode GetGroups n'a pas fonctionné pour moi, j'ai changé le nouveau contexte principal pour utiliser une autre surcharge du constructeur comme suit: PrincipalContext yourDomain = new PrincipalContext (ContextType.Domain, "192.168.2.23", "domain \ user", "password" ); c'est tout à fait logique car vous n'êtes pas toujours connecté via l'authentification Active Directory. J'espère que ça aide
Omid S.
2
Cette réponse est excellente. Il est également possible de simplifier l'itération des groupes à: result.AddRange (user.GetAuthorizationGroups (). OfType <GroupPrincipal> ()
tlbignerd
59

GetAuthorizationGroups()ne trouve pas les groupes imbriqués. Pour vraiment obtenir tous les groupes dont un utilisateur donné est membre (y compris les groupes imbriqués), essayez ceci:

using System.Security.Principal

private List<string> GetGroups(string userName)
{
    List<string> result = new List<string>();
    WindowsIdentity wi = new WindowsIdentity(userName);

    foreach (IdentityReference group in wi.Groups)
    {
        try
        {
            result.Add(group.Translate(typeof(NTAccount)).ToString());
        }
        catch (Exception ex) { }
    }
    result.Sort();
    return result;
}

J'utilise try/catchparce que j'ai eu quelques exceptions avec 2 groupes sur 200 dans un très grand AD car certains SID n'étaient plus disponibles. (L' Translate()appel effectue une conversion SID -> Nom.)

Mickey la souris
la source
3
les performances ont été améliorées en utilisant cette technique au lieu de passer par AD. Merci!
Philippe
GetAuthorisationGroups () est super lent pour moi, c'est-à-dire 26 et tous les autres codes que j'ai trouvés jusqu'à présent n'incluaient pas les identifiants bien connus tels que Tout le monde, Utilisateurs du domaine, etc ... Le code que vous avez fourni est littéralement instantané et inclut tous les sids, oui seulement les sids mais c'est ce dont j'ai besoin, y compris les plus connus et personnalisés!
Thierry le
19

Tout d'abord, GetAuthorizationGroups () est une excellente fonction mais présente malheureusement 2 inconvénients:

  1. Les performances sont médiocres, en particulier dans les grandes entreprises avec de nombreux utilisateurs et groupes. Il récupère beaucoup plus de données que ce dont vous avez réellement besoin et fait un appel au serveur pour chaque itération de boucle dans le résultat
  2. Il contient des bogues qui peuvent empêcher votre application de fonctionner «un jour» lorsque les groupes et les utilisateurs évoluent. Microsoft a reconnu le problème et est lié à certains SID. L'erreur que vous obtiendrez est "Une erreur s'est produite lors de l'énumération des groupes"

Par conséquent, j'ai écrit une petite fonction pour remplacer GetAuthorizationGroups () par de meilleures performances et une sécurité d'erreur. Il ne fait qu'un seul appel LDAP avec une requête utilisant des champs indexés. Il peut être facilement étendu si vous avez besoin de plus de propriétés que les seuls noms de groupe (propriété "cn").

// Usage: GetAdGroupsForUser2("domain\user") or GetAdGroupsForUser2("user","domain")
public static List<string> GetAdGroupsForUser2(string userName, string domainName = null)
{
    var result = new List<string>();

    if (userName.Contains('\\') || userName.Contains('/'))
    {
        domainName = userName.Split(new char[] { '\\', '/' })[0];
        userName = userName.Split(new char[] { '\\', '/' })[1];
    }

    using (PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, domainName))
        using (UserPrincipal user = UserPrincipal.FindByIdentity(domainContext, userName))
            using (var searcher = new DirectorySearcher(new DirectoryEntry("LDAP://" + domainContext.Name)))
            {
                searcher.Filter = String.Format("(&(objectCategory=group)(member={0}))", user.DistinguishedName);
                searcher.SearchScope = SearchScope.Subtree;
                searcher.PropertiesToLoad.Add("cn");

                foreach (SearchResult entry in searcher.FindAll())
                    if (entry.Properties.Contains("cn"))
                        result.Add(entry.Properties["cn"][0].ToString());
            }

    return result;
}
Bigjim
la source
Impressionnant! Merci. J'ai commencé à écrire du code et j'utilisais GetAuthorizationGroups et j'étais horrifié de voir qu'il fallait 300 ms à 2,5 s pour obtenir tous les groupes. Votre méthode se fait en 20-30 ms.
Keith
4
Cela semblait prometteur, mais cela ne résout pas les groupes imbriqués, par exemple un utilisateur est membre du groupe a, qui est lui-même membre du groupe x. Le code ci-dessus affichera simplement le groupe a, mais pas le groupe x. J'ai utilisé cette méthode via tokenGroups: stackoverflow.com/a/4460658/602449
Robert Muehsig
Jetez un œil au commentaire de Robert Muehsig - cela fait des groupes imbriqués et est encore plus rapide. Seul inconvénient, il ne renvoie que les groupes de sécurité et non les groupes de distribution
Nick Rubino
@bigjim Impossible d'utiliser GetAuthorizationGroups car il faut près de 6 secondes pour renvoyer ses données mais le code que vous avez fourni ne renvoie pas de groupes bien connus tels que Tout le monde, Utilisateurs du domaine, etc. et j'ai besoin de ceux-ci. Tout semble ne renvoyer que des "groupes personnalisés" et non pas tous les groupes auxquels appartient un utilisateur.
Thierry le
11

Dans l'AD, chaque utilisateur a une propriété memberOf. Celui-ci contient une liste de tous les groupes auxquels il appartient.

Voici un petit exemple de code:

// (replace "part_of_user_name" with some partial user name existing in your AD)
var userNameContains = "part_of_user_name";

var identity = WindowsIdentity.GetCurrent().User;
var allDomains = Forest.GetCurrentForest().Domains.Cast<Domain>();

var allSearcher = allDomains.Select(domain =>
{
    var searcher = new DirectorySearcher(new DirectoryEntry("LDAP://" + domain.Name));

    // Apply some filter to focus on only some specfic objects
    searcher.Filter = String.Format("(&(&(objectCategory=person)(objectClass=user)(name=*{0}*)))", userNameContains);
    return searcher;
});

var directoryEntriesFound = allSearcher
    .SelectMany(searcher => searcher.FindAll()
        .Cast<SearchResult>()
        .Select(result => result.GetDirectoryEntry()));

var memberOf = directoryEntriesFound.Select(entry =>
{
    using (entry)
    {
        return new
        {
            Name = entry.Name,
            GroupName = ((object[])entry.Properties["MemberOf"].Value).Select(obj => obj.ToString())
        };
    }
});

foreach (var item in memberOf)
{
    Debug.Print("Name = " + item.Name);
    Debug.Print("Member of:");

    foreach (var groupName in item.GroupName)
    {
        Debug.Print("   " + groupName);
    }

    Debug.Print(String.Empty);
}
}
Oliver
la source
1
@Tassisto: Oui, il vous comprend. L'extrait de code ci-dessus fonctionnera exactement comme vous le souhaitez. Remplacez simplement la boucle foreach finale par une boucle qui génère une liste des noms de groupe au lieu de l'impression de débogage.
Joel Etherton
2
Il échouera à répertorier le groupe principal de l'utilisateur (souvent les utilisateurs du domaine). Vous devez revenir en arrière et rechercher ces informations séparément. GetAuthorizationGroups n'a pas ce problème.
Andy
1

Dans mon cas, la seule façon pour moi de continuer à utiliser GetGroups () sans aucune suppression était d'ajouter l'utilisateur (USER_WITH_PERMISSION) au groupe qui a l'autorisation de lire l'AD (Active Directory). Il est extrêmement essentiel de construire le PrincipalContext en passant cet utilisateur et ce mot de passe.

var pc = new PrincipalContext(ContextType.Domain, domain, "USER_WITH_PERMISSION", "PASS");
var user = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, userName);
var groups = user.GetGroups();

Étapes que vous pouvez suivre dans Active Directory pour le faire fonctionner:

  1. Dans Active Directory, créez un groupe (ou prenez-en un) et sous l'onglet de sécurité, ajoutez "Groupe d'accès aux autorisations Windows"
  2. Cliquez sur le bouton "Avancé"
  3. Sélectionnez "Groupe d'accès aux autorisations Windows" et cliquez sur "Afficher"
  4. Cochez "Lire tokenGroupsGlobalAndUniversal"
  5. Localisez l'utilisateur souhaité et ajoutez-le au groupe que vous avez créé (pris) à partir de la première étape
Gandarez
la source
1
Cela entre probablement en jeu si vous utilisez des comptes intégrés pour un compte de pool de services / applications dans votre application Web. Si vous utilisez un compte de domaine comme compte de pool de services / applications, ou si vous empruntez l'identité d'un compte de domaine dans le code, il doit disposer de droits de lecture par défaut et ne pas avoir ce problème.
vapcguy
1

Cela fonctionne pour moi

public string[] GetGroupNames(string domainName, string userName)
    {
        List<string> result = new List<string>();

        using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, domainName))
        {
            using (PrincipalSearchResult<Principal> src = UserPrincipal.FindByIdentity(principalContext, userName).GetGroups())
            {
                src.ToList().ForEach(sr => result.Add(sr.SamAccountName));
            }
        }

        return result.ToArray();
    }
Taran
la source
1

La réponse dépend du type de groupes que vous souhaitez récupérer. L' System.DirectoryServices.AccountManagementespace de noms fournit deux méthodes de récupération de groupe:

GetGroups - Renvoie une collection d'objets de groupe qui spécifient les groupes dont le principal actuel est membre.

Cette méthode surchargée ne renvoie que les groupes dont le principal est directement membre; aucune recherche récursive n'est effectuée.

GetAuthorizationGroups - Renvoie une collection d'objets principaux qui contient tous les groupes d'autorisation dont cet utilisateur est membre. Cette fonction renvoie uniquement les groupes qui sont des groupes de sécurité; les groupes de distribution ne sont pas renvoyés.

Cette méthode recherche tous les groupes de manière récursive et renvoie les groupes dont l'utilisateur est membre. L'ensemble renvoyé peut également inclure des groupes supplémentaires dont le système considérerait l'utilisateur comme membre à des fins d'autorisation.

Donc, GetGroupsobtient tous les groupes dont l'utilisateur est un membre direct et GetAuthorizationGroupsobtient tous les groupes d' autorisation dont l'utilisateur est un membre direct ou indirect .

Malgré la façon dont ils sont nommés, l'un n'est pas un sous-ensemble de l'autre. Il peut y avoir des groupes renvoyés par GetGroupsnon retournés par GetAuthorizationGroupset vice versa.

Voici un exemple d'utilisation:

PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, "MyDomain", "OU=AllUsers,DC=MyDomain,DC=Local");
UserPrincipal inputUser = new UserPrincipal(domainContext);
inputUser.SamAccountName = "bsmith";
PrincipalSearcher adSearcher = new PrincipalSearcher(inputUser);
inputUser = (UserPrincipal)adSearcher.FindAll().ElementAt(0);
var userGroups = inputUser.GetGroups();
Tawab Wakil
la source
1

Ma solution:

UserPrincipal user = UserPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain, myDomain), IdentityType.SamAccountName, myUser);
List<string> UserADGroups = new List<string>();            
foreach (GroupPrincipal group in user.GetGroups())
{
    UserADGroups.Add(group.ToString());
}
Darcu
la source
0

Dans le cas où Translate fonctionne localement mais pas à distance, ei group. Traduire (typeof (NTAccount)

Si vous souhaitez que le code d'application s'exécute à l'aide de l'identité LOGGED IN USER, activez l'emprunt d'identité. L'emprunt d'identité peut être activé via IIS ou en ajoutant l'élément suivant dans le fichier web.config .

<system.web>
<identity impersonate="true"/>

Si l'emprunt d'identité est activé, l'application s'exécute en utilisant les autorisations trouvées dans votre compte d'utilisateur. Donc, si l'utilisateur connecté a accès à une ressource réseau spécifique, alors seulement il pourra accéder à cette ressource via l'application.

Merci à PRAGIM tech pour ces informations de sa vidéo diligente

Authentification Windows dans asp.net Partie 87:

https://www.youtube.com/watch?v=zftmaZ3ySMc

Mais l'usurpation d'identité crée beaucoup de frais généraux sur le serveur

La meilleure solution pour autoriser les utilisateurs de certains groupes réseau est de refuser les anonymes dans la configuration web <authorization><deny users="?"/><authentication mode="Windows"/>

et dans votre code derrière, de préférence dans le global.asax, utilisez le HttpContext.Current.User.IsInRole :

Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
If HttpContext.Current.User.IsInRole("TheDomain\TheGroup") Then
//code to do when user is in group
End If

REMARQUE: Le groupe doit être écrit avec une barre oblique inverse \ ie "TheDomain \ TheGroup"

Pierre-David Sabourin
la source