Как получить все группы AD для определенного пользователя?

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

Я написал следующий код. Но я не могу продолжать дальше, так как не знаю, как предоставить фильтр и как получить доступ к свойствам.

class Program
{
    static void Main(string[] args)
    {
        DirectoryEntry de = new DirectoryEntry("LDAP://mydomain.com");
        DirectorySearcher searcher = new DirectorySearcher(de);
        searcher.Filter = "(&(ObjectClass=group))";
        searcher.PropertiesToLoad.Add("distinguishedName");
        searcher.PropertiesToLoad.Add("sAMAccountName");
        searcher.PropertiesToLoad.Add("name");
        searcher.PropertiesToLoad.Add("objectSid");
        SearchResultCollection results = searcher.FindAll();
        int i = 1;
        foreach (SearchResult res in results)
        {
            Console.WriteLine("Result" + Convert.ToString(i++));
            DisplayProperties("distinguishedName", res);
            DisplayProperties("sAMAccouontName", res);
            DisplayProperties("name", res);
            DisplayProperties("objectSid", res);
            Console.WriteLine();
        }

        Console.ReadKey();
    }

    private static void DisplayProperties(string property, SearchResult res)
    {
        Console.WriteLine("\t" + property);
        ResultPropertyValueCollection col = res.Properties[property];
        foreach (object o in col)
        {
            Console.WriteLine("\t\t" + o.ToString());
        }
    }
}

Любые идеи?

Ответ 1

Просто запросите свойство memberOf и повторите попытку, возвращаясь, например:

            search.PropertiesToLoad.Add("memberOf");
            StringBuilder groupNames = new StringBuilder(); //stuff them in | delimited

                SearchResult result = search.FindOne();
                int propertyCount = result.Properties["memberOf"].Count;
                String dn;
                int equalsIndex, commaIndex;

                for (int propertyCounter = 0; propertyCounter < propertyCount;
                    propertyCounter++)
                {
                    dn = (String)result.Properties["memberOf"][propertyCounter];

                    equalsIndex = dn.IndexOf("=", 1);
                    commaIndex = dn.IndexOf(",", 1);
                    if (-1 == equalsIndex)
                    {
                        return null;
                    }
                    groupNames.Append(dn.Substring((equalsIndex + 1),
                                (commaIndex - equalsIndex) - 1));
                    groupNames.Append("|");
                }

            return groupNames.ToString();

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

Ответ 2

Вы должны использовать System.DirectoryServices.AccountManagement. Это намного проще. Вот хороший проект кода article, дающий вам обзор всех классов в этой DLL.

Как вы указали, ваш текущий подход не определяет основную группу. На самом деле это намного хуже, чем вы думали. Есть еще несколько случаев, когда он не работает, например, локальная группа домена из другого домена. Здесь вы можете проверить здесь. Вот как выглядит код, если вы переключитесь на использование System.DirectoryServices.AccountManagement. Следующий код может найти непосредственные группы, которым назначен этот пользователь, который включает основную группу.

UserPrincipal user = UserPrincipal.FindByIdentity(new PrincipalContext (ContextType.Domain, "mydomain.com"), IdentityType.SamAccountName, "username");
foreach (GroupPrincipal group in user.GetGroups())
{
    Console.Out.WriteLine(group);
}

Ответ 3

Используйте tokenGroups:

DirectorySearcher ds = new DirectorySearcher();
ds.Filter = String.Format("(&(objectClass=user)(sAMAccountName={0}))", username);
SearchResult sr = ds.FindOne();

DirectoryEntry user = sr.GetDirectoryEntry();
user.RefreshCache(new string[] { "tokenGroups" });

for (int i = 0; i < user.Properties["tokenGroups"].Count; i++) {
    SecurityIdentifier sid = new SecurityIdentifier((byte[]) user.Properties["tokenGroups"][i], 0);
    NTAccount nt = (NTAccount)sid.Translate(typeof(NTAccount));
    //do something with the SID or name (nt.Value)
}

Примечание: это получает только группы безопасности

Ответ 4

Этот код работает еще быстрее (два раза быстрее, чем моя предыдущая версия):

    public List<String> GetUserGroups(WindowsIdentity identity)
    {
        List<String> groups = new List<String>();

        String userName = identity.Name;
        int pos = userName.IndexOf(@"\");
        if (pos > 0) userName = userName.Substring(pos + 1);

        PrincipalContext domain = new PrincipalContext(ContextType.Domain, "riomc.com");
        UserPrincipal user = UserPrincipal.FindByIdentity(domain, IdentityType.SamAccountName, userName); // NGeodakov

        DirectoryEntry de = new DirectoryEntry("LDAP://RIOMC.com");
        DirectorySearcher search = new DirectorySearcher(de);
        search.Filter = "(&(objectClass=group)(member=" + user.DistinguishedName + "))";
        search.PropertiesToLoad.Add("cn");
        search.PropertiesToLoad.Add("samaccountname");
        search.PropertiesToLoad.Add("memberOf");

        SearchResultCollection results = search.FindAll();
        foreach (SearchResult sr in results)
        {
            GetUserGroupsRecursive(groups, sr, de);
        }

        return groups;
    }

    public void GetUserGroupsRecursive(List<String> groups, SearchResult sr, DirectoryEntry de)
    {
        if (sr == null) return;

        String group = (String)sr.Properties["cn"][0];
        if (String.IsNullOrEmpty(group))
        {
            group = (String)sr.Properties["samaccountname"][0];
        }
        if (!groups.Contains(group))
        {
            groups.Add(group);
        }

        DirectorySearcher search;
        SearchResult sr1;
        String name;
        int equalsIndex, commaIndex;
        foreach (String dn in sr.Properties["memberof"])
        {
            equalsIndex = dn.IndexOf("=", 1);
            if (equalsIndex > 0)
            {
                commaIndex = dn.IndexOf(",", equalsIndex + 1);
                name = dn.Substring(equalsIndex + 1, commaIndex - equalsIndex - 1);

                search = new DirectorySearcher(de);
                search.Filter = "(&(objectClass=group)(|(cn=" + name + ")(samaccountname=" + name + ")))";
                search.PropertiesToLoad.Add("cn");
                search.PropertiesToLoad.Add("samaccountname");
                search.PropertiesToLoad.Add("memberOf");
                sr1 = search.FindOne();
                GetUserGroupsRecursive(groups, sr1, de);
            }
        }
    }

Ответ 5

Следующий пример из статьи проекта кода (почти) Все в Active Directory через С#:

// userDn is a Distinguished Name such as:
// "LDAP://CN=Joe Smith,OU=Sales,OU=domain,OU=com"
public ArrayList Groups(string userDn, bool recursive)
{
    ArrayList groupMemberships = new ArrayList();
    return AttributeValuesMultiString("memberOf", userDn,
        groupMemberships, recursive);
}

public ArrayList AttributeValuesMultiString(string attributeName,
     string objectDn, ArrayList valuesCollection, bool recursive)
{
    DirectoryEntry ent = new DirectoryEntry(objectDn);
    PropertyValueCollection ValueCollection = ent.Properties[attributeName];
    IEnumerator en = ValueCollection.GetEnumerator();

    while (en.MoveNext())
    {
        if (en.Current != null)
        {
            if (!valuesCollection.Contains(en.Current.ToString()))
            {
                valuesCollection.Add(en.Current.ToString());
                if (recursive)
                {
                    AttributeValuesMultiString(attributeName, "LDAP://" +
                    en.Current.ToString(), valuesCollection, true);
                }
            }
        }
    }
    ent.Close();
    ent.Dispose();
    return valuesCollection;
}

Просто вызовите метод Группы с Distinguished Name для пользователя и передайте флаг bool, чтобы указать если вы хотите включить членство в вложенных/дочерних группах в результирующий ArrayList:

ArrayList groups = Groups("LDAP://CN=Joe Smith,OU=Sales,OU=domain,OU=com", true);
foreach (string groupName in groups)
{
    Console.WriteLine(groupName);
}

Если вам нужно сделать какой-либо серьезный уровень программирования Active Directory в .NET, я настоятельно рекомендую закладок и обзор статьи Code Project, упомянутой выше.

Ответ 6

Вот код, который работал у меня:

public ArrayList GetBBGroups(WindowsIdentity identity)
{
    ArrayList groups = new ArrayList();

    try
    {
        String userName = identity.Name;
        int pos = userName.IndexOf(@"\");
        if (pos > 0) userName = userName.Substring(pos + 1);

        PrincipalContext domain = new PrincipalContext(ContextType.Domain, "riomc.com");
        UserPrincipal user = UserPrincipal.FindByIdentity(domain, IdentityType.SamAccountName, userName);

        DirectoryEntry de = new DirectoryEntry("LDAP://RIOMC.com");
        DirectorySearcher search = new DirectorySearcher(de);
        search.Filter = "(&(objectClass=group)(member=" + user.DistinguishedName + "))";
        search.PropertiesToLoad.Add("samaccountname");
        search.PropertiesToLoad.Add("cn");

        String name;
        SearchResultCollection results = search.FindAll();
        foreach (SearchResult result in results)
        {
            name = (String)result.Properties["samaccountname"][0];
            if (String.IsNullOrEmpty(name))
            {
                name = (String)result.Properties["cn"][0];
            }
            GetGroupsRecursive(groups, de, name);
        }
    }
    catch
    {
        // return an empty list...
    }

    return groups;
}

public void GetGroupsRecursive(ArrayList groups, DirectoryEntry de, String dn)
{
    DirectorySearcher search = new DirectorySearcher(de);
    search.Filter = "(&(objectClass=group)(|(samaccountname=" + dn + ")(cn=" + dn + ")))";
    search.PropertiesToLoad.Add("memberof");

    String group, name;
    SearchResult result = search.FindOne();
    if (result == null) return;

    group = @"RIOMC\" + dn;
    if (!groups.Contains(group))
    {
        groups.Add(group);
    }
    if (result.Properties["memberof"].Count == 0) return;
    int equalsIndex, commaIndex;
    foreach (String dn1 in result.Properties["memberof"])
    {
        equalsIndex = dn1.IndexOf("=", 1);
        if (equalsIndex > 0)
        {
            commaIndex = dn1.IndexOf(",", equalsIndex + 1);
            name = dn1.Substring(equalsIndex + 1, commaIndex - equalsIndex - 1);
            GetGroupsRecursive(groups, de, name);
        }
    }
}

Я измерил его производительность в цикле 200 прогонов против кода, использующего рекурсивный метод AttributeValuesMultiString; и он работал в 1,3 раза быстрее. Возможно, это связано с нашими настройками AD. Оба фрагмента дали тот же результат.

Ответ 7

Я хотел бы сказать, что в Microsoft LDAP есть несколько специальных способов рекурсивного поиска всех членств пользователя.

  1. Правило соответствия, которое вы можете указать для атрибута "member". В частности, использование правила Microsoft Exclusive LDAP_MATCHING_RULE_IN_CHAIN для атрибута "member" позволяет выполнять рекурсивный/вложенный поиск членства. Правило используется, когда вы добавляете его после атрибута участника. Ex. (член: 1.2.840.113556.1.4.1941: = XXXXX)

  2. Для того же домена, что и для учетной записи, фильтр может использовать <SID = S-1-5-21-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX> вместо атрибута Accounts DistinguishedName, который очень удобен для использования междоменного домена при необходимости. ОДНАКО кажется, что вам нужно использовать ForeignSecurityPrincipal <GUID = YYYY>, так как он не разрешит ваш SID, так как кажется, что тег <SID => не учитывает тип объекта ForeignSecurityPrincipal. Вы также можете использовать ForeignSecurityPrincipal DistinguishedName.

Используя эти знания, вы можете LDAP запрашивать тех, кто трудно получить членство, таких как группы "Локальный домен", членом которых является Учетная запись, но если вы не посмотрите на членов группы, вы не узнаете, был ли пользователь участником.

//Get Direct+Indirect Memberships of User (where SID is XXXXXX)

string str = "(& (objectCategory=group)(member:1.2.840.113556.1.4.1941:=<SID=XXXXXX>) )";

//Get Direct+Indirect **Domain Local** Memberships of User (where SID is XXXXXX)

string str2 = "(& (objectCategory=group)(|(groupType=-2147483644)(groupType=4))(member:1.2.840.113556.1.4.1941:=<SID=XXXXXX>) )";

//TAA DAA



Не стесняйтесь пробовать эти запросы LDAP после замены SID пользователя, для которого вы хотите получить все членства в группах. Я полагаю, что это аналогично, если не тому же самому запросу, что и команда PowerShell Get-ADPrincipalGroupMembership, которая используется за кулисами. Команда заявляет: "Если вы хотите искать локальные группы в другом домене, используйте параметр ResourceContextServer, чтобы указать альтернативный сервер в другом домене".

Если вы достаточно знакомы с С# и Active Directory, вы должны знать, как выполнять поиск LDAP с использованием предоставленных запросов LDAP.

Дополнительная документация:

Ответ 8

Если у вас есть соединение LDAP с именем пользователя и паролем для подключения к Active Directory, вот код, который я использовал для правильного подключения:

using System.DirectoryServices.AccountManagement;

// ...

// Connection information
var connectionString = "LDAP://domain.com/DC=domain,DC=com";
var connectionUsername = "your_ad_username";
var connectionPassword = "your_ad_password";

// Get groups for this user
var username = "myusername";

// Split the LDAP Uri
var uri = new Uri(connectionString);
var host = uri.Host;
var container = uri.Segments.Count() >=1 ? uri.Segments[1] : "";

// Create context to connect to AD
var princContext = new PrincipalContext(ContextType.Domain, host, container, connectionUsername, connectionPassword);

// Get User
UserPrincipal user = UserPrincipal.FindByIdentity(princContext, IdentityType.SamAccountName, username);

// Browse user groups
foreach (GroupPrincipal group in user.GetGroups())
{
    Console.Out.WriteLine(group.Name);
}

Ответ 9

Вот как я перечисляю все группы (прямые и косвенные) для определенного отличительного имени:

Строка 1.2.840.113556.1.4.1941 указывает LDAP_MATCHING_RULE_IN_CHAIN.

Это правило ограничено фильтрами, которые применяются к DN. Это специальный "расширенный" оператор сопоставления, который проходит цепочку предков в объектах вплоть до корня, пока не найдет совпадение.

Этот метод в 25 раз быстрее, чем метод UserPrincipal.GetGroups() в моем тестировании.

Примечание. Основная группа (обычно пользователи домена) не возвращается этим методом или методом GetGroups(). Чтобы получить имя основной группы, я подтвердил, что этот метод работает.

Кроме того, я нашел этот список фильтров LDAP чрезвычайно полезным.

private IEnumerable<string> GetGroupsForDistinguishedName(DirectoryEntry domainDirectoryEntry, string distinguishedName)
{
    var groups = new List<string>();
    if (!string.IsNullOrEmpty(distinguishedName))
    {
        var getGroupsFilterForDn = $"(member:1.2.840.113556.1.4.1941:={distinguishedName})";
        using (var dirSearch = CreateDirectorySearcher(domainDirectoryEntry, getGroupsFilterForDn))
        {
            dirSearch.PropertiesToLoad.Add("name");

            using (var results = dirSearch.FindAll())
            {
                foreach (SearchResult result in results)
                {
                    if (result.Properties.Contains("name"))
                        groups.Add((string)result.Properties["name"][0]);
                }
            }
        }
    }

    return groups;
}

Ответ 10

PrincipalContext pc1 = new PrincipalContext(ContextType.Domain, "DomainName", UserAccountOU, UserName, Password);
UserPrincipal UserPrincipalID = UserPrincipal.FindByIdentity(pc1, IdentityType.SamAccountName, UserID);

searcher.Filter = "(&(ObjectClass=group)(member = " + UserPrincipalID.DistinguishedName + "));