diff --git a/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml b/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml index 24e41ecd4e7ea5..3580fab4ecfe6f 100644 --- a/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml +++ b/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml @@ -94,6 +94,7 @@ Note: cn=admin,dc=example,dc=com password ServerBind,None + True localhost @@ -102,6 +103,7 @@ Note: cn=admin,dc=example,dc=com password ServerBind,None + False danmose-ldap.danmose-domain.com @@ -110,6 +112,7 @@ Note: danmose-domain\Administrator %TESTPASSWORD% ServerBind,None + True ldap.local @@ -119,6 +122,7 @@ Note: password ServerBind,None true + False \ No newline at end of file diff --git a/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs b/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs index a9156b8efd5bbe..beed82aabcfa62 100644 --- a/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs +++ b/src/libraries/Common/tests/System/DirectoryServices/LdapConfiguration.cs @@ -10,7 +10,7 @@ namespace System.DirectoryServices.Tests { internal class LdapConfiguration { - private LdapConfiguration(string serverName, string searchDn, string userName, string password, string port, AuthenticationTypes at, bool useTls) + private LdapConfiguration(string serverName, string searchDn, string userName, string password, string port, AuthenticationTypes at, bool useTls, bool supportsServerSideSort) { ServerName = serverName; SearchDn = searchDn; @@ -19,6 +19,7 @@ private LdapConfiguration(string serverName, string searchDn, string userName, s Port = port; AuthenticationTypes = at; UseTls = useTls; + SupportsServerSideSort = supportsServerSideSort; } private static LdapConfiguration s_ldapConfiguration = GetConfiguration("LDAP.Configuration.xml"); @@ -32,6 +33,7 @@ private LdapConfiguration(string serverName, string searchDn, string userName, s internal string SearchDn { get; set; } internal AuthenticationTypes AuthenticationTypes { get; set; } internal bool UseTls { get; set; } + internal bool SupportsServerSideSort { get; set; } 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 @@ -107,6 +109,7 @@ internal static LdapConfiguration GetConfiguration(string configFile) string password = ""; AuthenticationTypes at = AuthenticationTypes.None; bool useTls = false; + bool supportsServerSideSort = false; XElement child = connection.Element("ServerName"); if (child != null) @@ -141,6 +144,12 @@ internal static LdapConfiguration GetConfiguration(string configFile) useTls = bool.Parse(child.Value); } + child = connection.Element("SupportsServerSideSort"); + if (child != null) + { + supportsServerSideSort = bool.Parse(child.Value); + } + child = connection.Element("AuthenticationTypes"); if (child != null) { @@ -170,7 +179,7 @@ internal static LdapConfiguration GetConfiguration(string configFile) at |= AuthenticationTypes.Signing; } - ldapConfig = new LdapConfiguration(serverName, searchDn, user, password, port, at, useTls); + ldapConfig = new LdapConfiguration(serverName, searchDn, user, password, port, at, useTls, supportsServerSideSort); } } catch (Exception ex) diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj b/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj index 43c120b0a74a09..33bfe13efee4e1 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj +++ b/src/libraries/System.DirectoryServices.Protocols/src/System.DirectoryServices.Protocols.csproj @@ -42,6 +42,7 @@ + Common\DisableRuntimeMarshalling.cs @@ -58,6 +59,7 @@ + @@ -76,6 +78,7 @@ + diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Linux.cs new file mode 100644 index 00000000000000..6119ae0956500f --- /dev/null +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Linux.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.ComponentModel; +using System.Diagnostics; +using System.Text; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Runtime.Versioning; + +namespace System.DirectoryServices.Protocols +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + internal partial struct SortKeyInterop + { + } +} diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Windows.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Windows.cs new file mode 100644 index 00000000000000..42ada55602cc65 --- /dev/null +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.Windows.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.ComponentModel; +using System.Diagnostics; +using System.Text; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Runtime.Versioning; + +namespace System.DirectoryServices.Protocols +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal partial struct SortKeyInterop + { + } +} diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.cs new file mode 100644 index 00000000000000..29ceb3a005522b --- /dev/null +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SortKeyInterop.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.ComponentModel; +using System.Diagnostics; +using System.Text; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Runtime.Versioning; + +namespace System.DirectoryServices.Protocols +{ + // Declared as partial in order to be able to set the different StructLayout + // attributes in the Windows and Linux specific files. + // This is a layout-controlled struct, do not alter property ordering. + internal partial struct SortKeyInterop + { + public SortKeyInterop(SortKey sortKey) + { + if (sortKey == null) + throw new ArgumentNullException(nameof(sortKey)); + + AttributeName = sortKey.AttributeName; + MatchingRule = sortKey.MatchingRule; + ReverseOrder = sortKey.ReverseOrder; + } + + internal string AttributeName { get; set; } + + internal string MatchingRule { get; set; } + + internal bool ReverseOrder { get; set; } + } +} diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/DirectoryControl.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/DirectoryControl.cs index bdef26646fedb6..6592f09945136d 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/DirectoryControl.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/common/DirectoryControl.cs @@ -703,9 +703,15 @@ public SortKey[] SortKeys public override byte[] GetValue() { + SortKeyInterop[] nativeSortKeys = new SortKeyInterop[_keys.Length]; + for (int i = 0; i < _keys.Length; ++i) + { + nativeSortKeys[i] = new SortKeyInterop(_keys[i]); + } + IntPtr control = IntPtr.Zero; - int structSize = Marshal.SizeOf(typeof(SortKey)); - int keyCount = _keys.Length; + int structSize = Marshal.SizeOf(typeof(SortKeyInterop)); + int keyCount = nativeSortKeys.Length; IntPtr memHandle = Utility.AllocHGlobalIntPtrArray(keyCount + 1); try @@ -716,7 +722,7 @@ public override byte[] GetValue() for (i = 0; i < keyCount; i++) { sortPtr = Marshal.AllocHGlobal(structSize); - Marshal.StructureToPtr(_keys[i], sortPtr, false); + Marshal.StructureToPtr(nativeSortKeys[i], sortPtr, false); tempPtr = (IntPtr)((long)memHandle + IntPtr.Size * i); Marshal.WriteIntPtr(tempPtr, sortPtr); } diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs index f13a864d000187..5e2356366ddc83 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs @@ -15,6 +15,8 @@ public partial class DirectoryServicesProtocolsTests internal static bool IsLdapConfigurationExist => LdapConfiguration.Configuration != null; internal static bool IsActiveDirectoryServer => IsLdapConfigurationExist && LdapConfiguration.Configuration.IsActiveDirectoryServer; + internal static bool IsServerSideSortSupported => IsLdapConfigurationExist && LdapConfiguration.Configuration.SupportsServerSideSort; + [ConditionalFact(nameof(IsLdapConfigurationExist))] public void TestInvalidFilter() { @@ -533,6 +535,58 @@ public void TestPageRequests() } } + [ConditionalFact(nameof(IsServerSideSortSupported))] + public void TestSortedSearch() + { + using (LdapConnection connection = GetConnection()) + { + string ouName = "ProtocolsGroup10"; + string dn = "ou=" + ouName; + + try + { + for (int i=0; i<10; i++) + { + DeleteEntry(connection, "ou=ProtocolsSubGroup10." + i + "," + dn); + } + DeleteEntry(connection, dn); + + AddOrganizationalUnit(connection, dn); + SearchResultEntry sre = SearchOrganizationalUnit(connection, LdapConfiguration.Configuration.SearchDn, ouName); + Assert.NotNull(sre); + + for (int i=0; i<10; i++) + { + AddOrganizationalUnit(connection, "ou=ProtocolsSubGroup10." + i + "," + dn); + } + + string filter = "(objectClass=*)"; + SearchRequest searchRequest = new SearchRequest( + dn + "," + LdapConfiguration.Configuration.SearchDn, + filter, + SearchScope.Subtree, + null); + + var sortRequestControl = new SortRequestControl("ou", true); + searchRequest.Controls.Add(sortRequestControl); + + SearchResponse searchResponse = (SearchResponse) connection.SendRequest(searchRequest); + Assert.Equal(1, searchResponse.Controls.Length); + Assert.True(searchResponse.Controls[0] is SortResponseControl); + Assert.True(searchResponse.Entries.Count > 0); + Assert.Equal("ou=ProtocolsSubGroup10.9," + dn + "," + LdapConfiguration.Configuration.SearchDn, searchResponse.Entries[0].DistinguishedName); + } + finally + { + for (int i=0; i<20; i++) + { + DeleteEntry(connection, "ou=ProtocolsSubGroup10." + i + "," + dn); + } + DeleteEntry(connection, dn); + } + } + } + private void DeleteAttribute(LdapConnection connection, string entryDn, string attributeName) { string dn = entryDn + "," + LdapConfiguration.Configuration.SearchDn; diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs index 26bc2ca73f17d3..480b537bab85b6 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/SortRequestControlTests.cs @@ -32,11 +32,15 @@ public void Ctor_SortKeys(bool critical) control.IsCritical = critical; var expected = (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) ? + // WLDAP formatted ASN.1 new byte[] { 48, 132, 0, 0, 0, 43, 48, 132, 0, 0, 0, 17, 4, 5,110, 97, 109, 101, 49, 128, 5, 114, 117, 108, 101, 49, 129, 1, 255, 48, 132, 0, 0, 0, 14, 4, 5, 110, 97, 109, 101, - 50, 128, 5, 114, 117, 108, 101, 50} : - new byte[] { 48, 19, 48, 9, 4, 1, 110, 128, 1, 114, 129, 1, 255, 48, 6, 4, 1, 110, 128, 1, 114 }; + 50, 128, 5, 114, 117, 108, 101, 50 } : + // OpenLdap formatted ASN.1 + new byte[] { 48, 35, 48, 17, 4, 5, 110, 97, 109, 101, 49, 128, 5, + 114, 117, 108, 101, 49, 129, 1, 255, 48, 14, 4, 5, 110, 97, 109, + 101, 50, 128, 5, 114, 117, 108, 101, 50 }; Assert.Equal(expected, control.GetValue()); }