sAMAccountName和Domain的Active Directory LDAP查询

时间:2023-01-26 02:59:40

How do you do a query of an LDAP store by sAMAccountName and Domain? What is the "domain" property named in Active Directory or LDAP terms?

如何通过sAMAccountName和Domain查询LDAP存储?在Active Directory或LDAP术语中命名的“域”属性是什么?

This is what I have for the filter so far. I'd like to be able to add in the domain:

到目前为止,这就是我对过滤器的要求。我希望能够在域中添加:

(&(objectCategory=Person)(sAMAccountName=BTYNDALL))

7 个解决方案

#1


First, modify your search filter to only look for users and not contacts:

首先,修改您的搜索过滤器,仅查找用户而不是联系人:

(&(objectCategory=person)(objectClass=user)(sAMAccountName=BTYNDALL))

You can enumerate all of the domains of a forest by connecting to the configuration partition and enumerating all the entries in the partitions container. Sorry I don't have any C# code right now but here is some vbscript code I've used in the past:

您可以通过连接到配置分区并枚举分区容器中的所有条目来枚举林的所有域。对不起,我现在没有任何C#代码,但这里是我过去使用的一些vbscript代码:

Set objRootDSE = GetObject("LDAP://RootDSE")
AdComm.Properties("Sort on") = "name"
AdComm.CommandText = "<LDAP://cn=Partitions," & _
    objRootDSE.Get("ConfigurationNamingContext") & ">;" & _
        "(&(objectcategory=crossRef)(systemFlags=3));" & _
            "name,nCName,dnsRoot;onelevel"
set AdRs = AdComm.Execute

From that you can retrieve the name and dnsRoot of each partition:

从那里你可以检索每个分区的名称和dnsRoot:

AdRs.MoveFirst
With AdRs
  While Not .EOF
    dnsRoot = .Fields("dnsRoot")

    Set objOption = Document.createElement("OPTION")
    objOption.Text = dnsRoot(0)
    objOption.Value = "LDAP://" & dnsRoot(0) & "/" & .Fields("nCName").Value
    Domain.Add(objOption)
    .MoveNext 
  Wend 
End With

#2


You can use following queries

您可以使用以下查询

Users whose Logon Name(Pre-Windows 2000) is equal to John

登录名(Pre-Windows 2000)等于John的用户

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370)(sAMAccountName=**John**))

All Users

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370))

Enabled Users

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370)(!userAccountControl:1.2.840.113556.1.4.803:=2))

Disabled Users

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370)(userAccountControl:1.2.840.113556.1.4.803:=2))

LockedOut Users

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370)(lockouttime>=1))

#3


The best way of searching for users is (sAMAccountType=805306368).

搜索用户的最佳方式是(sAMAccountType = 805306368)。

Or for disabled users:

或者对于残疾用户:

(&(sAMAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=2))

Or for active users:

或者对于活跃用户:

(&(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

I find LDAP as not being so light at it was supposed to be.

我发现LDAP本身并不是那么轻松。

Also resource for common LDAP queries - trying to find them yourself and you will precious time and definitely make mistakes.

也是常见LDAP查询的资源 - 试图自己找到它们,你会花费宝贵的时间并且肯定会犯错误。

Regarding domains: it not possible in a single query because the domain is part of the user distinguisedName (DN) which, on Microsoft AD, is not searchable by partial matching.

关于域:在单个查询中不可能,因为域是用户distinguishedredName(DN)的一部分,在Microsoft AD上,不能通过部分匹配进行搜索。

#4


"Domain" is not a property of an LDAP object. It is more like the name of the database the object is stored in.

“域”不是LDAP对象的属性。它更像是存储对象的数据库的名称。

So you have to connect to the right database (in LDAP terms: "bind to the domain/directory server") in order to perform a search in that database.

因此,您必须连接到正确的数据库(在LDAP术语中:“绑定到域/目录服务器”)才能在该数据库中执行搜索。

Once you bound successfully, your query in it's current shape is all you need.

一旦成功绑定,您所需要的只是当前形状的查询。

BTW: Choosing "ObjectCategory=Person" over "ObjectClass=user" was a good decision. In AD, the former is an "indexed property" with excellent performance, the latter is not indexed and a tad slower.

BTW:在“ObjectClass = user”上选择“ObjectCategory = Person”是一个很好的决定。在AD中,前者是具有优异性能的“索引属性”,后者没有索引且速度稍慢。

#5


You have to perform your search in the domain:

您必须在域中执行搜索:

http://msdn.microsoft.com/en-us/library/ms677934(VS.85).aspx So, basically your should bind to a domain in order to search inside this domain.

http://msdn.microsoft.com/en-us/library/ms677934(VS.85).aspx所以,基本上你应该绑定到域以便在这个域内搜索。

#6


If you're using .NET, use the DirectorySearcher class. You can pass in your domain as a string into the constructor.

如果您使用的是.NET,请使用DirectorySearcher类。您可以将域作为字符串传递给构造函数。

// if you domain is domain.com...
string username = "user"
string domain = "LDAP://DC=domain,DC=com";
DirectorySearcher search = new DirectorySearcher(domain);
search.Filter = "(SAMAccountName=" + username + ")";

#7


I have written a C# class incorporating

我写了一个C#类合并

  • the algorithm from Dscoduc,
  • 来自Dscoduc的算法,

  • the query optimization from sorin,
  • 来自sorin的查询优化,

  • a cache for the domain to server mapping, and
  • 域到服务器映射的缓存,以及

  • a method to search for an account name in DOMAIN\sAMAccountName format.
  • 搜索DOMAIN \ sAMAccountName格式的帐户名的方法。

However, it is not Site-aware.

但是,它不是站点感知的。

using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Text;

public static class ADUserFinder
{
    private static Dictionary<string, string> _dictDomain2LDAPPath;

    private static Dictionary<string, string> DictDomain2LDAPPath
    {
        get
        {
            if (null == _dictDomain2LDAPPath)
            {
                string configContainer;
                using (DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE"))
                    configContainer = rootDSE.Properties["ConfigurationNamingContext"].Value.ToString();

                using (DirectoryEntry partitionsContainer = new DirectoryEntry("LDAP://CN=Partitions," + configContainer))
                using (DirectorySearcher dsPartitions = new DirectorySearcher(
                        partitionsContainer,
                        "(&(objectcategory=crossRef)(systemFlags=3))",
                        new string[] { "name", "nCName", "dnsRoot" },
                        SearchScope.OneLevel
                    ))
                using (SearchResultCollection srcPartitions = dsPartitions.FindAll())
                {
                    _dictDomain2LDAPPath = srcPartitions.OfType<SearchResult>()
                        .ToDictionary(
                        result => result.Properties["name"][0].ToString(), // the DOMAIN part
                        result => $"LDAP://{result.Properties["dnsRoot"][0]}/{result.Properties["nCName"][0]}"
                    );
                }
            }

            return _dictDomain2LDAPPath;
        }
    }

    private static DirectoryEntry FindRootEntry(string domainPart)
    {
        if (DictDomain2LDAPPath.ContainsKey(domainPart))
            return new DirectoryEntry(DictDomain2LDAPPath[domainPart]);
        else
            throw new ArgumentException($"Domain \"{domainPart}\" is unknown in Active Directory");
    }

    public static DirectoryEntry FindUser(string domain, string sAMAccountName)
    {
        using (DirectoryEntry rootEntryForDomain = FindRootEntry(domain))
        using (DirectorySearcher dsUser = new DirectorySearcher(
                rootEntryForDomain,
                $"(&(sAMAccountType=805306368)(sAMAccountName={EscapeLdapSearchFilter(sAMAccountName)}))" // magic number 805306368 means "user objects", it's more efficient than (objectClass=user)
            ))
            return dsUser.FindOne().GetDirectoryEntry();
    }

    public static DirectoryEntry FindUser(string domainBackslashSAMAccountName)
    {
        string[] domainAndsAMAccountName = domainBackslashSAMAccountName.Split('\\');
        if (domainAndsAMAccountName.Length != 2)
            throw new ArgumentException($"User name \"{domainBackslashSAMAccountName}\" is not in correct format DOMAIN\\SAMACCOUNTNAME", "DomainBackslashSAMAccountName");

        string domain = domainAndsAMAccountName[0];
        string sAMAccountName = domainAndsAMAccountName[1];

        return FindUser(domain, sAMAccountName);
    }

    /// <summary>
    /// Escapes the LDAP search filter to prevent LDAP injection attacks.
    /// Copied from https://*.com/questions/649149/how-to-escape-a-string-in-c-for-use-in-an-ldap-query
    /// </summary>
    /// <param name="searchFilter">The search filter.</param>
    /// <see cref="https://blogs.oracle.com/shankar/entry/what_is_ldap_injection" />
    /// <see cref="http://msdn.microsoft.com/en-us/library/aa746475.aspx" />
    /// <returns>The escaped search filter.</returns>
    private static string EscapeLdapSearchFilter(string searchFilter)
    {
        StringBuilder escape = new StringBuilder();
        for (int i = 0; i < searchFilter.Length; ++i)
        {
            char current = searchFilter[i];
            switch (current)
            {
                case '\\':
                    escape.Append(@"\5c");
                    break;
                case '*':
                    escape.Append(@"\2a");
                    break;
                case '(':
                    escape.Append(@"\28");
                    break;
                case ')':
                    escape.Append(@"\29");
                    break;
                case '\u0000':
                    escape.Append(@"\00");
                    break;
                case '/':
                    escape.Append(@"\2f");
                    break;
                default:
                    escape.Append(current);
                    break;
            }
        }

        return escape.ToString();
    }
}

#1


First, modify your search filter to only look for users and not contacts:

首先,修改您的搜索过滤器,仅查找用户而不是联系人:

(&(objectCategory=person)(objectClass=user)(sAMAccountName=BTYNDALL))

You can enumerate all of the domains of a forest by connecting to the configuration partition and enumerating all the entries in the partitions container. Sorry I don't have any C# code right now but here is some vbscript code I've used in the past:

您可以通过连接到配置分区并枚举分区容器中的所有条目来枚举林的所有域。对不起,我现在没有任何C#代码,但这里是我过去使用的一些vbscript代码:

Set objRootDSE = GetObject("LDAP://RootDSE")
AdComm.Properties("Sort on") = "name"
AdComm.CommandText = "<LDAP://cn=Partitions," & _
    objRootDSE.Get("ConfigurationNamingContext") & ">;" & _
        "(&(objectcategory=crossRef)(systemFlags=3));" & _
            "name,nCName,dnsRoot;onelevel"
set AdRs = AdComm.Execute

From that you can retrieve the name and dnsRoot of each partition:

从那里你可以检索每个分区的名称和dnsRoot:

AdRs.MoveFirst
With AdRs
  While Not .EOF
    dnsRoot = .Fields("dnsRoot")

    Set objOption = Document.createElement("OPTION")
    objOption.Text = dnsRoot(0)
    objOption.Value = "LDAP://" & dnsRoot(0) & "/" & .Fields("nCName").Value
    Domain.Add(objOption)
    .MoveNext 
  Wend 
End With

#2


You can use following queries

您可以使用以下查询

Users whose Logon Name(Pre-Windows 2000) is equal to John

登录名(Pre-Windows 2000)等于John的用户

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370)(sAMAccountName=**John**))

All Users

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370))

Enabled Users

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370)(!userAccountControl:1.2.840.113556.1.4.803:=2))

Disabled Users

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370)(userAccountControl:1.2.840.113556.1.4.803:=2))

LockedOut Users

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370)(lockouttime>=1))

#3


The best way of searching for users is (sAMAccountType=805306368).

搜索用户的最佳方式是(sAMAccountType = 805306368)。

Or for disabled users:

或者对于残疾用户:

(&(sAMAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=2))

Or for active users:

或者对于活跃用户:

(&(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

I find LDAP as not being so light at it was supposed to be.

我发现LDAP本身并不是那么轻松。

Also resource for common LDAP queries - trying to find them yourself and you will precious time and definitely make mistakes.

也是常见LDAP查询的资源 - 试图自己找到它们,你会花费宝贵的时间并且肯定会犯错误。

Regarding domains: it not possible in a single query because the domain is part of the user distinguisedName (DN) which, on Microsoft AD, is not searchable by partial matching.

关于域:在单个查询中不可能,因为域是用户distinguishedredName(DN)的一部分,在Microsoft AD上,不能通过部分匹配进行搜索。

#4


"Domain" is not a property of an LDAP object. It is more like the name of the database the object is stored in.

“域”不是LDAP对象的属性。它更像是存储对象的数据库的名称。

So you have to connect to the right database (in LDAP terms: "bind to the domain/directory server") in order to perform a search in that database.

因此,您必须连接到正确的数据库(在LDAP术语中:“绑定到域/目录服务器”)才能在该数据库中执行搜索。

Once you bound successfully, your query in it's current shape is all you need.

一旦成功绑定,您所需要的只是当前形状的查询。

BTW: Choosing "ObjectCategory=Person" over "ObjectClass=user" was a good decision. In AD, the former is an "indexed property" with excellent performance, the latter is not indexed and a tad slower.

BTW:在“ObjectClass = user”上选择“ObjectCategory = Person”是一个很好的决定。在AD中,前者是具有优异性能的“索引属性”,后者没有索引且速度稍慢。

#5


You have to perform your search in the domain:

您必须在域中执行搜索:

http://msdn.microsoft.com/en-us/library/ms677934(VS.85).aspx So, basically your should bind to a domain in order to search inside this domain.

http://msdn.microsoft.com/en-us/library/ms677934(VS.85).aspx所以,基本上你应该绑定到域以便在这个域内搜索。

#6


If you're using .NET, use the DirectorySearcher class. You can pass in your domain as a string into the constructor.

如果您使用的是.NET,请使用DirectorySearcher类。您可以将域作为字符串传递给构造函数。

// if you domain is domain.com...
string username = "user"
string domain = "LDAP://DC=domain,DC=com";
DirectorySearcher search = new DirectorySearcher(domain);
search.Filter = "(SAMAccountName=" + username + ")";

#7


I have written a C# class incorporating

我写了一个C#类合并

  • the algorithm from Dscoduc,
  • 来自Dscoduc的算法,

  • the query optimization from sorin,
  • 来自sorin的查询优化,

  • a cache for the domain to server mapping, and
  • 域到服务器映射的缓存,以及

  • a method to search for an account name in DOMAIN\sAMAccountName format.
  • 搜索DOMAIN \ sAMAccountName格式的帐户名的方法。

However, it is not Site-aware.

但是,它不是站点感知的。

using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Linq;
using System.Text;

public static class ADUserFinder
{
    private static Dictionary<string, string> _dictDomain2LDAPPath;

    private static Dictionary<string, string> DictDomain2LDAPPath
    {
        get
        {
            if (null == _dictDomain2LDAPPath)
            {
                string configContainer;
                using (DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE"))
                    configContainer = rootDSE.Properties["ConfigurationNamingContext"].Value.ToString();

                using (DirectoryEntry partitionsContainer = new DirectoryEntry("LDAP://CN=Partitions," + configContainer))
                using (DirectorySearcher dsPartitions = new DirectorySearcher(
                        partitionsContainer,
                        "(&(objectcategory=crossRef)(systemFlags=3))",
                        new string[] { "name", "nCName", "dnsRoot" },
                        SearchScope.OneLevel
                    ))
                using (SearchResultCollection srcPartitions = dsPartitions.FindAll())
                {
                    _dictDomain2LDAPPath = srcPartitions.OfType<SearchResult>()
                        .ToDictionary(
                        result => result.Properties["name"][0].ToString(), // the DOMAIN part
                        result => $"LDAP://{result.Properties["dnsRoot"][0]}/{result.Properties["nCName"][0]}"
                    );
                }
            }

            return _dictDomain2LDAPPath;
        }
    }

    private static DirectoryEntry FindRootEntry(string domainPart)
    {
        if (DictDomain2LDAPPath.ContainsKey(domainPart))
            return new DirectoryEntry(DictDomain2LDAPPath[domainPart]);
        else
            throw new ArgumentException($"Domain \"{domainPart}\" is unknown in Active Directory");
    }

    public static DirectoryEntry FindUser(string domain, string sAMAccountName)
    {
        using (DirectoryEntry rootEntryForDomain = FindRootEntry(domain))
        using (DirectorySearcher dsUser = new DirectorySearcher(
                rootEntryForDomain,
                $"(&(sAMAccountType=805306368)(sAMAccountName={EscapeLdapSearchFilter(sAMAccountName)}))" // magic number 805306368 means "user objects", it's more efficient than (objectClass=user)
            ))
            return dsUser.FindOne().GetDirectoryEntry();
    }

    public static DirectoryEntry FindUser(string domainBackslashSAMAccountName)
    {
        string[] domainAndsAMAccountName = domainBackslashSAMAccountName.Split('\\');
        if (domainAndsAMAccountName.Length != 2)
            throw new ArgumentException($"User name \"{domainBackslashSAMAccountName}\" is not in correct format DOMAIN\\SAMACCOUNTNAME", "DomainBackslashSAMAccountName");

        string domain = domainAndsAMAccountName[0];
        string sAMAccountName = domainAndsAMAccountName[1];

        return FindUser(domain, sAMAccountName);
    }

    /// <summary>
    /// Escapes the LDAP search filter to prevent LDAP injection attacks.
    /// Copied from https://*.com/questions/649149/how-to-escape-a-string-in-c-for-use-in-an-ldap-query
    /// </summary>
    /// <param name="searchFilter">The search filter.</param>
    /// <see cref="https://blogs.oracle.com/shankar/entry/what_is_ldap_injection" />
    /// <see cref="http://msdn.microsoft.com/en-us/library/aa746475.aspx" />
    /// <returns>The escaped search filter.</returns>
    private static string EscapeLdapSearchFilter(string searchFilter)
    {
        StringBuilder escape = new StringBuilder();
        for (int i = 0; i < searchFilter.Length; ++i)
        {
            char current = searchFilter[i];
            switch (current)
            {
                case '\\':
                    escape.Append(@"\5c");
                    break;
                case '*':
                    escape.Append(@"\2a");
                    break;
                case '(':
                    escape.Append(@"\28");
                    break;
                case ')':
                    escape.Append(@"\29");
                    break;
                case '\u0000':
                    escape.Append(@"\00");
                    break;
                case '/':
                    escape.Append(@"\2f");
                    break;
                default:
                    escape.Append(current);
                    break;
            }
        }

        return escape.ToString();
    }
}