11

SXA provides the ability to create new tenants on-the-fly. Ideally each tenant would have a unique set of roles such as tenant1\admin and tenant1\editor.

How do you configure roles and domains in Sitecore on-the-fly while also using the configuration patch for the abc domain? Is there a need for a domain provider that merges the behavior of both the file and config providers?

Requirements

Here are the following requirements:

  • Use Microsoft Active Directory for authentication
    • Users should login with their domain credentials (i.e. abc\michael.west)
  • Each tenant should have unique roles managed through Sitecore and not influence the groups found in AD.
    • The service account for the Sitecore App Pool only has read access to AD.
  • Only users in a specific group in AD have access to Sitecore.

Configuration

Here is what I have setup:

  1. Created a new AD group called GRP-Sitecore-Users
  2. Installed Sitecore AD module. Configuration sample below.
    • Added machine key
    • Added AD membership provider with a custom filter for the GRP-Sitecore-Users
    • Added AD role provider with a custom filter for the GRP-Sitecore-Users

Company.Authentication.config

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <domainManager>
      <patch:attribute name="defaultProvider">config</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" />
      </providers>
      <!-- Sample for use with config provider above -->
      <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 providerName="ad" storeFullNames="false" wildcard="*" domains="abc" />
      </membership>
      <roleManager>
        <provider providerName="ad" storeFullNames="false" wildcard="*" domains="abc" />
      </roleManager>
      <profile>
        <provider providerName="ad" storeFullNames="false" wildcard="*" domains="abc" />
      </profile>
    </switchingProviders>
  </sitecore>
</configuration>

Errors: Using the patch for domainManager I'm unable to use the role manager for creating roles under abc and unable to use the domain manager for creating new domains.

When adding a domain

The ConfigDomainProvider does not support adding domains programmatically. Please edit the config files instead.

When adding a role under the abc AD domain Error adding role to AD domain

Environment:

  • Sitecore 8.2 initial update 4
  • Active Directory Module 1.3

Update

This has been working fine for some time now. I've even added the sync of roles using Unicorn.

  • We faced similar multi-tenant issue last year December. However, the requirement that time was to use same email across multiple env like DEV/TEST/QA but wanted a separate OU per environment as the roles are managed from AD. Can't you use Sitecore Security Editor to restrict access to specific roles for each tenant? When the user logs in, based on their role in AD we can add additional roles dynamically. I found this dynamic roles blog upon searching adeneys.wordpress.com/2012/05/09/dynamic-roles-in-sitecore – phani Oct 28 '16 at 17:45
  • I'm interested in allowing domains to be configured through patch config and managed through Domain Manager screen. – Michael West Oct 28 '16 at 19:07
  • So I figured out how to combine the ConfigStore and Config domain providers. Will post the solution soon. – Michael West Oct 31 '16 at 0:39
13

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>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.