Jak zdobyć grupy użytkowników w Active Directory? (C #, asp.net)

109

Używam tego kodu, aby pobrać grupy bieżącego użytkownika. Ale chcę ręcznie podać użytkownikowi, a następnie pobrać jego grupy. W jaki sposób mogę to zrobić?

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
źródło

Odpowiedzi:

163

Jeśli korzystasz z platformy .NET 3.5 lub System.DirectoryServices.AccountManagementnowszej , możesz użyć nowej przestrzeni nazw (S.DS.AM), która znacznie ułatwia to niż dawniej.

Przeczytaj wszystko na ten temat tutaj: Managing Directory Security Principals w .NET Framework 3.5

Aktualizacja: starsze artykuły z magazynu MSDN nie są już dostępne online, niestety - musisz pobrać CHM dla magazynu MSDN ze stycznia 2008 od firmy Microsoft i przeczytać tam artykuł.

Zasadniczo musisz mieć „główny kontekst” (zwykle jest to Twoja domena), nazwę użytkownika, a wtedy możesz bardzo łatwo uzyskać jego grupy:

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

i to wszystko! Masz teraz wynik (listę) grup autoryzacji, do których należy użytkownik - iteruj po nich, wydrukuj ich nazwy lub cokolwiek musisz zrobić.

Aktualizacja: Aby uzyskać dostęp do niektórych właściwości, które nie są widoczne na UserPrincipalobiekcie, musisz zagłębić się w podłoże 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;
}

Aktualizacja nr 2: wydaje się, że nie powinno być zbyt trudno połączyć te dwa fragmenty kodu ... ale ok - gotowe:

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
źródło
@Tassisto: niestety ta właściwość nie jest dostępna bezpośrednio w UserPrincipal- zobacz moją zaktualizowaną odpowiedź, aby dowiedzieć się, jak to zrobić.
marc_s
Muszę podać nazwę użytkownika, aby uzyskać wartość pola departamentu
Tassisto
@Tassito: no to 1) utwórz kontekst domeny, 2) znajdź tego użytkownika według jego nazwy i 3) użyj mojego fragmentu kodu, aby uzyskać jego dział
marc_s
1
Metoda GetGroups nie zadziałała dla mnie, zmieniłem nowy kontekst podmiotu głównego, aby użyć innego przeciążenia konstruktora w następujący sposób: PrincipalContext yourDomain = new PrincipalContext (ContextType.Domain, "192.168.2.23", "domain \ user", "password" ); jest to całkowicie logiczne, ponieważ nie zawsze jesteś zalogowany za pośrednictwem uwierzytelniania Active Directory. Mam nadzieję, że to pomoże
Omid S.
2
Ta odpowiedź jest doskonała. Możliwe jest również uproszczenie iteracji grup do: result.AddRange (user.GetAuthorizationGroups (). OfType <GroupPrincipal> ()
tlbignerd
59

GetAuthorizationGroups()nie znajduje zagnieżdżonych grup. Aby naprawdę uzyskać wszystkie grupy, których członkiem jest dany użytkownik (w tym grupy zagnieżdżone), spróbuj tego:

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

Używam, try/catchponieważ miałem kilka wyjątków z 2 z 200 grup w bardzo dużej reklamie, ponieważ niektóre identyfikatory SID nie były już dostępne. ( Translate()Wywołanie wykonuje konwersję SID -> Nazwa).

Myszka Miki
źródło
3
wydajność została poprawiona dzięki zastosowaniu tej techniki zamiast uruchamiania przez AD. Dziękuję Ci!
Philippe
GetAuthorisationGroups () jest dla mnie bardzo powolne, tj. 26, a wszystkie inne kody, które do tej pory znalazłem, nie zawierały dobrze znanych identyfikatorów, takich jak Wszyscy, Użytkownicy domeny, itp ... Podany kod jest dosłownie natychmiastowy i obejmuje wszystkie identyfikatory, tak, tylko sidry, ale tego potrzebuję, w tym te dobrze znane i niestandardowe!
Thierry,
19

Przede wszystkim GetAuthorizationGroups () to świetna funkcja, ale niestety ma 2 wady:

  1. Wydajność jest słaba, szczególnie w dużej firmie z wieloma użytkownikami i grupami. Pobiera o wiele więcej danych niż faktycznie potrzebujesz i wykonuje wywołanie serwera dla każdej iteracji pętli w wyniku
  2. Zawiera błędy, które mogą spowodować, że aplikacja przestanie działać „któregoś dnia”, gdy grupy i użytkownicy ewoluują. Firma Microsoft rozpoznała problem i jest związana z niektórymi identyfikatorami SID. Wyświetlany błąd to „Wystąpił błąd podczas wyliczania grup”

Dlatego napisałem małą funkcję, aby zastąpić GetAuthorizationGroups () lepszą wydajnością i bezpieczną dla błędów. Wykonuje tylko 1 wywołanie LDAP z zapytaniem przy użyciu indeksowanych pól. Można ją łatwo rozszerzyć, jeśli potrzebujesz więcej właściwości niż tylko nazwy grup (właściwość „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
źródło
Niesamowite! Dzięki. Zacząłem pisać kod i używałem GetAuthorizationGroups i byłem przerażony, że pobranie wszystkich grup zajmuje 300 ms-2,5 s. Twoja metoda jest wykonywana w 20-30 ms.
Keith
4
Wydawało się to obiecujące, ale nie rozwiązuje problemu grup zagnieżdżonych, np. Użytkownik jest członkiem grupy a, która sama jest członkiem grupy x. Powyższy kod pokaże tylko grupę a, ale nie grupę x. Użyłem tej metody przez tokenGroups: stackoverflow.com/a/4460658/602449
Robert Muehsig
Spójrz na komentarz Roberta Muehsiga - dotyczy to zagnieżdżonych grup i jest jeszcze szybszy. Jedynym minusem jest to, że zwraca tylko grupy zabezpieczeń, a nie grupy dystrybucyjne
Nick Rubino
@bigjim Nie można użyć GetAuthorizationGroups, ponieważ zwrócenie danych zajmuje prawie 6 sekund, ale podany przez Ciebie kod nie zwraca dobrze znanych grup, takich jak Wszyscy, Użytkownicy domeny itp. i muszę je mieć. Wszystko tam wydaje się zwracać tylko „grupy niestandardowe”, a nie wszystkie grupy, do których należy użytkownik.
Thierry,
11

W usłudze AD każdy użytkownik ma właściwość memberOf . Zawiera listę wszystkich grup, do których należy.

Oto mały przykład kodu:

// (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
źródło
1
@Tassisto: Tak, on cię rozumie. Powyższy fragment kodu będzie działał dokładnie tak, jak chcesz. Po prostu zastąp końcową pętlę foreach pętlą, która generuje listę nazw grup zamiast debugowania drukowania.
Joel Etherton,
2
Nie wyświetli podstawowej grupy użytkownika (często Użytkownicy domeny). Musisz wrócić i osobno zapytać o te informacje. GetAuthorizationGroups nie ma tego problemu.
Andy
1

W moim przypadku jedynym sposobem, w jaki mogłem nadal używać GetGroups () bez żadnych wyjaśnień, było dodanie użytkownika (USER_WITH_PERMISSION) do grupy, która ma uprawnienia do odczytu AD (Active Directory). Niezwykle istotne jest, aby skonstruować PrincipalContext przekazujący tego użytkownika i hasło.

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

Kroki, które możesz wykonać w usłudze Active Directory, aby to działało:

  1. W Active Directory utwórz grupę (lub weź ją) i pod zakładką bezpieczeństwa dodaj „Grupę dostępu autoryzacji Windows”
  2. Kliknij przycisk „Zaawansowane”
  3. Wybierz „Grupa dostępu autoryzacji systemu Windows” i kliknij „Widok”
  4. Zaznacz „Czytaj tokenGroupsGlobalAndUniversal”
  5. Zlokalizuj żądanego użytkownika i dodaj go do grupy, którą utworzyłeś (pobrałeś) z pierwszego kroku
Gandarez
źródło
1
Prawdopodobnie pojawia się to, jeśli używasz wbudowanych kont dla konta usługi / puli aplikacji w swojej aplikacji internetowej. Jeśli używasz konta domeny jako konta usługi / puli aplikacji lub personifikujesz konto domeny w kodzie, powinno mieć domyślnie uprawnienia do odczytu i nie ma tego problemu.
vapcguy
1

To działa dla mnie

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
źródło
1

Odpowiedź zależy od rodzaju grup, które chcesz odzyskać. Przestrzeń System.DirectoryServices.AccountManagementnazw udostępnia dwie metody pobierania grup:

GetGroups - zwraca kolekcję obiektów grupy, które określają grupy, których członkiem jest bieżący podmiot zabezpieczeń.

Ta przeciążona metoda zwraca tylko grupy, których podmiot zabezpieczeń jest bezpośrednio członkiem; nie są wykonywane żadne wyszukiwania rekurencyjne.

GetAuthorizationGroups - zwraca kolekcję obiektów Principal, która zawiera wszystkie grupy autoryzacji, których członkiem jest ten użytkownik. Ta funkcja zwraca tylko grupy, które są grupami zabezpieczeń; grupy dystrybucyjne nie są zwracane.

Ta metoda przeszukuje wszystkie grupy rekurencyjnie i zwraca grupy, których członkiem jest użytkownik. Zwrócony zestaw może również zawierać dodatkowe grupy, których system uznałby za członka dla celów autoryzacji.

GetGroupsPobiera więc wszystkie grupy, których użytkownik jest bezpośrednim członkiem, oraz GetAuthorizationGroupswszystkie grupy uprawnień , do których użytkownik jest bezpośredni lub pośredni członkiem.

Pomimo sposobu, w jaki są nazywane, jeden nie jest podzbiorem drugiego. Mogą istnieć grupy zwrócone przez GetGroupsnie zwrócone przez GetAuthorizationGroupsi odwrotnie.

Oto przykład użycia:

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
źródło
1

Moje rozwiązanie:

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
źródło
0

W przypadku, gdy Tłumacz działa lokalnie, ale nie zdalnie ei group. Przetłumacz (typeof (NTAccount)

Jeśli chcesz, aby kod aplikacji był wykonywany przy użyciu tożsamości LOGGED IN USER, włącz personifikację. Personifikację można włączyć za pośrednictwem usług IIS lub przez dodanie następującego elementu w pliku web.config .

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

Jeśli personifikacja jest włączona, aplikacja jest wykonywana przy użyciu uprawnień znajdujących się na koncie użytkownika. Jeśli więc zalogowany użytkownik ma dostęp do określonego zasobu sieciowego, tylko wtedy będzie mógł uzyskać dostęp do tego zasobu za pośrednictwem aplikacji.

Podziękuj technikowi PRAGIM za te informacje z jego starannego filmu

Uwierzytelnianie systemu Windows w asp.net część 87:

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

Jednak podszywanie się pod inne osoby wiąże się z dużym obciążeniem serwera

Najlepszym rozwiązaniem pozwalającym użytkownikom niektórych grup sieciowych jest odmowa anonimowości w konfiguracji sieciowej <authorization><deny users="?"/><authentication mode="Windows"/>

aw swoim kodzie z tyłu, najlepiej w global.asax, użyj 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

UWAGA: Grupa musi być zapisana z odwrotnym ukośnikiem \ tj. „TheDomain \ TheGroup”

Pierre-David Sabourin
źródło