Come ottenere i gruppi di un utente in Active Directory? (c #, asp.net)


109

Uso questo codice per ottenere i gruppi dell'utente corrente. Ma voglio dare manualmente l'utente e poi ottenere i suoi gruppi. Come posso fare questo?

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

Risposte:


163

Se utilizzi .NET 3.5 o versioni successive, puoi utilizzare il nuovo System.DirectoryServices.AccountManagement versioni spazio dei nomi (S.DS.AM) che lo rende molto più semplice di quanto non fosse in passato.

Leggi tutto qui: Gestione delle entità di protezione delle directory in .NET Framework 3.5

Aggiornamento: i vecchi articoli della rivista MSDN non sono più online, sfortunatamente: dovrai scaricare CHM per la rivista MSDN di gennaio 2008 da Microsoft e leggere l'articolo in esso contenuto.

Fondamentalmente, devi avere un "contesto principale" (in genere il tuo dominio), un'entità utente e quindi ottenere i suoi gruppi molto facilmente:

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

e questo è tutto quello che c'è! Ora hai un risultato (un elenco) di gruppi di autorizzazione a cui appartiene l'utente: iterali su di essi, stampa i loro nomi o qualunque cosa tu debba fare.

Aggiornamento: per accedere a determinate proprietà, che non sono emerse UserPrincipalsull'oggetto, è necessario scavare nel sottostante 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;
}

Aggiornamento n. 2: sembra non dovrebbe essere troppo difficile mettere insieme questi due frammenti di codice ... ma ok - eccolo:

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

@Tassisto: sfortunatamente, quella proprietà non è disponibile direttamente su UserPrincipal- vedi la mia risposta aggiornata per come arrivarci.
marc_s

Devo fornire il nome utente per ottenere il valore del suo campo-dipartimento
Tassisto

@Tassito: beh, allora 1) crea un contesto di dominio, 2) trova quell'utente per nome e 3) usa il mio frammento di codice per ottenere il suo dipartimento
marc_s

1
Il metodo GetGroups non ha funzionato per me, ho cambiato il nuovo contesto principale per utilizzare un altro overload del costruttore come segue: PrincipalContext yourDomain = new PrincipalContext (ContextType.Domain, "192.168.2.23", "domain \ user", "password" ); è completamente logico in quanto non sei sempre connesso tramite l'autenticazione di Active Directory. Spero che aiuti
Omid S.

2
Questa risposta è eccellente. È anche possibile semplificare l'iterazione dei gruppi in: result.AddRange (user.GetAuthorizationGroups (). OfType <GroupPrincipal> ()
tlbignerd

59

GetAuthorizationGroups()non trova i gruppi nidificati. Per ottenere davvero tutti i gruppi di cui un dato utente è membro (compresi i gruppi nidificati), prova questo:

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

Uso try/catchperché avevo alcune eccezioni con 2 su 200 gruppi in un annuncio molto grande perché alcuni SID non erano più disponibili. (La Translate()chiamata esegue una conversione SID -> Nome.)


3
le prestazioni sono state migliorate utilizzando questa tecnica invece di correre attraverso l'AD. grazie!
Philippe

GetAuthorisationGroups () è super lento per me, ad esempio 26 e tutti gli altri codici che ho trovato finora non includevano identificatori noti come Everyone, Domain Users, ecc ... Il codice che hai fornito è letteralmente istantaneo e include tutti i sid, si solo i sid ma è quello che mi serve, compresi quelli noti e personalizzati!
Thierry

19

Prima di tutto, GetAuthorizationGroups () è un'ottima funzione ma sfortunatamente ha 2 svantaggi:

  1. Le prestazioni sono scarse, soprattutto nelle grandi aziende con molti utenti e gruppi. Recupera molti più dati di quelli effettivamente necessari e fa una chiamata al server per ogni iterazione del ciclo nel risultato
  2. Contiene bug che possono far sì che la tua applicazione smetta di funzionare "un giorno" quando i gruppi e gli utenti si evolvono. Microsoft ha riconosciuto il problema ed è correlato ad alcuni SID. L'errore che otterrai è "Si è verificato un errore durante l'enumerazione dei gruppi"

Pertanto, ho scritto una piccola funzione per sostituire GetAuthorizationGroups () con prestazioni migliori e senza errori. Esegue solo 1 chiamata LDAP con una query che utilizza i campi indicizzati. Può essere facilmente esteso se hai bisogno di più proprietà oltre ai nomi dei gruppi (proprietà "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;
}

Eccezionale! Grazie. Ho iniziato a scrivere del codice e stavo usando GetAuthorizationGroups e sono rimasto inorridito dal fatto che ci volessero 300 ms-2,5 secondi per ottenere tutti i gruppi. Il tuo metodo viene eseguito in 20-30 ms.
Keith

4
Sembrava promettente, ma non risolve i gruppi nidificati, ad esempio un utente è membro del gruppo a, che è esso stesso membro del gruppo x. Il codice sopra mostrerà solo il gruppo a, ma non il gruppo x. Ho usato questo metodo tramite tokenGroups: stackoverflow.com/a/4460658/602449
Robert Muehsig

Dai un'occhiata al commento di Robert Muehsig: questo fa i gruppi nidificati ed è ancora più veloce. L'unico aspetto negativo restituisce solo i gruppi di sicurezza non i gruppi di distribuzione
Nick Rubino

@bigjim Non è possibile utilizzare GetAuthorizationGroups poiché ci vogliono quasi 6 secondi per restituire i dati, ma il codice fornito non restituisce gruppi noti come Everyone, Domain Users, ecc ... e ho bisogno di questi. Tutto là fuori sembra restituire solo "gruppi personalizzati" e non tutti i gruppi a cui appartiene un utente.
Thierry

11

All'interno dell'AD ogni utente ha una proprietà memberOf. Questo contiene un elenco di tutti i gruppi a cui appartiene.

Ecco un piccolo esempio di codice:

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

1
@Tassisto: Sì, ti capisce. Lo snippet di codice sopra funzionerà esattamente come preferisci. Sostituisci semplicemente il ciclo foreach finale con un ciclo che genera un elenco dei nomi dei gruppi invece della stampa di debug.
Joel Etherton

2
Non riuscirà a elencare il gruppo principale dell'utente (spesso utenti di dominio). Devi tornare indietro e richiedere queste informazioni separatamente. GetAuthorizationGroups non presenta questo problema.
Andy

1

Nel mio caso l'unico modo in cui ho potuto continuare a utilizzare GetGroups () senza alcuna spesa è stato l'aggiunta dell'utente (USER_WITH_PERMISSION) al gruppo che ha il permesso di leggere l'AD (Active Directory). È estremamente essenziale costruire il PrincipalContext passando questo utente e la password.

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

Passaggi che puoi seguire all'interno di Active Directory per farlo funzionare:

  1. In Active Directory crea un gruppo (o prendine uno) e nella scheda sicurezza aggiungi "Gruppo di accesso alle autorizzazioni di Windows"
  2. Fare clic sul pulsante "Avanzate"
  3. Seleziona "Windows Authorization Access Group" e fai clic su "Visualizza"
  4. Seleziona "Leggi tokenGroupsGlobalAndUniversal"
  5. Individua l'utente desiderato e aggiungilo al gruppo che hai creato (preso) dal primo passaggio

1
Questo probabilmente entra in gioco se usi account integrati per un account di pool di servizi / app nella tua applicazione web. Se utilizzi un account di dominio come account del pool di servizi / app o impersonare un account di dominio all'interno del codice, dovrebbe disporre dei diritti di lettura per impostazione predefinita e non presentare questo problema.
vapcguy

1

Questo funziona per me

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

1

La risposta dipende dal tipo di gruppi che desideri recuperare. Lo System.DirectoryServices.AccountManagementspazio dei nomi fornisce due metodi di recupero del gruppo:

GetGroups : restituisce una raccolta di oggetti gruppo che specificano i gruppi di cui è membro l' corrente.

Questo metodo di overload restituisce solo i gruppi di cui l'entità è direttamente un membro; non vengono eseguite ricerche ricorsive.

GetAuthorizationGroups : restituisce una raccolta di oggetti principali che contiene tutti i gruppi di autorizzazione di cui questo utente è membro. Questa funzione restituisce solo gruppi che sono gruppi di sicurezza; i gruppi di distribuzione non vengono restituiti.

Questo metodo cerca in tutti i gruppi in modo ricorsivo e restituisce i gruppi di cui l'utente è membro. Il set restituito può anche includere gruppi aggiuntivi di cui il sistema considererebbe l'utente un membro ai fini dell'autorizzazione.

Quindi GetGroupsottiene tutti i gruppi di cui l'utente è un membro diretto e GetAuthorizationGroupsottiene tutti i gruppi di autorizzazione di cui l'utente è un diretto o indiretto membro .

Nonostante il modo in cui sono denominati, uno non è un sottoinsieme dell'altro. Potrebbero esserci gruppi restituiti da GetGroupsnon restituiti da GetAuthorizationGroupse viceversa.

Ecco un esempio di utilizzo:

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

1

La mia soluzione:

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

0

Nel caso in cui Translate funzioni localmente ma non in remoto, ei group.Tradurre (typeof (NTAccount)

Se si desidera che il codice dell'applicazione venga eseguito utilizzando l'identità UTENTE LOGGATO, abilitare la rappresentazione. La rappresentazione può essere abilitata tramite IIS o aggiungendo il seguente elemento nel file web.config .

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

Se la rappresentazione è abilitata, l'applicazione viene eseguita utilizzando le autorizzazioni trovate nel tuo account utente. Quindi, se l'utente che ha eseguito l'accesso ha accesso a una specifica risorsa di rete, solo allora sarà in grado di accedere a quella risorsa tramite l'applicazione.

Ringrazia il tecnico PRAGIM per queste informazioni dal suo diligente video

Autenticazione di Windows in asp.net Parte 87:

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

Ma la rappresentazione crea molto sovraccarico sul server

La soluzione migliore per consentire agli utenti di determinati gruppi di rete è negare l'anonimato nella configurazione web <authorization><deny users="?"/><authentication mode="Windows"/>

e nel codice sottostante, preferibilmente in global.asax, usa 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

NOTA: il gruppo deve essere scritto con una barra rovesciata \ ad esempio "TheDomain \ TheGroup"

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.