未验证 提交 49cfb350 编写于 作者: D Dan Moseley 提交者: GitHub

Improve S.DS.P tests, fix async API, and improve error messages (#47703)

上级 8e943c5e
......@@ -70,9 +70,11 @@ internal enum LdapOption
LDAP_OPT_CLIENT_CONTROLS = 0x13, // Not Supported in Windows
LDAP_OPT_API_FEATURE_INFO = 0x15,
LDAP_OPT_HOST_NAME = 0x30,
LDAP_OPT_ERROR_NUMBER = 0x31,
LDAP_OPT_ERROR_STRING = 0x32,
LDAP_OPT_SERVER_ERROR = 0x33,
LDAP_OPT_ERROR_NUMBER = 0x31, // aka LDAP_OPT_RESULT_CODE
LDAP_OPT_ERROR_STRING = 0x32, // aka LDAP_OPT_DIAGNOSTIC_MESSAGE
// This one is overloaded between Windows and Linux servers:
// in OpenLDAP, LDAP_OPT_MATCHED_DN = 0x33
LDAP_OPT_SERVER_ERROR = 0x33, // Not Supported in Linux
LDAP_OPT_SERVER_EXT_ERROR = 0x34, // Not Supported in Linux
LDAP_OPT_HOST_REACHABLE = 0x3E, // Not Supported in Linux
LDAP_OPT_PING_KEEP_ALIVE = 0x36, // Not Supported in Linux
......
<Configuration>
<CommentThatAllowsDoubleHyphens>
To enable the tests marked with [ConditionalFact(nameof(IsLdapConfigurationExist))], you need to setup an LDAP server and provide the needed server info here.
To ship, we should test on both an Active Directory LDAP server, and at least one other server, as behaviors are a little different. However for local testing, it is easiest to connect to an OpenDJ LDAP server in a docker container (eg., in WSL2).
OPENDJ SERVER
=============
docker run -p 1389:1389 -e ROOT_USER_DN='cn=admin,dc=example,dc=com' -e BASE_DN='dc=example,dc=com' -e ROOT_PASSWORD=password -d openidentityplatform/opendj
test it with this command - it should return some results in WSL2
ldapsearch -h localhost -p 1389 -D 'cn=admin,dc=example,dc=com' -x -w password
this command views the status
docker exec -it opendj01 /bin/bash /opt/opendj/bin/status -D 'cn=admin,dc=example,dc=com' -w password
SLAPD OPENLDAP SERVER
=====================
docker run -p:390:389 -e LDAP_DOMAIN=example.com -e LDAP_ROOTPASS=password -d nickstenning/slapd
and to test and view status
ldapsearch -h localhost -p 390 -D 'cn=admin,dc=example,dc=com' -x -w password
docker exec -it slapd01 slapcat
ACTIVE DIRECTORY
================
For Active Directory, it is necessary to set up a VM that is a Domain Controller. Typical settings look like this, depending on the values you choose during the setup wizard (observe the default port is different, and user is prefixed by the AD user domain):
When running against Active Directory from a Windows client, you should not see any tests marked `[ConditionalFact(nameof(IsActiveDirectoryServer))]` skip. At the moment, that means that there are zero total skipped test cases when you run against Active Directory using tests on Windows.
If you are running your AD server as a VM on the same machine that you are running WSL2, you must execute this command on the host to bridge the two Hyper-V networks so that it is visible from WSL2:
Get-NetIPInterface | where {$_.InterfaceAlias -eq 'vEthernet (WSL)' -or $_.InterfaceAlias -eq 'vEthernet (Default Switch)'} | Set-NetIPInterface -Forwarding Enabled
The WSL2 VM should now be able to see the AD VM by IP address. To make it visible by host name, it's probably easiest to just add it to /etc/hosts.
For the S.DS.AM and S.DS tests (which only run on Windows) to work successfully against AD, the test machine needs to be on the AD domain. It is easiest to have the test machine in a VM as well. Set the primary DNS server for the test machine to be the AD machine, join the machine to that domain, and log into it as a (the) domain user.
To verify the AD connection from Linux, use either of these:
ldapsearch -h danmose-ldap -p 389 -D 'CN=Administrator,CN=Users,DC=danmose-domain,DC=com' -x -w $TESTPASSWORD
ldapsearch -h danmose-ldap -p 389 -D 'danmose-domain\Administrator' -x -w $TESTPASSWORD
Note:
`Password` is read from the environment if it is surrounded by %, eg %TESTPASSWORD%
</CommentThatAllowsDoubleHyphens>
<!-- To choose a connection, set an environment variable LDAP_TEST_SERVER_INDEX
to the zero-based index, eg., 0, 1, or 2
If you don't set LDAP_TEST_SERVER_INDEX then tests that require a server
will skip.
-->
<Connection Name="OPENDJ SERVER">
<ServerName>localhost</ServerName>
<SearchDN>DC=example,DC=com</SearchDN>
<Port>1389</Port>
<User>cn=admin,dc=example,dc=com</User>
<Password>password</Password>
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
</Connection>
<Connection Name="SLAPD OPENLDAP SERVER">
<ServerName>localhost</ServerName>
<SearchDN>DC=example,DC=com</SearchDN>
<Port>390</Port>
<User>cn=admin,dc=example,dc=com</User>
<Password>password</Password>
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
</Connection>
<Connection Name="ACTIVE DIRECTORY SERVER">
<ServerName>danmose-ldap.danmose-domain.com</ServerName>
<SearchDN>DC=danmose-domain,DC=com</SearchDN>
<Port>389</Port>
<User>danmose-domain\Administrator</User>
<Password>%TESTPASSWORD%</Password>
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
</Connection>
</Configuration>
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace System.DirectoryServices.Tests
{
internal class LdapConfiguration
{
private LdapConfiguration(string serverName, string domain, string userName, string password, string port, AuthenticationTypes at)
private LdapConfiguration(string serverName, string searchDn, string userName, string password, string port, AuthenticationTypes at)
{
ServerName = serverName;
Domain = domain;
SearchDn = searchDn;
UserName = userName;
Password = password;
Port = port;
......@@ -26,9 +28,9 @@ private LdapConfiguration(string serverName, string domain, string userName, str
internal string UserName { get; set; }
internal string Password { get; set; }
internal string Port { get; set; }
internal string Domain { get; set; }
internal string SearchDn { get; set; }
internal AuthenticationTypes AuthenticationTypes { get; set; }
internal string LdapPath => string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/{Domain}" : $"LDAP://{ServerName}:{Port}/{Domain}";
internal string LdapPath => string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/{SearchDn}" : $"LDAP://{ServerName}:{Port}/{SearchDn}";
internal string RootDSEPath => string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/rootDSE" : $"LDAP://{ServerName}:{Port}/rootDSE";
internal string UserNameWithNoDomain
{
......@@ -48,7 +50,7 @@ internal string UserNameWithNoDomain
internal string GetLdapPath(string prefix) // like "ou=something"
{
return string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/{prefix},{Domain}" : $"LDAP://{ServerName}:{Port}/{prefix},{Domain}";
return string.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/{prefix},{SearchDn}" : $"LDAP://{ServerName}:{Port}/{prefix},{SearchDn}";
}
private const string LDAP_CAP_ACTIVE_DIRECTORY_OID = "1.2.840.113556.1.4.800";
......@@ -59,6 +61,7 @@ internal bool IsActiveDirectoryServer
{
try
{
// This requires System.DirectoryServices.dll, which is Windows-only
using (DirectoryEntry rootDse = new DirectoryEntry(LdapConfiguration.Configuration.RootDSEPath,
LdapConfiguration.Configuration.UserName,
LdapConfiguration.Configuration.Password,
......@@ -77,77 +80,95 @@ internal bool IsActiveDirectoryServer
internal static LdapConfiguration GetConfiguration(string configFile)
{
if (!File.Exists(configFile))
Environment.FailFast("LDAP test configuration file not found: " + configFile);
// To use test servers, set an environment variable LDAP_TEST_SERVER_INDEX
// to the 0-based index of the <Connection> element in LDAP.Configuration.xml
if (!int.TryParse(Environment.GetEnvironmentVariable("LDAP_TEST_SERVER_INDEX"), out int serverIndex))
{
return null;
}
LdapConfiguration ldapConfig = null;
try
{
XElement configuration = XDocument.Load(configFile).Element("Configuration");
XElement connection = configuration.Elements("Connection").Skip(serverIndex).First();
Debug.WriteLine($"Using test LDAP server {connection.Attribute("Name").Value}");
string serverName = "";
string domain = "";
string searchDn = "";
string port = "";
string user = "";
string password = "";
AuthenticationTypes at = AuthenticationTypes.None;
XElement config = XDocument.Load(configFile).Element("Configuration");
if (config != null)
{
XElement child = config.Element("ServerName");
if (child != null)
serverName = child.Value;
XElement child = connection.Element("ServerName");
if (child != null)
serverName = child.Value;
child = config.Element("Domain");
if (child != null)
domain = child.Value;
child = connection.Element("SearchDN");
if (child != null)
searchDn = child.Value;
child = config.Element("Port");
if (child != null)
port = child.Value;
child = connection.Element("Port");
if (child != null)
port = child.Value;
child = config.Element("User");
if (child != null)
user = child.Value;
child = connection.Element("User");
if (child != null)
user = child.Value;
child = config.Element("Password");
if (child != null)
password = child.Value;
child = connection.Element("Password");
if (child != null)
{
string val = child.Value;
if (val.StartsWith("%") && val.EndsWith("%"))
{
val = Environment.GetEnvironmentVariable(val.Substring(1, val.Length - 2));
}
password = val;
}
child = config.Element("AuthenticationTypes");
if (child != null)
child = connection.Element("AuthenticationTypes");
if (child != null)
{
string[] parts = child.Value.Split(',');
foreach (string p in parts)
{
string[] parts = child.Value.Split(',');
foreach (string p in parts)
{
string s = p.Trim();
if (s.Equals("Anonymous", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.Anonymous;
if (s.Equals("Delegation", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.Delegation;
if (s.Equals("Encryption", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.FastBind;
if (s.Equals("FastBind", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.FastBind;
if (s.Equals("ReadonlyServer", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.ReadonlyServer;
if (s.Equals("Sealing", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.Sealing;
if (s.Equals("Secure", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.Secure;
if (s.Equals("SecureSocketsLayer", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.SecureSocketsLayer;
if (s.Equals("ServerBind", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.ServerBind;
if (s.Equals("Signing", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.Signing;
}
string s = p.Trim();
if (s.Equals("Anonymous", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.Anonymous;
if (s.Equals("Delegation", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.Delegation;
if (s.Equals("Encryption", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.FastBind;
if (s.Equals("FastBind", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.FastBind;
if (s.Equals("ReadonlyServer", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.ReadonlyServer;
if (s.Equals("Sealing", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.Sealing;
if (s.Equals("Secure", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.Secure;
if (s.Equals("SecureSocketsLayer", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.SecureSocketsLayer;
if (s.Equals("ServerBind", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.ServerBind;
if (s.Equals("Signing", StringComparison.OrdinalIgnoreCase))
at |= AuthenticationTypes.Signing;
}
ldapConfig = new LdapConfiguration(serverName, domain, user, password, port, at);
ldapConfig = new LdapConfiguration(serverName, searchDn, user, password, port, at);
}
}
catch
catch (Exception ex)
{
// Couldn't read the configurations, usually we'll skip the tests which depend on that
// This runs within a test filter; if it throws, the test just skips. Instead we want to stop
// so that it's quite clear that the server configuration is malformed.
Environment.FailFast(ex.ToString());
}
return ldapConfig;
}
......
......@@ -9,7 +9,7 @@ internal static class GlobalConfig
{
public const DebugLevel DefaultDebugLevel =
#if DEBUG
DebugLevel.Warn;
DebugLevel.Info;
#else
DebugLevel.None;
#endif
......
<Configuration>
<!--
To enable the tests marked with [ConditionalFact(nameof(IsActiveDirectoryServer))], need to setup a Windows Active Directory server and provide the needed server info here.
After that delete this comment and have the following contents in this Xml file:
<ServerName>MyServer.example.com</ServerName>
<Domain>DC=example,DC=com</Domain>
<Port>1389</Port>
<User>adminUser</User>
<Password>password</Password>
<AuthenticationTypes>Secure,Sealing</AuthenticationTypes>
-->
</Configuration>
......@@ -14,7 +14,7 @@
Link="Common\DirectoryServices\LdapConfiguration.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="LDAP.Configuration.xml">
<Content Include="$(CommonTestPath)System\DirectoryServices\LDAP.Configuration.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
......
......@@ -238,7 +238,7 @@
<value>The LDAP connection cannot retrieve a partial result set.</value>
</data>
<data name="DefaultLdapError" xml:space="preserve">
<value>The LDAP server returned an unknown error.</value>
<value>The LDAP server returned an unknown error: {0}</value>
</data>
<data name="LDAP_PARTIAL_RESULTS" xml:space="preserve">
<value>Only partial results and referrals were received.</value>
......
......@@ -43,7 +43,7 @@ public ConnectionHandle() : base(true)
if (handle == IntPtr.Zero)
{
int error = Interop.Ldap.LdapGetLastError();
if (Utility.IsLdapError((LdapError)error))
if (LdapErrorMappings.IsLdapError(error))
{
string errorMessage = LdapErrorMappings.MapResultCode(error);
throw new LdapException(error, errorMessage);
......@@ -61,7 +61,7 @@ internal ConnectionHandle(IntPtr value, bool disposeHandle) : base(true)
if (value == IntPtr.Zero)
{
int error = Interop.Ldap.LdapGetLastError();
if (Utility.IsLdapError((LdapError)error))
if (LdapErrorMappings.IsLdapError(error))
{
string errorMessage = LdapErrorMappings.MapResultCode(error);
throw new LdapException(error, errorMessage);
......
......@@ -266,7 +266,7 @@ internal static object[] TryDecode(string format, byte[] value, out bool decodeS
if (format == null)
throw new ArgumentNullException(nameof(format));
Debug.WriteLine("Begin decoding\n");
Debug.WriteLine("Begin decoding");
UTF8Encoding utf8Encoder = new UTF8Encoding(false, true);
berval berValue = new berval();
......
......@@ -733,7 +733,7 @@ public override byte[] GetValue()
if (error != 0)
{
if (Utility.IsLdapError((LdapError)error))
if (LdapErrorMappings.IsLdapError(error))
{
string errorMessage = LdapErrorMappings.MapResultCode(error);
throw new LdapException(error, errorMessage);
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text;
using System.Runtime.Serialization;
namespace System.DirectoryServices.Protocols
......@@ -38,22 +39,35 @@ public class DirectoryOperationException : DirectoryException, ISerializable
public DirectoryOperationException(string message, Exception inner) : base(message, inner) { }
public DirectoryOperationException(DirectoryResponse response) : base(SR.DefaultOperationsError)
public DirectoryOperationException(DirectoryResponse response) :
base(CreateMessage(response, message: null))
{
Response = response;
}
public DirectoryOperationException(DirectoryResponse response, string message) : base(message)
public DirectoryOperationException(DirectoryResponse response, string message)
: base(CreateMessage(response, message))
{
Response = response;
}
public DirectoryOperationException(DirectoryResponse response, string message, Exception inner) : base(message, inner)
public DirectoryOperationException(DirectoryResponse response, string message, Exception inner)
: base(CreateMessage(response, message), inner)
{
Response = response;
}
public DirectoryResponse Response { get; internal set; }
private static string CreateMessage(DirectoryResponse response, string message)
{
string result = message ?? SR.DefaultOperationsError;
if (!string.IsNullOrEmpty(response?.ErrorMessage))
{
result += " " + response.ErrorMessage;
}
return result;
}
}
[Serializable]
......
......@@ -7,16 +7,6 @@ namespace System.DirectoryServices.Protocols
{
internal class Utility
{
internal static bool IsLdapError(LdapError error)
{
if (error == LdapError.IsLeaf || error == LdapError.InvalidCredentials || error == LdapError.SendTimeOut)
{
return true;
}
return (error >= LdapError.ServerDown && error <= LdapError.ReferralLimitExceeded);
}
internal static bool IsResultCode(ResultCode code)
{
if (code >= ResultCode.Success && code <= ResultCode.SaslBindInProgress)
......
......@@ -987,7 +987,7 @@ private void Connect()
int certError = LdapPal.SetClientCertOption(_ldapHandle, LdapOption.LDAP_OPT_CLIENT_CERTIFICATE, _clientCertificateRoutine);
if (certError != (int)ResultCode.Success)
{
if (Utility.IsLdapError((LdapError)certError))
if (LdapErrorMappings.IsLdapError(certError))
{
string certerrorMessage = LdapErrorMappings.MapResultCode(certError);
throw new LdapException(certError, certerrorMessage);
......@@ -1012,7 +1012,7 @@ private void Connect()
// Failed, throw an exception.
if (error != (int)ResultCode.Success)
{
if (Utility.IsLdapError((LdapError)error))
if (LdapErrorMappings.IsLdapError(error))
{
string errorMessage = LdapErrorMappings.MapResultCode(error);
throw new LdapException(error, errorMessage);
......@@ -1156,7 +1156,7 @@ private void BindHelper(NetworkCredential newCredential, bool needSetCredential)
string errorMessage = OperationErrorMappings.MapResultCode(error);
throw new DirectoryOperationException(null, errorMessage);
}
else if (Utility.IsLdapError((LdapError)error))
else if (LdapErrorMappings.IsLdapError(error))
{
string errorMessage = LdapErrorMappings.MapResultCode(error);
string serverErrorMessage = SessionOptions.ServerErrorMessage;
......@@ -1942,7 +1942,7 @@ private DirectoryException ConstructException(int error, LdapOperation operation
}
else
{
if (Utility.IsLdapError((LdapError)error))
if (LdapErrorMappings.IsLdapError(error))
{
string errorMessage = LdapErrorMappings.MapResultCode(error);
string serverErrorMessage = SessionOptions.ServerErrorMessage;
......
......@@ -10,23 +10,27 @@ internal enum LdapError
{
IsLeaf = 0x23,
InvalidCredentials = 49,
ServerDown = 0x51,
LocalError = 0x52,
EncodingError = 0x53,
DecodingError = 0x54,
TimeOut = 0x55,
AuthUnknown = 0x56,
FilterError = 0x57,
UserCancelled = 0x58,
ParameterError = 0x59,
NoMemory = 0x5a,
ConnectError = 0x5b,
NotSupported = 0x5c,
NoResultsReturned = 0x5e,
ControlNotFound = 0x5d,
MoreResults = 0x5f,
ClientLoop = 0x60,
ReferralLimitExceeded = 0x61,
// The following values are defined in the LDAP C API standard, and are used in Windows Winldap.h.
// See https://tools.ietf.org/html/draft-ietf-ldapext-ldap-c-api-05
// Servers built from OpenLDAP headers use negative numbers for some, as shown below.
// See https://github.com/openldap/openldap/blob/70488c22bf69be2a2c84127692413b815d8f9044/include/ldap.h#L724-L749
ServerDown = 0x51, // -1 from OpenLDAP servers
LocalError = 0x52, // -2
EncodingError = 0x53, // -3
DecodingError = 0x54, // -4
TimeOut = 0x55, // -5
AuthUnknown = 0x56, // -6
FilterError = 0x57, // -7
UserCancelled = 0x58, // -8
ParameterError = 0x59, // -9
NoMemory = 0x5a, // -10
ConnectError = 0x5b, // -11
NotSupported = 0x5c, // -12
NoResultsReturned = 0x5e, // -13
ControlNotFound = 0x5d, // -14
MoreResults = 0x5f, // -15
ClientLoop = 0x60, // -16
ReferralLimitExceeded = 0x61, // -17
SendTimeOut = 0x70
}
......@@ -56,17 +60,38 @@ internal class LdapErrorMappings
{ LdapError.SendTimeOut, SR.LDAP_SEND_TIMEOUT }
};
internal static int NormalizeResultCode(int errorCode)
{
// OpenLDAP codes -1 to -17 should map to 81 to 97 respectively;
// See note above.
return (errorCode <= -1 && errorCode >= -17) ? 80 - errorCode : errorCode;
}
public static string MapResultCode(int errorCode)
{
errorCode = NormalizeResultCode(errorCode);
s_resultCodeMapping.TryGetValue((LdapError)errorCode, out string errorMessage);
return errorMessage;
}
internal static bool IsLdapError(int errorCode)
{
LdapError error = (LdapError)NormalizeResultCode(errorCode);
if (error == LdapError.IsLeaf || error == LdapError.InvalidCredentials || error == LdapError.SendTimeOut)
{
return true;
}
return (error >= LdapError.ServerDown && error <= LdapError.ReferralLimitExceeded);
}
}
[Serializable]
[System.Runtime.CompilerServices.TypeForwardedFrom("System.DirectoryServices.Protocols, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
public class LdapException : DirectoryException, ISerializable
{
private int _errorCode;
protected LdapException(SerializationInfo info, StreamingContext context) : base(info, context) { }
public LdapException() : base() { }
......@@ -75,7 +100,8 @@ public class LdapException : DirectoryException, ISerializable
public LdapException(string message, Exception inner) : base(message, inner) { }
public LdapException(int errorCode) : base(SR.DefaultLdapError)
public LdapException(int errorCode)
: base(SR.Format(SR.DefaultLdapError, LdapErrorMappings.NormalizeResultCode(errorCode)))
{
ErrorCode = errorCode;
}
......@@ -96,7 +122,14 @@ public LdapException(int errorCode, string message, Exception inner) : base(mess
ErrorCode = errorCode;
}
public int ErrorCode { get; }
public int ErrorCode
{
get => _errorCode;
private set
{
_errorCode = LdapErrorMappings.NormalizeResultCode(value);
}
}
public string ServerErrorMessage { get; }
......@@ -139,7 +172,7 @@ public static void CheckAndSetLdapError(int error)
string errorMessage = OperationErrorMappings.MapResultCode(error);
throw new DirectoryOperationException(null, errorMessage);
}
else if (Utility.IsLdapError((LdapError)error))
else if (LdapErrorMappings.IsLdapError(error))
{
string errorMessage = LdapErrorMappings.MapResultCode(error);
throw new LdapException(error, errorMessage);
......
......@@ -19,6 +19,7 @@ internal class LdapPartialResultsProcessor
internal LdapPartialResultsProcessor(ManualResetEvent eventHandle)
{
_workThreadWaitHandle = eventHandle;
_ = new PartialResultsRetriever(eventHandle, this);
}
public void Add(LdapPartialAsyncResult asyncResult)
......
......@@ -477,7 +477,7 @@ public QueryClientCertificateCallback QueryClientCertificate
int certError = LdapPal.SetClientCertOption(_connection._ldapHandle, LdapOption.LDAP_OPT_CLIENT_CERTIFICATE, _connection._clientCertificateRoutine);
if (certError != (int)ResultCode.Success)
{
if (Utility.IsLdapError((LdapError)certError))
if (LdapErrorMappings.IsLdapError(certError))
{
string certerrorMessage = LdapErrorMappings.MapResultCode(certError);
throw new LdapException(certError, certerrorMessage);
......@@ -524,6 +524,7 @@ public VerifyServerCertificateCallback VerifyServerCertificate
}
}
// In practice, this apparently rarely if ever contains useful text
internal string ServerErrorMessage => GetStringValueHelper(LdapOption.LDAP_OPT_SERVER_ERROR, true);
internal DereferenceAlias DerefAlias
......@@ -664,7 +665,7 @@ public unsafe void StartTransportLayerSecurity(DirectoryControlCollection contro
response.ResponseName = "1.3.6.1.4.1.1466.20037";
throw new TlsOperationException(response);
}
else if (Utility.IsLdapError((LdapError)error))
else if (LdapErrorMappings.IsLdapError(error))
{
string errorMessage = LdapErrorMappings.MapResultCode(error);
throw new LdapException(error, errorMessage);
......
......@@ -15,6 +15,62 @@ public partial class DirectoryServicesProtocolsTests
internal static bool IsLdapConfigurationExist => LdapConfiguration.Configuration != null;
internal static bool IsActiveDirectoryServer => IsLdapConfigurationExist && LdapConfiguration.Configuration.IsActiveDirectoryServer;
[ConditionalFact(nameof(IsLdapConfigurationExist))]
public void TestInvalidFilter()
{
using LdapConnection connection = GetConnection();
LdapException ex = Assert.Throws<LdapException>(() =>
{
var searchRequest = new SearchRequest(LdapConfiguration.Configuration.SearchDn, "==invalid==", SearchScope.OneLevel);
_ = (SearchResponse) connection.SendRequest(searchRequest);
});
Assert.Equal(/* LdapError.FilterError */ 0x57, ex.ErrorCode);
}
[ConditionalFact(nameof(IsLdapConfigurationExist))]
public void TestInvalidSearchDn()
{
using LdapConnection connection = GetConnection();
DirectoryOperationException ex = Assert.Throws<DirectoryOperationException>(() =>
{
var searchRequest = new SearchRequest("==invaliddn==", "(objectClass=*)", SearchScope.OneLevel);
var searchResponse = (SearchResponse) connection.SendRequest(searchRequest);
});
Assert.Equal(ResultCode.InvalidDNSyntax, ex.Response.ResultCode);
}
[ConditionalFact(nameof(IsLdapConfigurationExist))]
public void TestUnavailableCriticalExtension()
{
using LdapConnection connection = GetConnection();
DirectoryOperationException ex = Assert.Throws<DirectoryOperationException>(() =>
{
var searchRequest = new SearchRequest(LdapConfiguration.Configuration.SearchDn, "(objectClass=*)", SearchScope.OneLevel);
var control = new DirectoryControl("==invalid-control==", value: null, isCritical: true, serverSide: true);
searchRequest.Controls.Add(control);
_ = (SearchResponse) connection.SendRequest(searchRequest);
});
Assert.Equal(ResultCode.UnavailableCriticalExtension, ex.Response.ResultCode);
}
[ConditionalFact(nameof(IsLdapConfigurationExist))]
public void TestUnavailableNonCriticalExtension()
{
using LdapConnection connection = GetConnection();
var searchRequest = new SearchRequest(LdapConfiguration.Configuration.SearchDn, "(objectClass=*)", SearchScope.OneLevel);
var control = new DirectoryControl("==invalid-control==", value: null, isCritical: false, serverSide: true);
searchRequest.Controls.Add(control);
_ = (SearchResponse) connection.SendRequest(searchRequest);
// Does not throw
}
[ConditionalFact(nameof(IsLdapConfigurationExist))]
public void TestAddingOU()
{
......@@ -22,11 +78,12 @@ public void TestAddingOU()
{
string ouName = "ProtocolsGroup1";
string dn = "ou=" + ouName;
try
{
DeleteEntry(connection, dn);
AddOrganizationalUnit(connection, dn);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
}
finally
......@@ -47,11 +104,11 @@ public void TestDeleteOU()
{
DeleteEntry(connection, dn);
AddOrganizationalUnit(connection, dn);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
DeleteEntry(connection, dn);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.Null(sre);
}
finally
......@@ -74,18 +131,18 @@ public void TestAddAndModifyAttribute()
AddOrganizationalUnit(connection, dn);
AddAttribute(connection, dn, "description", "Protocols Group 3");
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
Assert.Equal("Protocols Group 3", (string) sre.Attributes["description"][0]);
Assert.Throws<DirectoryOperationException>(() => AddAttribute(connection, dn, "description", "Protocols Group 3"));
ModifyAttribute(connection, dn, "description", "Modified Protocols Group 3");
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
Assert.Equal("Modified Protocols Group 3", (string) sre.Attributes["description"][0]);
DeleteAttribute(connection, dn, "description");
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
Assert.Null(sre.Attributes["description"]);
}
......@@ -112,11 +169,11 @@ public void TestNestedOUs()
try
{
AddOrganizationalUnit(connection, dnLevel1);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouLevel1Name);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouLevel1Name);
Assert.NotNull(sre);
AddOrganizationalUnit(connection, dnLevel2);
sre = SearchOrganizationalUnit(connection, dnLevel1 + "," + LdapConfiguration.Configuration.Domain, ouLevel2Name);
sre = SearchOrganizationalUnit(connection, dnLevel1 + "," + LdapConfiguration.Configuration.SearchDn, ouLevel2Name);
Assert.NotNull(sre);
}
finally
......@@ -144,13 +201,13 @@ public void TestAddUser()
try
{
AddOrganizationalUnit(connection, dn);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
AddOrganizationalRole(connection, user1Dn);
AddOrganizationalRole(connection, user2Dn);
string usersRoot = dn + "," + LdapConfiguration.Configuration.Domain;
string usersRoot = dn + "," + LdapConfiguration.Configuration.SearchDn;
sre = SearchUser(connection, usersRoot, "protocolUser1");
Assert.NotNull(sre);
......@@ -167,7 +224,7 @@ public void TestAddUser()
Assert.Null(sre);
DeleteEntry(connection, dn);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.Null(sre);
}
finally
......@@ -203,14 +260,14 @@ public void TestAddingMultipleAttributes()
DirectoryAttributeModification[] mods = new DirectoryAttributeModification[2] { mod1, mod2 };
string fullDn = dn + "," + LdapConfiguration.Configuration.Domain;
string fullDn = dn + "," + LdapConfiguration.Configuration.SearchDn;
ModifyRequest modRequest = new ModifyRequest(fullDn, mods);
ModifyResponse modResponse = (ModifyResponse) connection.SendRequest(modRequest);
Assert.Equal(ResultCode.Success, modResponse.ResultCode);
Assert.Throws<DirectoryOperationException>(() => (ModifyResponse) connection.SendRequest(modRequest));
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
Assert.Equal("Description 5", (string) sre.Attributes["description"][0]);
Assert.Throws<DirectoryOperationException>(() => AddAttribute(connection, dn, "description", "Description 5"));
......@@ -231,7 +288,7 @@ public void TestAddingMultipleAttributes()
modResponse = (ModifyResponse) connection.SendRequest(modRequest);
Assert.Equal(ResultCode.Success, modResponse.ResultCode);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
Assert.Equal("Modified Description 5", (string) sre.Attributes["description"][0]);
Assert.Throws<DirectoryOperationException>(() => AddAttribute(connection, dn, "description", "Modified Description 5"));
......@@ -250,7 +307,7 @@ public void TestAddingMultipleAttributes()
modResponse = (ModifyResponse) connection.SendRequest(modRequest);
Assert.Equal(ResultCode.Success, modResponse.ResultCode);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
Assert.Null(sre.Attributes["description"]);
Assert.Null(sre.Attributes["postalAddress"]);
......@@ -284,23 +341,23 @@ public void TestMoveAndRenameUser()
try
{
AddOrganizationalUnit(connection, dn1);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName1);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName1);
Assert.NotNull(sre);
AddOrganizationalUnit(connection, dn2);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName2);
sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName2);
Assert.NotNull(sre);
AddOrganizationalRole(connection, userDn1);
string user1Root = dn1 + "," + LdapConfiguration.Configuration.Domain;
string user2Root = dn2 + "," + LdapConfiguration.Configuration.Domain;
string user1Root = dn1 + "," + LdapConfiguration.Configuration.SearchDn;
string user2Root = dn2 + "," + LdapConfiguration.Configuration.SearchDn;
sre = SearchUser(connection, user1Root, "protocolUser7.1");
Assert.NotNull(sre);
ModifyDNRequest modDnRequest = new ModifyDNRequest( userDn1 + "," + LdapConfiguration.Configuration.Domain,
dn2 + "," + LdapConfiguration.Configuration.Domain,
ModifyDNRequest modDnRequest = new ModifyDNRequest( userDn1 + "," + LdapConfiguration.Configuration.SearchDn,
dn2 + "," + LdapConfiguration.Configuration.SearchDn,
"cn=protocolUser7.2");
ModifyDNResponse modDnResponse = (ModifyDNResponse) connection.SendRequest(modDnRequest);
Assert.Equal(ResultCode.Success, modDnResponse.ResultCode);
......@@ -338,7 +395,7 @@ public void TestAsyncSearch()
DeleteEntry(connection, dn);
AddOrganizationalUnit(connection, dn);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
for (int i=0; i<20; i++)
......@@ -348,7 +405,7 @@ public void TestAsyncSearch()
string filter = "(objectClass=organizationalUnit)";
SearchRequest searchRequest = new SearchRequest(
dn + "," + LdapConfiguration.Configuration.Domain,
dn + "," + LdapConfiguration.Configuration.SearchDn,
filter,
SearchScope.OneLevel,
null);
......@@ -432,7 +489,7 @@ public void TestPageRequests()
DeleteEntry(connection, dn);
AddOrganizationalUnit(connection, dn);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.Domain, ouName);
SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName);
Assert.NotNull(sre);
for (int i=0; i<20; i++)
......@@ -442,7 +499,7 @@ public void TestPageRequests()
string filter = "(objectClass=*)";
SearchRequest searchRequest = new SearchRequest(
dn + "," + LdapConfiguration.Configuration.Domain,
dn + "," + LdapConfiguration.Configuration.SearchDn,
filter,
SearchScope.Subtree,
null);
......@@ -478,7 +535,7 @@ public void TestPageRequests()
private void DeleteAttribute(LdapConnection connection, string entryDn, string attributeName)
{
string dn = entryDn + "," + LdapConfiguration.Configuration.Domain;
string dn = entryDn + "," + LdapConfiguration.Configuration.SearchDn;
ModifyRequest modifyRequest = new ModifyRequest(dn, DirectoryAttributeOperation.Delete, attributeName);
ModifyResponse modifyResponse = (ModifyResponse) connection.SendRequest(modifyRequest);
Assert.Equal(ResultCode.Success, modifyResponse.ResultCode);
......@@ -486,7 +543,7 @@ private void DeleteAttribute(LdapConnection connection, string entryDn, string a
private void ModifyAttribute(LdapConnection connection, string entryDn, string attributeName, string attributeValue)
{
string dn = entryDn + "," + LdapConfiguration.Configuration.Domain;
string dn = entryDn + "," + LdapConfiguration.Configuration.SearchDn;
ModifyRequest modifyRequest = new ModifyRequest(dn, DirectoryAttributeOperation.Replace, attributeName, attributeValue);
ModifyResponse modifyResponse = (ModifyResponse) connection.SendRequest(modifyRequest);
Assert.Equal(ResultCode.Success, modifyResponse.ResultCode);
......@@ -494,7 +551,7 @@ private void ModifyAttribute(LdapConnection connection, string entryDn, string a
private void AddAttribute(LdapConnection connection, string entryDn, string attributeName, string attributeValue)
{
string dn = entryDn + "," + LdapConfiguration.Configuration.Domain;
string dn = entryDn + "," + LdapConfiguration.Configuration.SearchDn;
ModifyRequest modifyRequest = new ModifyRequest(dn, DirectoryAttributeOperation.Add, attributeName, attributeValue);
ModifyResponse modifyResponse = (ModifyResponse) connection.SendRequest(modifyRequest);
Assert.Equal(ResultCode.Success, modifyResponse.ResultCode);
......@@ -502,7 +559,7 @@ private void AddAttribute(LdapConnection connection, string entryDn, string attr
private void AddOrganizationalUnit(LdapConnection connection, string entryDn)
{
string dn = entryDn + "," + LdapConfiguration.Configuration.Domain;
string dn = entryDn + "," + LdapConfiguration.Configuration.SearchDn;
AddRequest addRequest = new AddRequest(dn, "organizationalUnit");
AddResponse addResponse = (AddResponse) connection.SendRequest(addRequest);
Assert.Equal(ResultCode.Success, addResponse.ResultCode);
......@@ -510,7 +567,7 @@ private void AddOrganizationalUnit(LdapConnection connection, string entryDn)
private void AddOrganizationalRole(LdapConnection connection, string entryDn)
{
string dn = entryDn + "," + LdapConfiguration.Configuration.Domain;
string dn = entryDn + "," + LdapConfiguration.Configuration.SearchDn;
AddRequest addRequest = new AddRequest(dn, "organizationalRole");
AddResponse addResponse = (AddResponse) connection.SendRequest(addRequest);
Assert.Equal(ResultCode.Success, addResponse.ResultCode);
......@@ -520,7 +577,7 @@ private void DeleteEntry(LdapConnection connection, string entryDn)
{
try
{
string dn = entryDn + "," + LdapConfiguration.Configuration.Domain;
string dn = entryDn + "," + LdapConfiguration.Configuration.SearchDn;
DeleteRequest delRequest = new DeleteRequest(dn);
DeleteResponse delResponse = (DeleteResponse) connection.SendRequest(delRequest);
Assert.Equal(ResultCode.Success, delResponse.ResultCode);
......@@ -570,8 +627,11 @@ private LdapConnection GetConnection()
AuthType = AuthType.Basic
};
connection.Bind();
// Set server protocol before bind; OpenLDAP servers default
// to LDAP v2, which we do not support, and will return LDAP_PROTOCOL_ERROR
connection.SessionOptions.ProtocolVersion = 3;
connection.Bind();
connection.Timeout = new TimeSpan(0, 3, 0);
return connection;
}
......
<!--
To enable the tests marked with [ConditionalFact(nameof(IsLdapConfigurationExist))], need to setup LDAP server and provide the needed server info here.
The easiest way to get LDAP server is to download and install OpenDJ https://backstage.forgerock.com/downloads/OpenDJ/Directory%20Services/5.0.0#browse.
Download "DS zip" file then extract it. you can set it up by running a command like that following:
/setup directory-server --sampleData 2 --rootUserDN "cn=Directory Manager" --rootUserPassword password --hostname localhost.localdomain --ldapPort 1389 --ldapsPort 1636 --httpPort 8080 --httpsPort 8443 --adminConnectorPort 4444 --baseDN dc=example,dc=com --acceptLicense –-enableStartTls
After that delete this comment and have the following contents in this Xml file:
<Configuration>
<ServerName>server machine name</ServerName>
<Domain>DC=example,DC=com</Domain>
<Port>1389</Port>
<User>cn=Directory Manager</User>
<Password>password</Password>
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
</Configuration>
-->
\ No newline at end of file
......@@ -52,7 +52,7 @@
Link="Common\DirectoryServices\LdapConfiguration.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="LDAP.Configuration.xml">
<Content Include="$(CommonTestPath)System\DirectoryServices\LDAP.Configuration.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
......
......@@ -59,6 +59,20 @@ public override void GetObjectData(SerializationInfo serializationInfo, Streamin
{
base.GetObjectData(serializationInfo, streamingContext);
}
public override string Message
{
get
{
string s = base.Message;
if (!string.IsNullOrEmpty(Name))
{
s += " " + Name;
}
return s;
}
}
}
[Serializable]
......
<!--
To enable the tests marked with [ConditionalFact(nameof(IsLdapConfigurationExist))], need to setup LDAP server and provide the needed server info here.
The easiest way to get LDAP server is to download and install OpenDJ https://backstage.forgerock.com/downloads/OpenDJ/Directory%20Services/5.0.0#browse.
Download "DS zip" file then extract it. you can set it up by running a command like that following:
/setup directory-server --sampleData 2 --rootUserDN "cn=Directory Manager" --rootUserPassword password --hostname localhost.localdomain --ldapPort 1389 --ldapsPort 1636 --httpPort 8080 --httpsPort 8443 --adminConnectorPort 4444 --baseDN dc=example,dc=com --acceptLicense –-enableStartTls
After that delete this comment and have the following contents in this Xml file:
<Configuration>
<ServerName>server machine name</ServerName>
<Domain>DC=example,DC=com</Domain>
<Port>1389</Port>
<User>cn=Directory Manager</User>
<Password>password</Password>
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
</Configuration>
-->
\ No newline at end of file
......@@ -23,7 +23,7 @@
Link="Common\DirectoryServices\LdapConfiguration.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="LDAP.Configuration.xml">
<Content Include="$(CommonTestPath)System\DirectoryServices\LDAP.Configuration.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
......
......@@ -31,11 +31,11 @@ public void TestComInterfaces()
IADs iads = (IADs) rootOU.NativeObject;
Assert.Equal("ou=dateRoot", iads.Name);
Assert.Equal("Class", iads.Class);
Assert.True(iads.ADsPath.IndexOf(LdapConfiguration.Configuration.ServerName, StringComparison.OrdinalIgnoreCase) >= 0);
Assert.Contains(LdapConfiguration.Configuration.ServerName, iads.ADsPath, StringComparison.OrdinalIgnoreCase);
IADsSecurityDescriptor iadsSD = (IADsSecurityDescriptor) de.Properties["ntSecurityDescriptor"].Value;
Assert.True(LdapConfiguration.Configuration.Domain.IndexOf(iadsSD.Owner.Split('\\')[0], StringComparison.OrdinalIgnoreCase) >= 0);
Assert.True(LdapConfiguration.Configuration.Domain.IndexOf(iadsSD.Group.Split('\\')[0], StringComparison.OrdinalIgnoreCase) >= 0);
Assert.Contains(iadsSD.Owner.Split('\\')[0], LdapConfiguration.Configuration.SearchDn, StringComparison.OrdinalIgnoreCase);
Assert.Contains(iadsSD.Group.Split('\\')[0], LdapConfiguration.Configuration.SearchDn, StringComparison.OrdinalIgnoreCase);
}
}
finally
......
......@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Collections;
using Xunit;
using Xunit.Sdk;
namespace System.DirectoryServices.Tests
{
......@@ -534,12 +535,12 @@ public void TestAttributesWithDifferentTypes()
private void CheckSpecificException(Action blockToExecute)
{
Exception exception = Record.Exception(blockToExecute);
Assert.NotNull(exception);
Assert.True(exception != null, "No exception was thrown");
if (IsActiveDirectoryServer)
Assert.IsType<DirectoryServicesCOMException>(exception);
else
Assert.IsType<COMException>(exception);
Type expected = IsActiveDirectoryServer ? typeof(DirectoryServicesCOMException) : typeof(COMException);
if (exception.GetType() != expected)
throw new XunitException(String.Format("Expected exception type '{0}' got '{1}' with message '{2}'", expected, exception.GetType(), exception.Message));
}
private DirectoryEntry CreateOU(DirectoryEntry de, string ou, string description)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册