2011-06-06 40 views
31

Estoy buscando obtener una lista de todos los grupos de los que un usuario es miembro en Active Directory, tanto explícitamente enumerados en la lista de propiedades memberOf como implícitamente a través de anidados membresía de grupo. Por ejemplo, si examino UserA y UserA es una parte de GroupA y GroupB, también quiero incluir GroupC si GroupB es miembro de GroupC.Buscar membresía de grupo recursivo (Active Directory) con C#

Para darle un poco más de información sobre mi aplicación, lo haré de forma limitada. Básicamente, quiero un control de seguridad ocasionalmente que enumere estas membresías adicionales. Querré diferenciarlos, pero eso no debería ser difícil.

Mi problema es que no he encontrado una forma eficiente de hacer que esta consulta funcione. El texto estándar en Active Directory (This CodeProject Article) muestra una forma de hacer esto que es básicamente una búsqueda recursiva. Eso parece terriblemente ineficiente. Incluso en mi pequeño dominio, un usuario puede tener más de 30 membresías grupales. Eso significa más de 30 llamadas a Active Directory para un usuario.

He mirado en el siguiente código de LDAP para obtener todas las entradas memberOf a la vez:

(memberOf:1.2.840.113556.1.4.1941:={0}) 

donde {0} sería mi ruta LDAP (por ejemplo: CN = Usuario A, OU = Usuarios, DC = foo, DC = org). Sin embargo, no devuelve ningún registro. La desventaja de este método, incluso si funcionara, sería que no sabría qué grupo era explícito y cuál implícito.

Eso es lo que tengo hasta ahora. Me gustaría saber si hay una forma mejor que el artículo de CodeProject y, de ser así, cómo se podría lograr (el código real sería maravilloso). Estoy usando .NET 4.0 y C#. Mi Active Directory está en un nivel funcional de Windows 2008 (aún no es R2).

Respuesta

22

Thirst gracias por esta una pregunta interesante.

A continuación, sólo una corrección, que dicen:

He mirado en el siguiente código de LDAP para obtener todas las entradas memberOf a la vez:

(memberOf:1.2.840.113556.1.4.1941:={0}) 

usted no hace que funcione . Recuerdo que lo hice funcionar cuando supe de su existencia, pero estaba en un filtro LDIFDE.EXE. Entonces lo aplico a ADSI en C# y todavía funciona. Había demasiados paréntesis en la muestra que tomé de Microsoft, pero estaba funcionando (source in AD Search Filter Syntax).

Según su observación sobre el hecho de que no sabemos si un usuario pertenece explícitamente al grupo, agrego una solicitud más. Sé que esto no es muy bueno, pero es lo mejor que puedo hacer.

static void Main(string[] args) 
{ 
    /* Connection to Active Directory 
    */ 
    DirectoryEntry deBase = new DirectoryEntry("LDAP://WM2008R2ENT:389/dc=dom,dc=fr"); 


    /* To find all the groups that "user1" is a member of : 
    * Set the base to the groups container DN; for example root DN (dc=dom,dc=fr) 
    * Set the scope to subtree 
    * Use the following filter : 
    * (member:1.2.840.113556.1.4.1941:=cn=user1,cn=users,DC=x) 
    */ 
    DirectorySearcher dsLookFor = new DirectorySearcher(deBase); 
    dsLookFor.Filter = "(member:1.2.840.113556.1.4.1941:=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)"; 
    dsLookFor.SearchScope = SearchScope.Subtree; 
    dsLookFor.PropertiesToLoad.Add("cn"); 

    SearchResultCollection srcGroups = dsLookFor.FindAll(); 

    /* Just to know if user is explicitly in group 
    */ 
    foreach (SearchResult srcGroup in srcGroups) 
    { 
    Console.WriteLine("{0}", srcGroup.Path); 

    foreach (string property in srcGroup.Properties.PropertyNames) 
    { 
     Console.WriteLine("\t{0} : {1} ", property, srcGroup.Properties[property][0]); 
    } 

    DirectoryEntry aGroup = new DirectoryEntry(srcGroup.Path); 
    DirectorySearcher dsLookForAMermber = new DirectorySearcher(aGroup); 
    dsLookForAMermber.Filter = "(member=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)"; 
    dsLookForAMermber.SearchScope = SearchScope.Base; 
    dsLookForAMermber.PropertiesToLoad.Add("cn"); 

    SearchResultCollection memberInGroup = dsLookForAMermber.FindAll(); 
    Console.WriteLine("Find the user {0}", memberInGroup.Count); 

    } 

    Console.ReadLine(); 
} 

En mi árbol de pruebas das esto:

LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr 
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr 
cn : MonGrpSec 
Find the user 1 

LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr 
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr 
cn : MonGrpDis 
Find the user 1 

LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr 
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr 
cn : MonGrpPlusSec 
Find the user 0 

LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr 
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr 
cn : MonGrpPlusSecUniv 
Find the user 0 

(editado) '1.2.840.113556.1.4.1941' no está funcionando en W2K3 SP1, comienza a trabajar con SP2 . Supongo que es lo mismo con W2K3 R2. Se supone que funciona en W2K8. Pruebo aquí con W2K8R2. Pronto podré probar esto en W2K8.

+1

Gracias por compartir esto.Esto parece muy prometedor. Estoy lejos de donde puedo probarlo de inmediato (apenas tengo Internet) pero lo probaré lo antes posible y le contaré lo que encuentre. Gracias de nuevo. – IAmTimCorey

+0

Editado: realizo algunas investigaciones más y agrego detalles de cuándo Microsoft comienza a admitir estos controles recursivos. Es extraño que la información no esté en la raíz DSE. – JPBlanc

+0

Entonces ¿Lo pruebas? – JPBlanc

6

Si no hay otra manera que las llamadas recursivas (y no creo que hay) a continuación, al menos se puede dejar que el marco haga el trabajo por usted: ver la UserPrincipal.GetAuthorizationGroups method (en el espacio de nombres System.DirectoryServices.AccountManagement e introducido en .Net 3,5)

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

Comparar con los resultados de GetGroups("Devuelve una colección de objetos de grupo que especifican los grupos de los que el director actual es un miembro") para ver si el número de miembros es explícito o implícito.

+0

Gracias por la respuesta. Hay un par de problemas que encontré al probar este método. En primer lugar, esto obtiene algunos grupos que normalmente no se enumeran como grupos (como "nivel medio obligatorio" o algo así). En segundo lugar, esto solo obtiene grupos de seguridad (no grupos de distribución). Desafortunadamente, necesito ambos, aunque si hubiera un método similar para obtener grupos de distribución, sería genial, ya que podría ser aún más granular. – IAmTimCorey

2

Utilice el filtro ldap de manera recursiva pero consulte todos los grupos devueltos después de cada consulta para reducir el número de viajes redondos.

Ex:

  1. Obtener todos los grupos en los que el usuario es miembro
  2. Obtener todos los grupos en los que el paso 1 Grupos son miembros
  3. Obtener todos los grupos en el Paso 2 grupos son miembros
  4. ...

En mi experiencia, rara vez hay más de 5, pero definitivamente debe ser mucho menor que 30.

también:

  • Asegúrese de que no se retiran de las propiedades que se van a necesitar volver.
  • Los resultados del almacenamiento en caché pueden ayudar significativamente al rendimiento de , pero hacen que mi código sea mucho más complicado .
  • Asegúrese de utilizar la agrupación de conexiones.
  • Grupo primario tiene que ser manejado por separado
+0

Esta es una buena idea. Gracias por la ayuda. – IAmTimCorey

0
static List<SearchResult> ad_find_all_members(string a_sSearchRoot, string a_sGroupDN, string[] a_asPropsToLoad) 
    { 
     using (DirectoryEntry de = new DirectoryEntry(a_sSearchRoot)) 
      return ad_find_all_members(de, a_sGroupDN, a_asPropsToLoad); 
    } 

    static List<SearchResult> ad_find_all_members(DirectoryEntry a_SearchRoot, string a_sGroupDN, string[] a_asPropsToLoad) 
    { 
     string sDN = "distinguishedName"; 
     string sOC = "objectClass"; 
     string sOC_GROUP = "group"; 
     string[] asPropsToLoad = a_asPropsToLoad; 
     Array.Sort<string>(asPropsToLoad); 
     if (Array.BinarySearch<string>(asPropsToLoad, sDN) < 0) 
     { 
      Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1); 
      asPropsToLoad[asPropsToLoad.Length-1] = sDN; 
     } 
     if (Array.BinarySearch<string>(asPropsToLoad, sOC) < 0) 
     { 
      Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1); 
      asPropsToLoad[asPropsToLoad.Length-1] = sOC; 
     } 

     List<SearchResult> lsr = new List<SearchResult>(); 

     using (DirectorySearcher ds = new DirectorySearcher(a_SearchRoot)) 
     { 
      ds.Filter = "(&(|(objectClass=group)(objectClass=user))(memberOf=" + a_sGroupDN + "))"; 
      ds.PropertiesToLoad.Clear(); 
      ds.PropertiesToLoad.AddRange(asPropsToLoad); 
      ds.PageSize = 1000; 
      ds.SizeLimit = 0; 
      foreach (SearchResult sr in ds.FindAll()) 
       lsr.Add(sr); 
     } 

     for(int i=0;i<lsr.Count;i++) 
      if (lsr[i].Properties.Contains(sOC) && lsr[i].Properties[sOC].Contains(sOC_GROUP)) 
       lsr.AddRange(ad_find_all_members(a_SearchRoot, (string)lsr[i].Properties[sDN][0], asPropsToLoad)); 

     return lsr; 
    } 

    static void Main(string[] args) 
    { 
    foreach (var sr in ad_find_all_members("LDAP://DC=your-domain,DC=com", "CN=your-group-name,OU=your-group-ou,DC=your-domain,DC=com", new string[] { "sAMAccountName" })) 
     Console.WriteLine((string)sr.Properties["distinguishedName"][0] + " : " + (string)sr.Properties["sAMAccountName"][0]); 
    } 
2

puede utilizar el tokenGroups y propiedades tokenGroupsGlobalAndUniversal si se encuentra en el servidor de Exchange. tokenGroups le dará todas los grupos de seguridad pertenece este usuario, incluidos los grupos anidados y los usuarios del dominio, usuarios, etc tokenGroupsGlobalAndUniversal incluirá desde tokenGroups y grupos de distribución

private void DoWorkWithUserGroups(string domain, string user) 
    { 
     var groupType = "tokenGroupsGlobalAndUniversal"; // use tokenGroups for only security groups 

     using (var userContext = new PrincipalContext(ContextType.Domain, domain)) 
     { 
      using (var identity = UserPrincipal.FindByIdentity(userContext, IdentityType.SamAccountName, user)) 
      { 
       if (identity == null) 
        return; 

       var userEntry = identity.GetUnderlyingObject() as DirectoryEntry; 
       userEntry.RefreshCache(new[] { groupType }); 
       var sids = from byte[] sid in userEntry.Properties[groupType] 
          select new SecurityIdentifier(sid, 0); 

       foreach (var sid in sids) 
       { 
        using(var groupIdentity = GroupPrincipal.FindByIdentity(userContext, IdentityType.Sid, sid.ToString())) 
        { 
         if(groupIdentity == null) 
          continue; // this group is not in the domain, probably from sidhistory 

         // extract the info you want from the group 
        } 
       } 
      } 
     } 
    } 
1

Si está utilizando .NET 3.5 o superior puede usar el espacio de nombres System.DirectoryServices.AccountManagement que realmente lo hace fácil.

Ver la respuesta relacionada aquí: Active Directory nested groups

Cuestiones relacionadas