After a nice weekend of trial-and-error with the help of DotPeek, here is what I've come up with. This should be a complete example for use after installing the Active Directory module (using version 1.3 with Sitecore 8.2).
With the following code you should now be able to patch domains required when using switchingProviders
as well as add domains through the Domain Manager when needing to add custom domains that align with the tenants.
From what I have seen in products like SXA, you can create roles such as the following:
- tenant1\Admin
- tenant1\FrontEnd
- tenant2\Admin
- tenant2\FrontEnd
DynamicConfigDomainProvider - Combines the behaviors of two config providers
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Xml;
using Sitecore.Collections;
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.IO;
using Sitecore.Security.Domains;
namespace Company.Feature.Authentication.Domains
{
public class DynamicConfigDomainProvider : ConfigStoreDomainProvider
{
private SafeDictionary<string, Domain> _domainsFromList;
private readonly object _lock = new object();
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
RefreshDomainsFromList(config);
}
private void RefreshDomainsFromList(NameValueCollection config)
{
lock (_lock)
{
var path = config.Get("domainList");
Assert.IsNotNullOrEmpty(path, "No valid domainList has been provided in the DynamicConfigDomainProvider configuration.");
var xpath = FileUtil.MakePath(path, "domain", '/');
var domains = new SafeDictionary<string, Domain>(StringComparer.OrdinalIgnoreCase);
foreach (XmlNode node in Factory.GetConfigNodes(xpath))
{
var domain = ParseDomain(node);
Assert.IsFalse(domains.ContainsKey(domain.Name),
$"Duplicate domain definitions in DynamicConfigDomainProvider configuration. Domain name: {0}", domain.Name);
domains.Add(domain.Name, domain);
}
Assert.IsTrue(domains.Count > 0,
$"No domains found in the list pointed to in the DynamicConfigDomainProvider configuration ({0}).", path);
_domainsFromList = domains;
}
}
private static Domain ParseDomain(XmlNode node)
{
var domain = Factory.CreateObject(node, true) as Domain;
if (domain == null)
{
throw new InvalidOperationException("Could not create domain from configuration node: " + node.OuterXml);
}
return domain;
}
public override IEnumerable<Domain> GetDomains()
{
foreach (var domain in base.GetDomains())
{
yield return domain;
}
foreach (var domain in _domainsFromList.Values)
{
yield return domain;
}
}
public override Domain GetDomain(string name)
{
Assert.ArgumentNotNull(name, "name");
lock (_lock)
{
return base.GetDomain(name) ?? _domainsFromList[name];
}
}
}
}
Company.Feature.Authentication.config - Patches the LDAP and domain settings
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<settings>
<setting name="Authentication.ClientSessionTimeout">
<patch:attribute name="value">180</patch:attribute>
</setting>
<setting name="LDAP.EnableSorting">
<patch:attribute name="value">true</patch:attribute>
</setting>
<setting name="LDAP.SortKey">
<patch:attribute name="value">samaccountname</patch:attribute>
</setting>
</settings>
<domainManager>
<patch:attribute name="defaultProvider">dynamicConfig</patch:attribute>
<providers>
<clear />
<add name="file" type="Sitecore.Security.Domains.ConfigStoreDomainProvider, Sitecore.Kernel" configStoreName="domains" />
<add name="config" type="Sitecore.SecurityModel.ConfigDomainProvider, Sitecore.Kernel" domainList="domainManager/domains" defaultDomain="sitecore" />
<add name="dynamicConfig" type="Company.Feature.Authentication.Domains.DynamicConfigDomainProvider, Company.Feature.Authentication" configStoreName="domains" domainList="domainManager/domains" />
</providers>
<domains>
<patch:delete />
</domains>
<domains>
<domain id="abc" type="Sitecore.Security.Domains.Domain, Sitecore.Kernel">
<param desc="name">$(id)</param>
<ensureAnonymousUser>false</ensureAnonymousUser>
</domain>
</domains>
</domainManager>
<switchingProviders>
<membership>
<provider patch:before="*" providerName="ad" storeFullNames="false" wildcard="*" domains="abc" />
</membership>
<roleManager>
<provider patch:before="*" providerName="ad" storeFullNames="false" wildcard="*" domains="abc" />
</roleManager>
<profile>
<provider patch:before="*" providerName="ad" storeFullNames="false" wildcard="*" domains="abc" />
</profile>
</switchingProviders>
</sitecore>
</configuration>
Web.Common.config - Transforms the web.config
Update : I noticed that the profile defaultProvider
did not have switcher configured which caused the "FullName" value to be empty when viewing users in the User Manager.
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.web>
<authentication>
<forms xdt:Transform="Replace" xdt:Locator="Match(name)" name=".ASPXAUTH" cookieless="UseCookies" timeout="180" />
</authentication>
<machineKey xdt:Transform="Remove" />
<machineKey xdt:Transform="InsertBefore(/configuration/system.web/membership)" validationKey="[INSERT_KEY]" validation="SHA1" decryption="AES" />
<membership>
<providers>
<add xdt:Transform="SetAttributes" xdt:Locator="Match(name)" name="sitecore" realProviderName="switcher" />
<add xdt:Transform="InsertIfMissing" xdt:Locator="Match(name)" name="ad" type="LightLDAP.SitecoreADMembershipProvider" connectionStringName="activedirectory" applicationName="sitecore" connectionProtection="Secure" attributeMapUsername="sAMAccountName" enableSearchMethods="true" customFilter="(memberOf=CN=GRP-Sitecore-Users,OU=Groups,OU=Company,DC=abc,DC=company,DC=corp)" />
</providers>
</membership>
<roleManager>
<providers>
<add xdt:Transform="SetAttributes" xdt:Locator="Match(name)" name="sitecore" realProviderName="switcher" />
<add xdt:Transform="InsertIfMissing" xdt:Locator="Match(name)" name="ad" type="LightLDAP.SitecoreADRoleProvider" connectionStringName="activedirectory" applicationName="sitecore" attributeMapUsername="sAMAccountName" customFilter="(memberOf=CN=GRP-Sitecore-Users,OU=Groups,OU=Company,DC=abc,DC=company,DC=corp)" />
</providers>
</roleManager>
<profile xdt:Transform="SetAttributes" defaultProvider="switcher">
<providers>
<add xdt:Transform="InsertIfMissing" xdt:Locator="Match(name)" name="ad" type="LightLDAP.SitecoreADProfileProvider" connectionStringName="activedirectory" applicationName="sitecore" sitecoreMapDomainName="abc" />
</providers>
</profile>
</system.web>
</configuration>