Найти рекурсивное членство в группе (Active Directory) с помощью C # - PullRequest
31 голосов
/ 06 июня 2011

Я хочу получить список всех групп, членом которых является пользователь в Active Directory, как явно перечисленных в списке свойств memberOf, так и неявно через членство во вложенных группах. Например, если я проверяю UserA и UserA является частью GroupA и GroupB, я также хочу перечислить GroupC, если GroupB является членом GroupC.

Чтобы дать вам более подробное представление о моем заявлении, я буду делать это на ограниченной основе. По сути, я хочу время от времени проверять безопасность, в которой будут перечислены эти дополнительные членства. Я хочу различать два, но это не должно быть трудно.

Моя проблема в том, что я не нашел эффективного способа заставить этот запрос работать. Стандартный текст в Active Directory ( Эта статья CodeProject ) показывает способ сделать это, по сути, рекурсивным поиском. Это кажется ужасно неэффективным. Даже в моем небольшом домене у пользователя может быть более 30 групп. Это означает, что более 30 звонков в Active Directory для одного пользователя.

Я изучил следующий код LDAP, чтобы получить все записи memberOf одновременно:

(memberOf:1.2.840.113556.1.4.1941:={0})

где {0} будет моим путем LDAP (например, CN = UserA, OU = Users, DC = foo, DC = org). Тем не менее, он не возвращает никаких записей. Недостатком этого метода, даже если бы он работал, было бы то, что я не знаю, какая группа была явной, а какая неявной.

Это то, что я имею до сих пор. Я хотел бы знать, есть ли лучший способ, чем статья CodeProject, и, если да, как это можно сделать (реальный код был бы замечательным). Я использую .NET 4.0 и C #. Мой Active Directory находится на функциональном уровне Windows 2008 (это еще не R2).

Ответы [ 6 ]

24 голосов
/ 09 июня 2011

Жажда за этот интересный вопрос.

Далее, просто исправление, вы говорите:

Я изучил следующий код LDAP, чтобы получить все записи memberOf одновременно:

(memberOf:1.2.840.113556.1.4.1941:={0})

Ты не заставляешь это работать. Я помню, я заставил это работать, когда я узнал о его существовании, но это было в фильтре LDIFDE.EXE. Поэтому я применяю его к ADSI в C #, и он все еще работает. В образце, взятом из Microsoft, было слишком много скобок, но он работал ( источник в синтаксисе фильтра поиска AD ).

В соответствии с вашим замечанием о том, что мы не знаем, принадлежит ли пользователь явно к группе, я добавляю еще один запрос. Я знаю, что это не очень хорошо, но это лучшее, что я могу сделать.

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

В моем тестовом дереве это дает:

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

(изм) 1.2.840.113556.1.4.1941 не работает в W2K3 SP1, он начинает работать с SP2. Я полагаю, что то же самое с W2K3 R2. Он должен работать на W2K8. Я тестирую здесь с W2K8R2. Скоро я смогу проверить это на W2K8.

7 голосов
/ 06 июня 2011

Если нет другого способа, кроме рекурсивных вызовов (и я не верю, что это так), то, по крайней мере, вы можете позволить платформе сделать всю работу за вас: см. Метод UserPrincipal.GetAuthorizationGroups System.DirectoryServices.AccountManagement пространство имен и введено в .Net 3.5)

Этот метод рекурсивно ищет все группы и возвращает группы, в которые входит пользователь.Возвращенный набор может также включать дополнительные группы, в которых система будет считать пользователя участником для целей авторизации.

Сравнить с результатами GetGroups ("Возвращает коллекциюгрупповых объектов, которые указывают группы, членом которых является текущий участник ") , чтобы увидеть, является ли членство явным или неявным.

2 голосов
/ 31 января 2013

вы можете использовать свойства tokenGroups и tokenGroupsGlobalAndUniversal, если вы находитесь на сервере Exchange.tokenGroups предоставит вам все группы безопасности, к которым принадлежит этот пользователь, включая вложенные группы и пользователей домена, пользователей и т. д. tokenGroupsGlobalAndUniversal будет включать в себя все, от tokenGroups И групп рассылки

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
                    }
                }
            }
        }
    }
2 голосов
/ 15 июня 2011

Используйте фильтр ldap рекурсивно, но запрашивайте все группы, возвращаемые после каждого запроса, чтобы уменьшить количество циклов.

Пример:

  1. Получить все группы, членом которых является пользователь
  2. Получить все группы, в которых группы 1-го уровня являются членами
  3. Получить все группы, в которых группы 2-го этапа являются участниками
  4. ...

В моемопыта редко бывает больше 5, но определенно должно быть намного меньше 30.

Кроме того:

  • Убедитесь, что вы вернули только те свойства, которые вам понадобятся.
  • Результаты кэширования могут значительно повысить производительность, но значительно усложнили мой код.
  • Обязательно используйте пул соединений.
  • Первичная группа должна обрабатываться отдельно
1 голос
/ 16 июля 2013

Если вы используете .NET 3.5 или выше, вы можете использовать пространство имен System.DirectoryServices.AccountManagement, которое действительно облегчает эту задачу.

См. Соответствующий ответ здесь: Вложенные группы Active Directory

0 голосов
/ 19 июля 2011
    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]);
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...