¿Cómo obtener los grupos de un usuario en Active Directory? (c #, asp.net)

109

Utilizo este código para obtener los grupos del usuario actual. Pero quiero darle manualmente al usuario y luego obtener sus grupos. ¿Cómo puedo hacer esto?

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
fuente

Respuestas:

163

Si está en .NET 3.5 o superior, puede usar el nuevo System.DirectoryServices.AccountManagementespacio de nombres (S.DS.AM) que hace que esto sea mucho más fácil de lo que solía ser.

Lea todo sobre esto aquí: Administración de los principales de seguridad de directorios en .NET Framework 3.5

Actualización: los artículos más antiguos de la revista MSDN ya no están en línea, desafortunadamente; deberá descargar el CHM para la revista MSDN de enero de 2008 de Microsoft y leer el artículo allí.

Básicamente, necesita tener un "contexto principal" (normalmente su dominio), un principal de usuario, y luego obtiene sus grupos muy fácilmente:

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

y eso es todo lo que hay! Ahora tiene un resultado (una lista) de los grupos de autorización a los que pertenece el usuario: iterar sobre ellos, imprimir sus nombres o lo que sea que necesite hacer.

Actualización: para acceder a ciertas propiedades, que no aparecen en el UserPrincipalobjeto, debe profundizar en el subyacente 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;
}

Actualización n. ° 2: parece que no debería ser demasiado difícil juntar estos dos fragmentos de código ... pero está bien, aquí va:

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
fuente
@Tassisto: desafortunadamente, esa propiedad no está disponible directamente en el UserPrincipal- vea mi respuesta actualizada sobre cómo obtenerla.
marc_s
Necesito dar el nombre de usuario para obtener el valor de su campo de departamento
Tassisto
@Tassito: bueno, entonces 1) cree un contexto de dominio, 2) busque ese usuario por su nombre y 3) use mi fragmento de código para obtener su departamento
marc_s
1
El método GetGroups no funcionó para mí, cambié el nuevo contexto principal para usar otra sobrecarga del constructor de la siguiente manera: PrincipalContext yourDomain = new PrincipalContext (ContextType.Domain, "192.168.2.23", "dominio \ usuario", "contraseña" ); es completamente lógico ya que no siempre está conectado a través de la autenticación del directorio activo. Espero que ayude
Omid S.
2
Esta respuesta es excelente. También es posible simplificar la iteración de los grupos a: result.AddRange (user.GetAuthorizationGroups (). OfType <GroupPrincipal> ()
tlbignerd
59

GetAuthorizationGroups()no encuentra grupos anidados. Para obtener realmente todos los grupos de los que un usuario determinado es miembro (incluidos los grupos anidados), intente esto:

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

Lo uso try/catchporque tuve algunas excepciones con 2 de 200 grupos en un AD muy grande porque algunos SID ya no estaban disponibles. (La Translate()llamada realiza una conversión de SID -> Nombre).

Mickey Mouse
fuente
3
las actuaciones se mejoraron utilizando esta técnica en lugar de ejecutar AD. ¡gracias!
Philippe
GetAuthorisationGroups () es muy lento para mí, es decir, 26 y todos los demás códigos que he encontrado hasta ahora no incluían identificadores conocidos como Everyone, Domain Users, etc. El código que proporcionó es literalmente instantáneo e incluye todos los sids, sí, solo los sids, pero eso es lo que necesito, ¡incluidos los conocidos y personalizados!
Thierry
19

En primer lugar, GetAuthorizationGroups () es una función excelente, pero desafortunadamente tiene 2 desventajas:

  1. El rendimiento es deficiente, especialmente en empresas grandes con muchos usuarios y grupos. Obtiene muchos más datos de los que realmente necesita y realiza una llamada al servidor para cada iteración de bucle en el resultado
  2. Contiene errores que pueden hacer que su aplicación deje de funcionar "algún día" cuando los grupos y los usuarios estén evolucionando. Microsoft reconoció el problema y está relacionado con algunos SID. El error que obtendrá es "Se produjo un error al enumerar los grupos".

Por lo tanto, escribí una pequeña función para reemplazar GetAuthorizationGroups () con un mejor rendimiento y a prueba de errores. Realiza solo 1 llamada LDAP con una consulta utilizando campos indexados. Se puede ampliar fácilmente si necesita más propiedades que solo los nombres de grupo (propiedad "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
fuente
¡Increíble! Gracias. Comencé a escribir código y estaba usando GetAuthorizationGroups y me horrorizó que tomara 300ms-2.5s obtener todos los grupos. Su método se realiza en 20-30 ms.
Keith
4
Esto parecía prometedor, pero no resuelve los grupos anidados, por ejemplo, un usuario es miembro del grupo a, que a su vez es miembro del grupo x. El código anterior solo mostrará el grupo a, pero no el grupo x. Usé este método a través de tokenGroups: stackoverflow.com/a/4460658/602449
Robert Muehsig
Eche un vistazo al comentario de Robert Muehsig: esto hace grupos anidados y es aún más rápido. El único inconveniente es que solo devuelve grupos de seguridad, no grupos de distribución
Nick Rubino
@bigjim No puede usar GetAuthorizationGroups ya que tarda casi 6 segundos en devolver sus datos, pero el código que proporcionó no devuelve grupos conocidos como Todos, Usuarios de dominio, etc. y necesito tener estos. Todo lo que existe parece devolver solo "grupos personalizados" y no todos los grupos a los que pertenece un usuario.
Thierry
11

Dentro del AD cada usuario tiene una propiedad memberOf. Contiene una lista de todos los grupos a los que pertenece.

Aquí hay un pequeño ejemplo 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
fuente
1
@Tassisto: Sí, te entiende. El fragmento de código anterior hará exactamente lo que desee. Simplemente reemplace el ciclo foreach final con un ciclo que genera una lista de los nombres de grupo en lugar de la impresión de depuración.
Joel Etherton
2
No podrá enumerar el grupo principal del usuario (a menudo, usuarios del dominio). Tienes que volver y consultar esa información por separado. GetAuthorizationGroups no tiene este problema.
Andy
1

En mi caso, la única forma en que podía seguir usando GetGroups () sin ninguna excepción era agregando al usuario (USER_WITH_PERMISSION) al grupo que tiene permiso para leer el AD (Active Directory). Es extremadamente esencial construir el PrincipalContext pasando este usuario y contraseña.

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

Pasos que puede seguir dentro de Active Directory para que funcione:

  1. En Active Directory cree un grupo (o tome uno) y en la pestaña de seguridad agregue "Grupo de acceso de autorización de Windows"
  2. Haga clic en el botón "Avanzado"
  3. Seleccione "Grupo de acceso de autorización de Windows" y haga clic en "Ver"
  4. Marque "Leer tokenGroupsGlobalAndUniversal"
  5. Busque el usuario deseado y agréguelo al grupo que creó (tomó) desde el primer paso
Gandarez
fuente
1
Es probable que esto entre en juego si usa cuentas integradas para una cuenta de servicio / grupo de aplicaciones en su aplicación web. Si usa una cuenta de dominio como la cuenta del grupo de aplicaciones o servicios, o se hace pasar por una cuenta de dominio dentro del código, debería tener derechos de lectura de forma predeterminada y no tener este problema.
vapcguy
1

Esto funciona para mi

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

La respuesta depende del tipo de grupos que desee recuperar. El System.DirectoryServices.AccountManagementespacio de nombres proporciona dos métodos de recuperación de grupos:

GetGroups : devuelve una colección de objetos de grupo que especifican los grupos de los que es miembro el principal actual.

Este método sobrecargado solo devuelve los grupos de los que el principal es directamente miembro; no se realizan búsquedas recursivas.

GetAuthorizationGroups : devuelve una colección de objetos principales que contiene todos los grupos de autorización de los que este usuario es miembro. Esta función solo devuelve grupos que son grupos de seguridad; los grupos de distribución no se devuelven.

Este método busca todos los grupos de forma recursiva y devuelve los grupos en los que el usuario es miembro. El conjunto devuelto también puede incluir grupos adicionales de los que el sistema consideraría al usuario miembro para fines de autorización.

Por lo tanto, GetGroupsobtiene todos los grupos de los que el usuario es miembro directo y GetAuthorizationGroupstodos los grupos de autorización de los que el usuario es miembro directo o indirecto .

A pesar de la forma en que se nombran, uno no es un subconjunto del otro. Puede haber grupos devueltos por GetGroupsno devueltos por GetAuthorizationGroupsy viceversa.

Aquí tienes un ejemplo 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
fuente
1

Mi solución:

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

En caso de que Translate funcione localmente pero no de forma remota, ei group. Traducir (typeof (NTAccount)

Si desea que el código de la aplicación se ejecute utilizando la identidad de USUARIO INICIADO, habilite la suplantación. La suplantación se puede habilitar a través de IIS o agregando el siguiente elemento en web.config .

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

Si la suplantación está habilitada, la aplicación se ejecuta utilizando los permisos que se encuentran en su cuenta de usuario. Entonces, si el usuario que inició sesión tiene acceso a un recurso de red específico, solo entonces podrá acceder a ese recurso a través de la aplicación.

Agradezca a PRAGIM tech por esta información de su diligente video

Autenticación de Windows en asp.net Parte 87:

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

Pero la suplantación crea mucha sobrecarga en el servidor

La mejor solución para permitir a los usuarios de ciertos grupos de red es negar el anonimato en la configuración web <authorization><deny users="?"/><authentication mode="Windows"/>

y en su código detrás, preferiblemente en global.asax, use 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: El grupo debe escribirse con una barra invertida \ es decir, "TheDomain \ TheGroup"

Pierre-David Sabourin
fuente