Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

First DirectoryServcies tests which runs against real server #24316

Merged
merged 4 commits into from
Sep 29, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions src/Common/tests/System/DirectoryServices/LdapConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.IO;
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)
{
ServerName = serverName;
Domain = domain;
UserName = userName;
Password = password;
Port = port;
AuthenticationTypes = at;
}

private static LdapConfiguration s_ldapConfiguration = GetConfiguration("LDAP.Configuration.xml");

internal static LdapConfiguration Configuration => s_ldapConfiguration;

internal string ServerName { get; set; }
internal string UserName { get; set; }
internal string Password { get; set; }
internal string Port { get; set; }
internal string Domain { get; set; }
internal AuthenticationTypes AuthenticationTypes { get; set; }
internal string LdapPath => String.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/{Domain}" : $"LDAP://{ServerName}:{Port}/{Domain}";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This format is very specific to the System.DirectoryServices* assembly but doesn't apply to System.DirectoryServices.Protocols We may want to reuse this config class for all the S.DS.* assemblies to maybe this assembly specific logic should be moved to the assembly specific tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding tests to System.DirectoryServices.Protocols, we can look at what configuration is used there and then can update the configuration class to support it. or to use another specific configuration class.


internal string GetLdapPath(string prefix) // like "ou=something"
{
return String.IsNullOrEmpty(Port) ? $"LDAP://{ServerName}/{prefix},{Domain}" : $"LDAP://{ServerName}:{Port}/{prefix},{Domain}";
}

internal static LdapConfiguration GetConfiguration(string configFile)
{
if (!File.Exists(configFile))
return null;

LdapConfiguration ldapConfig = null;
try
{
string serverName = "";
string domain = "";
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;

child = config.Element("Domain");
if (child != null)
domain = child.Value;

child = config.Element("Port");
if (child != null)
port = child.Value;

child = config.Element("User");
if (child != null)
user = child.Value;

child = config.Element("Password");
if (child != null)
password = child.Value;

child = config.Element("AuthenticationTypes");
if (child != null)
{
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;
}
}

ldapConfig = new LdapConfiguration(serverName, domain, user, password, port, at);
}
}
catch
{
// Couldn't read the configurations, usually we'll skip the tests which depend on that
}
return ldapConfig;
}
}
}
19 changes: 19 additions & 0 deletions src/System.DirectoryServices/tests/LDAP.Configuration.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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>
-->
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<Compile Include="System\DirectoryServices\ActiveDirectorySecurityTests.cs" />
<Compile Include="System\DirectoryServices\ActiveDirectory\DomainControllerTests.cs" />
<Compile Include="System\DirectoryServices\DirectoryEntryTests.cs" />
<Compile Include="System\DirectoryServices\DirectoryServicesTests.cs" />
<Compile Include="System\DirectoryServices\DirectorySynchronizationTests.cs" />
<Compile Include="System\DirectoryServices\DirectoryVirtualListViewContextTests.cs" />
<Compile Include="System\DirectoryServices\DirectoryVirtualListViewTests.cs" />
Expand All @@ -18,6 +19,15 @@
<Compile Include="System\DirectoryServices\ActiveDirectory\ActiveDirectoryInterSiteTransportTests.cs" />
<Compile Include="System\DirectoryServices\ActiveDirectory\DirectoryContextTests.cs" />
<Compile Include="System\DirectoryServices\ActiveDirectory\ForestTests.cs" />

<Compile Include="$(CommonTestPath)\System\DirectoryServices\LdapConfiguration.cs">
<Link>Common\DirectoryServices\LdapConfiguration.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="LDAP.Configuration.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ public void FindByTransportType_ForestNoDomainAssociatedWithName_ThrowsActiveDir
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "Not approved COM object for app")]
public void FindByTransportType_ForestNoDomainAssociatedWithName_ThrowsActiveDirectoryOperationException_NoUap()
{
var context = new DirectoryContext(DirectoryContextType.Forest, "\0");
AssertExtensions.Throws<ArgumentException>("context", () => ActiveDirectoryInterSiteTransport.FindByTransportType(context, ActiveDirectoryTransportType.Rpc));
// Domain joined machines will not throw on the ActiveDirectoryInterSiteTransport.FindByTransportType call.
if (Environment.MachineName.Equals(Environment.UserDomainName, StringComparison.OrdinalIgnoreCase))
{
var context = new DirectoryContext(DirectoryContextType.Forest, "\0");
AssertExtensions.Throws<ArgumentException>("context", () => ActiveDirectoryInterSiteTransport.FindByTransportType(context, ActiveDirectoryTransportType.Rpc));
}
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,13 @@ public void FindOne_InvalidName_ThrowsException(string name, Type exceptionType)
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "Not approved COM object for app")]
public void FindAll_NoSuchName_ReturnsEmpty()
{
var context = new DirectoryContext(DirectoryContextType.Domain, "\0");
Assert.Empty(DomainController.FindAll(context));
Assert.Empty(DomainController.FindAll(context, "siteName"));
// Domain joined machines can have entries in the DomainController.
if (Environment.MachineName.Equals(Environment.UserDomainName, StringComparison.OrdinalIgnoreCase))
{
var context = new DirectoryContext(DirectoryContextType.Domain, "\0");
Assert.Empty(DomainController.FindAll(context));
Assert.Empty(DomainController.FindAll(context, "siteName"));
}
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Collections;
using Xunit;

namespace System.DirectoryServices.Tests
{
public class DirectoryServicesTests
{
internal static bool IsLdapConfigurationExist => LdapConfiguration.Configuration != null;

[ConditionalFact(nameof(IsLdapConfigurationExist))]
public void TestOU() // adding and removing organization unit
{
using (DirectoryEntry de = new DirectoryEntry(LdapConfiguration.Configuration.LdapPath,
LdapConfiguration.Configuration.UserName,
LdapConfiguration.Configuration.Password,
LdapConfiguration.Configuration.AuthenticationTypes))
{
string ouName = "NetCoreDevs";
CreateOU(de, ouName, ".Net Core Developers Unit");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should try and remove any pre-exisiting OUs with the target name first to take care of tests which leave the environment dirty

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll fix that

try
{
SearchOUByName(de, ouName);
}
finally
{
// Clean up the added ou
DeleteOU(de, ouName);
}
}
}

[ConditionalFact(nameof(IsLdapConfigurationExist))]
public void TestCN() // adding and removing organization unit
{
using (DirectoryEntry de = new DirectoryEntry(LdapConfiguration.Configuration.LdapPath,
LdapConfiguration.Configuration.UserName,
LdapConfiguration.Configuration.Password,
LdapConfiguration.Configuration.AuthenticationTypes))
{
DirectoryEntry rootOU = CreateOU(de, "CoreFxRootOU", "CoreFx Root OU");
try
{
DirectoryEntry child1OU = CreateOU(rootOU, "CoreFxChild1OU", "CoreFx Child OU 1");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need using statements

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll fix that

DirectoryEntry child2OU = CreateOU(rootOU, "CoreFxChild2OU", "CoreFx Child OU 2");

CreateCN(child1OU, "user.ou1.1", "User 1 is in CoreFx ou 1", "1 111 111 11111");
CreateCN(child1OU, "user.ou1.2", "User 2 is in CoreFx ou 1", "1 222 222 22222");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose we'll add some test logging in the future so it's clear which of these sub steps failed when the test fails?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XUnit will catch any thrown exception and have it in the logs.

CreateCN(child2OU, "user.ou2.1", "User 1 is in CoreFx ou 2", "1 333 333 3333");
CreateCN(child2OU, "user.ou2.2", "User 2 is in CoreFx ou 2", "1 333 333 3333");

// now let's search for the added data:
SearchOUByName(rootOU, "CoreFxChild1OU");
SearchOUByName(rootOU, "CoreFxChild2OU");

SearchCNByName(child1OU, "user.ou1.1");
SearchCNByName(child1OU, "user.ou1.2");

SearchCNByName(child2OU, "user.ou2.1");
SearchCNByName(child2OU, "user.ou2.2");
}
finally
{
// rootOU.DeleteTree(); doesn't work as getting "A protocol error occurred. (Exception from HRESULT: 0x80072021)"
DeleteOU(de, "CoreFxRootOU");
}
}
}

private DirectoryEntry CreateOU(DirectoryEntry de, string ou, string description)
{
DirectoryEntry ouCoreDevs = de.Children.Add($"ou={ou}","Class");
ouCoreDevs.Properties["objectClass"].Value = "organizationalUnit"; // has to be organizationalUnit
ouCoreDevs.Properties["description"].Value = description;
ouCoreDevs.Properties["ou"].Value = ou;
ouCoreDevs.CommitChanges();
return ouCoreDevs;
}

private DirectoryEntry CreateCN(DirectoryEntry ouEntry, string cn, string description, string phone)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CN isn't really a "type" it stands for common name. I would change this name to match the object type that is actually getting created.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll fix that

{
DirectoryEntry cnEntry = ouEntry.Children.Add($"cn={cn}","Class");
cnEntry.Properties["objectClass"].Value = "organizationalRole";
cnEntry.Properties["cn"].Value = cn;
cnEntry.Properties["description"].Value = description;
cnEntry.Properties["ou"].Value = ouEntry.Name;
cnEntry.Properties["telephoneNumber"].Value = phone;
cnEntry.CommitChanges();
return cnEntry;
}

private void DeleteOU(DirectoryEntry parentDe, string ou)
{
try
{
// We didn't use DirectoryEntry.DeleteTree as it fails on OpenDJ with "A protocol error occurred. (Exception from HRESULT: 0x80072021)"
// Also on AD servers DirectoryEntry.Children.Remove(de) will fail if the de is not a leaf entry. so we had to do it recursively.
DirectoryEntry de = parentDe.Children.Find($"ou={ou}");
DeleteDirectoryEntry(parentDe, de);
}
catch
{
// ignore the error if the test failed early and couldn't create the OU we are trying to delete
}
}

private void DeleteDirectoryEntry(DirectoryEntry parent, DirectoryEntry de)
{
foreach (DirectoryEntry child in de.Children)
{
DeleteDirectoryEntry(de, child);
}

parent.Children.Remove(de);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad formatting

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll fix that

parent.CommitChanges();
}

private void SearchOUByName(DirectoryEntry de, string ouName)
{
using (DirectorySearcher ds = new DirectorySearcher(de))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set ds.ClientTimeout to some reasonable value. I think the default is infinite wait.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll fix that. I'll use 2 minutes timeout which I think can be reasonable.

{
ds.Filter = $"(&(objectClass=organizationalUnit)(ou={ouName}))";
ds.PropertiesToLoad.Add("ou");
SearchResult sr = ds.FindOne();
Assert.Equal(ouName, sr.Properties["ou"][0]);
}
}

private void SearchCNByName(DirectoryEntry de, string cnName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same feedback on naming

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll fix that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all of these test* methods, should we add a finally block and then assert if any exception is thrown? That way you know which action failed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed. XUnit is catching the thrown exception and fail the test with the exception logs. we should be fine here.

{
using (DirectorySearcher ds = new DirectorySearcher(de))
{
ds.Filter = $"(&(objectClass=organizationalRole)(cn={cnName}))";
ds.PropertiesToLoad.Add("cn");
SearchResult sr = ds.FindOne();
Assert.Equal(cnName, sr.Properties["cn"][0]);
}
}

}
}