<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blog posts by Jukka Hyvärinen</title><link href="http://world.optimizely.com" /><updated>2016-01-08T01:00:00.0000000Z</updated><id>https://world.optimizely.com/blogs/jukka-hyvarinen/</id> <generator uri="http://world.optimizely.com" version="2.0">Optimizely World</generator> <entry><title>Things to be aware of while integrating Active Directory with Episerver</title><link href="http://dev.solita.fi/episerver/2016/01/08/active-directory-integration-with-episerver" /><id>&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;/h2&gt;

&lt;p&gt;Recently I was tasked with enabling our customer to log in to Episerver (version 8) using their Windows Active Directory credentials. 
During the implementation I encountered multiple issues that aren’t mentioned in the &lt;a href=&quot;/link/c1e47f8e7f2043699e8778b6dd6aad6f.aspx&quot;&gt;official documentation&lt;/a&gt;.
In the end, I ended up rewriting large parts of both &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/system.web.security.activedirectorymembershipprovider&quot;&gt;ActiveDirectoryMembershipProvider&lt;/a&gt; (built-in to .NET API) 
and &lt;a href=&quot;/link/55fe410f4f65482cab72fb59d47c7561.aspx?documentId=cms/8/49807A1D&quot;&gt;ActiveDirectoryRoleProvider&lt;/a&gt; (provided by Episerver).&lt;/p&gt;

&lt;p&gt;The official documentation already does good job at explaining the general steps needed for the integration so I won’t repeat those here. 
Instead, I’ll focus on the issues I encountered and how those could be solved. 
Some of these issues have been reported elsewhere by others already, but the solutions are spread around different blogs, 
so hopefully collecting those into one post will be useful to someone else in the same situation.&lt;/p&gt;

&lt;h2 id=&quot;firewall-ports&quot;&gt;Firewall ports&lt;/h2&gt;

&lt;p&gt;The official documentation says that ports 389 and 445 both need to be opened, but it’s not explained why.
Port 389 is the main port for LDAP communication so that one is to be expected, but 445 is the port for SMB file transfer, 
usually only needed in LAN environments, and opening it could possibly be a security issue, so IT departments are generally very reluctant about opening that port.&lt;/p&gt;

&lt;p&gt;However, as expected, for one reason or another, that port really needs to be open in firewall. 
We first tried with just the port 389, but this led to an error message “Workstation service is not started”. 
Opening port 445 in the firewall fixed that.
Just make sure that it’s limited to allowing traffic only from certain IPs.
The reason is still a bit uncertain, but I saw suggestions &lt;a href=&quot;http://www.windowsnetworking.com/kbase/WindowsTips/Windows2003/AdminTips/Security/Port445andtrustcreation.html&quot;&gt;that it has something to do with establishing trust between the computers&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;searching-users&quot;&gt;Searching users&lt;/h2&gt;

&lt;p&gt;We noticed that searching users by name or email doesn’t work at all. This is apparently a common issue.&lt;/p&gt;

&lt;p&gt;After some debugging it turned out that Episerver surrounds the keyword with SQL wildcards ‘%’, which obviously doesn’t work. 
In LDAP you need to use ‘*’ instead. The fix is to override the query methods in ActiveDirectoryMembershipProvider, 
replacing the ‘%’ in the query with ‘*’.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public override MembershipUserCollection FindUsersByName(
    string usernameToMatch, 
    int pageIndex, 
    int pageSize, 
    out int totalRecords)
{
    return base.FindUsersByName(usernameToMatch.Replace(&quot;%&quot;, &quot;*&quot;), pageIndex, pageSize, out totalRecords);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&quot;limits-for-the-number-of-groups&quot;&gt;Limits for the number of groups&lt;/h2&gt;

&lt;p&gt;LDAP (at least the Active Directory implementation) has a limit of 1000 entries per query. 
We hit that limit with groups, which led to Episerver not listing all groups in the AD, because ActiveDirectoryRoleProvider tries to load all groups 
and do the searching/pagination client side. 
I solved this by tweaking the LDAP query so that only specific groups are loaded, which also made it a lot faster since a smaller number of entries is returned.
Loading the full list of groups was painfully slow, even though the result is cached after the first query.&lt;/p&gt;

&lt;h2 id=&quot;caching-problems-with-multiple-active-directories&quot;&gt;Caching problems with multiple active directories&lt;/h2&gt;

&lt;p&gt;To improve performance, the AD role provider in Episerver caches results of AD queries, in a class called &lt;a href=&quot;/link/55fe410f4f65482cab72fb59d47c7561.aspx?documentId=cms/8/EEDB98B2&quot;&gt;AdsiDataFactory&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Looking in decompiler, the cache key looks like this:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;string cacheKey = &quot;EPiServer:DirectoryServiceFindAll:&quot; + filter + scope.ToString();
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Where &lt;em&gt;scope&lt;/em&gt; is an enum value. It uses the query itself as the cache key, which works fine if you have just one AD.
We had two ADs, which means two providers. Since the same queries are issued to both ADs, the results get cached only for the first one.&lt;/p&gt;

&lt;p&gt;My solution was to override GetAllRoles in ActiveDirectoryRoleProvider.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public override string[] GetAllRoles()
{
    // For example (sAMAccountName=externals)(sAMAccountName=others)
    var groupsToGet = string.Join(&quot;&quot;, (ConfigurationManager.AppSettings[&quot;AD.GroupsToGet&quot;] ?? string.Empty)
        .Split(new [] { &quot;,&quot; }, StringSplitOptions.RemoveEmptyEntries)
        .Select(g =&amp;gt; string.Format(&quot;({0}={1})&quot;, roleNameAttribute, g)));

    // AdsiDataFactory (DirectoryDataFactory) caches by query key. 
    // The extraCacheKey={1} part is needed to add provider name as part of the query to support multiple providers.
    var query = string.Format(&quot;(&amp;amp;(objectClass=group)(|{0}(extraCacheKey={1})))&quot;, groupsToGet, Name);

    // The rest is copied from the base class
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Where &lt;em&gt;groupsToGet&lt;/em&gt; is a list of AD group names to be loaded. We’re generating a custom LDAP query that filters by group names, and also appends the
provider name to the LDAP query so that it gets cached &lt;strong&gt;per provider&lt;/strong&gt;. 
This appended part doesn’t match to anything (it’s always false), but since it’s an OR operation, the comparison is effectively ignored.&lt;/p&gt;

&lt;h2 id=&quot;group-names-with-special-characters&quot;&gt;Group names with special characters&lt;/h2&gt;

&lt;p&gt;The last problem I encountered was about commas in AD group names, for example a group called “Cats, dogs and sheep” would not be identified.&lt;/p&gt;

&lt;p&gt;I pinpointed the problem to how the names are encoded. In ActiveDirectoryRoleProvider there’s this code:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;private void GetRolesForUserRecursive(DirectoryData entry, List&amp;lt;string&amp;gt; roles, HashSet&amp;lt;string&amp;gt; visitedDirectoryEntry)
{
    string[] propertyValue1;
    if (!entry.TryGetValue(&quot;memberOf&quot;, out propertyValue1))
        return;
    foreach (string distinguishedName in propertyValue1)
    {
        DirectoryData entry1 = this.DirectoryDataFactory.GetEntry(distinguishedName);
        /* removed unnecessary code */
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The problem is that &lt;em&gt;DirectoryDataFactory.GetEntry&lt;/em&gt; encodes the distinguishedName parameter, but the  distinguished name (DN)
returned by &lt;em&gt;entry.TryGetValue&lt;/em&gt; is already encoded, so it gets encoded twice! This means the DN of the “Cats, Dogs and Sheep” group becomes
&lt;em&gt;CN=Cats\\, dogs and sheep,CN=Users,DC=dev,DC=ad&lt;/em&gt; (notice the double backslash before the comma).&lt;/p&gt;

&lt;p&gt;You need to override this method as well and somehow make it not double encode the DNs.&lt;/p&gt;

&lt;h2 id=&quot;alternatives&quot;&gt;Alternatives&lt;/h2&gt;

&lt;p&gt;Others told me they have implemented the integration using Active Directory Federation Services (ADFS) with better luck, so I’d look into that if possible. 
In our case the customer wasn’t ready for ADFS quite yet, so we had to do the integration with LDAP. 
It’s also possible to integrate with the AD using &lt;a href=&quot;/link/55fe410f4f65482cab72fb59d47c7561.aspx?documentId=cms/8/C3725E6D&quot;&gt;WindowsMembershipProvider&lt;/a&gt;, 
if your webserver is a member of the AD, which wasn’t true in our case. I heard this is even more simple, but I have no first hand experience.&lt;/p&gt;
</id><updated>2016-01-08T01:00:00.0000000Z</updated><summary type="html">Blog post</summary></entry></feed>