Como obter os grupos de um usuário no Active Directory? (c #, asp.net)

109

Eu uso este código para obter os grupos do usuário atual. Mas eu quero fornecer manualmente ao usuário e, em seguida, obter seus grupos. Como posso fazer isso?

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
fonte

Respostas:

163

Se você estiver no .NET 3.5 ou superior, poderá usar o novo System.DirectoryServices.AccountManagementnamespace (S.DS.AM), que torna isso muito mais fácil do que costumava ser.

Leia tudo sobre isso aqui: Gerenciando princípios de segurança de diretório no .NET Framework 3.5

Atualização: artigos mais antigos da revista MSDN não estão mais online, infelizmente - você precisará baixar o CHM para a revista MSDN de janeiro de 2008 da Microsoft e ler o artigo lá.

Basicamente, você precisa ter um "contexto principal" (normalmente seu domínio), um principal do usuário, e então obter seus grupos muito 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 isso é tudo que existe! Agora você tem um resultado (uma lista) de grupos de autorização aos quais o usuário pertence - iterar sobre eles, imprimir seus nomes ou o que quer que você precise fazer.

Atualização: para acessar certas propriedades, que não são apresentadas no UserPrincipalobjeto, você precisa se aprofundar em 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;
}

Atualização nº 2: parece que não deve ser muito difícil colocar esses dois trechos de código juntos ... mas ok - aqui vai:

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
fonte
@Tassisto: infelizmente, essa propriedade não está disponível diretamente no UserPrincipal- veja minha resposta atualizada para saber como acessá- la.
marc_s
Preciso fornecer o nome de usuário para obter o valor de seu campo de departamento
Tassisto
@Tassito: bem, então 1) crie um contexto de domínio, 2) encontre esse usuário pelo nome e 3) use meu snippet de código para obter seu departamento
marc_s
1
O método GetGroups não funcionou para mim, mudei o novo contexto principal para usar outra sobrecarga do construtor da seguinte forma: PrincipalContext yourDomain = new PrincipalContext (ContextType.Domain, "192.168.2.23", "domínio \ usuário", "senha" ); é completamente lógico, pois nem sempre você está conectado por meio da autenticação do Active Directory. Espero que ajude
Omid S.
2
Essa resposta é excelente. Também é possível simplificar a iteração dos grupos para: result.AddRange (user.GetAuthorizationGroups (). OfType <GroupPrincipal> ()
tlbignerd
59

GetAuthorizationGroups()não encontra grupos aninhados. Para realmente obter todos os grupos dos quais um determinado usuário é membro (incluindo grupos aninhados), tente o seguinte:

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

Eu uso try/catchporque tive algumas exceções com 2 de 200 grupos em um AD muito grande porque alguns SIDs não estavam mais disponíveis. (A Translate()chamada faz um SID -> conversão de nome.)

Mickey Mouse
fonte
3
o desempenho foi melhorado com o uso dessa técnica em vez de percorrer o AD. obrigado!
Philippe
GetAuthorisationGroups () é muito lento para mim, ou seja, 26 e todos os outros códigos que encontrei até agora não incluíam identificadores conhecidos como Todos, Usuários de domínio, etc ... O código que você forneceu é literalmente instantâneo e inclui todos os sids sim só os sids mas é disso que preciso, inclusive os mais conhecidos e customizados!
Thierry
19

Em primeiro lugar, GetAuthorizationGroups () é uma ótima função, mas infelizmente tem 2 desvantagens:

  1. O desempenho é ruim, especialmente em grandes empresas com muitos usuários e grupos. Ele busca muito mais dados do que você realmente precisa e faz uma chamada de servidor para cada iteração de loop no resultado
  2. Ele contém bugs que podem fazer com que seu aplicativo pare de funcionar 'algum dia' quando grupos e usuários estiverem evoluindo. A Microsoft reconheceu o problema e está relacionada a alguns SIDs. O erro que você obterá é "Ocorreu um erro ao enumerar os grupos"

Portanto, escrevi uma pequena função para substituir GetAuthorizationGroups () com melhor desempenho e à prova de erros. Ele faz apenas 1 chamada LDAP com uma consulta usando campos indexados. Ele pode ser facilmente estendido se você precisar de mais propriedades do que apenas os nomes dos grupos (propriedade "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
fonte
Impressionante! Obrigado. Comecei a escrever alguns códigos e estava usando GetAuthorizationGroups e fiquei horrorizado ao ver que estava demorando 300ms-2,5s para obter todos os grupos. Seu método é executado em 20-30 ms.
Keith
4
Isso parecia promissor, mas não resolve grupos aninhados, por exemplo, um usuário é membro do grupo a, que por sua vez é membro do grupo x. O código acima mostrará apenas o grupo a, mas não o grupo x. Usei este método por meio de tokenGroups: stackoverflow.com/a/4460658/602449
Robert Muehsig
Dê uma olhada no comentário de Robert Muehsig - isso faz grupos aninhados e é ainda mais rápido. A desvantagem é que ele retorna grupos de segurança, não grupos de distribuição
Nick Rubino
@bigjim Não é possível usar GetAuthorizationGroups, pois leva quase 6 segundos para retornar seus dados, mas o código que você forneceu não retorna grupos conhecidos como Todos, Usuários de Domínio, etc ... e eu preciso ter estes. Tudo lá fora parece retornar apenas "grupos personalizados" e não todos os grupos aos quais um usuário pertence.
Thierry
11

Dentro do AD, todo usuário tem uma propriedade memberOf. Este contém uma lista de todos os grupos aos quais ele pertence.

Aqui está um pequeno exemplo de código:

// (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
fonte
1
@Tassisto: Sim, ele te entende. O snippet de código acima fará exatamente o que você quiser. Simplesmente substitua o loop foreach final por um loop que gera uma lista de nomes de grupos em vez de impressão de depuração.
Joel Etherton
2
Não será possível listar o grupo primário do usuário (geralmente Usuários do domínio). Você tem que voltar e consultar essas informações separadamente. GetAuthorizationGroups não tem esse problema.
Andy
1

No meu caso, a única maneira de continuar usando GetGroups () sem qualquer expcetion foi adicionando o usuário (USER_WITH_PERMISSION) ao grupo que tem permissão para ler o AD (Active Directory). É extremamente essencial construir o PrincipalContext passando este usuário e senha.

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

Etapas que você pode seguir dentro do Active Directory para fazê-lo funcionar:

  1. No Active Directory, crie um grupo (ou escolha um) e na guia de segurança adicione "Grupo de Acesso de Autorização do Windows"
  2. Clique no botão "Avançado"
  3. Selecione "Windows Authorization Access Group" e clique em "View"
  4. Marque "Ler tokenGroupsGlobalAndUniversal"
  5. Localize o usuário desejado e adicione ao grupo que você criou (pegou) na primeira etapa
Gandarez
fonte
1
Isso provavelmente entra em jogo se você usar contas internas para uma conta de pool de serviço / aplicativo em seu aplicativo da web. Se você usar uma conta de domínio como a conta do pool de serviço / aplicativo ou personificar uma conta de domínio dentro do código, ela deve ter direitos de leitura por padrão e não deve ter esse problema.
vapcguy
1

Isso funciona para mim

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
fonte
1

A resposta depende de que tipo de grupos você deseja recuperar. O System.DirectoryServices.AccountManagementnamespace fornece dois métodos de recuperação de grupo:

GetGroups - Retorna uma coleção de objetos de grupo que especificam os grupos dos quais o principal atual é membro.

Esse método sobrecarregado retorna apenas os grupos dos quais o principal é diretamente um membro; nenhuma pesquisa recursiva é realizada.

GetAuthorizationGroups - Retorna uma coleção de objetos principais que contém todos os grupos de autorização dos quais este usuário é membro. Esta função retorna apenas grupos que são grupos de segurança; grupos de distribuição não são retornados.

Este método pesquisa todos os grupos recursivamente e retorna os grupos dos quais o usuário é membro. O conjunto retornado também pode incluir grupos adicionais dos quais o sistema consideraria o usuário um membro para fins de autorização.

Então GetGroupsfica todos os grupos dos quais o usuário é um direto membro, e GetAuthorizationGroupsrecebe todos de autorização grupos dos quais o usuário é um direto ou indireto membro.

Apesar da forma como são nomeados, um não é um subconjunto do outro. Pode haver grupos retornados por GetGroupsnão retornados GetAuthorizationGroupse vice-versa.

Aqui está um exemplo de uso:

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
fonte
1

Minha solução:

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
fonte
0

No caso de o Tradutor funcionar localmente, mas não remotamente ei group. Traduzir (typeof (NTAccount)

Se você deseja que o código do aplicativo seja executado usando a identidade LOGGED IN USER, ative a representação. A personificação pode ser habilitada através do IIS ou adicionando o seguinte elemento no web.config .

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

Se a representação estiver habilitada, o aplicativo será executado usando as permissões encontradas na sua conta de usuário. Portanto, se o usuário logado tiver acesso, a um recurso específico da rede, só então ele poderá acessar esse recurso através do aplicativo.

Agradeça ao PRAGIM tech por esta informação de seu diligente vídeo

Autenticação do Windows no asp.net Parte 87:

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

Mas a representação cria muita sobrecarga no servidor

A melhor solução para permitir usuários de certos grupos de rede é negar anônimos na configuração da web <authorization><deny users="?"/><authentication mode="Windows"/>

e em seu code behind, de preferência no global.asax, use o 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: O grupo deve ser escrito com uma barra invertida \ ou seja, "TheDomain \ TheGroup"

Pierre-David Sabourin
fonte