Applies to
-
Carousel 7.5.6+
-
FrontDoor 6.3.0.3+
(changes are in Trms.UserManagement.dll version 5.4.0.21)
Why do we need this document?
The way Carousel queries Active Directory changed considerably starting with these releases. This document is meant to help guide existing customers who want to upgrade, as well as new customers wanting to set up AD.
Why did we change how we do AD?
Customers required integrating with more complex AD infrastructures, with the main requirement being the need to support users from multiple domains inside the AD forest.
What did we change?
Previously we cycled through the connection strings, and queried each to find the groups and users we were looking for. This proved challenging when groups referenced users from different domains, which would’ve required us to chase the references, and ended up making the code more complex and harder to maintain.
Instead we opted to query the Global Catalog for our group and user info, and only rely on the connection strings when retrieving the aspnet membership user.
How does it work?
1- We query the Directory Services for the Carousel server’s AD Forest.
2- We query the AD Forest and get the first Global Catalog (GC) we find.
3- We query the GC for the TRMS_Admins and TRMS_Users groups, and extract their members' Distinguished Names (DN).
4- We query the GC for each user’s DN, which gives us an AD User object with partial information.
5- And finally we cycle through the membership.config’s membership providers to resolve each AD User into a full ASPNet user.
Some assumptions are made:
-
The Carousel server is on an AD domain, which is part of an AD forest.
-
The AD Forest has a Global Catalog.
Connection Strings
The way we interpret connection strings has changed.
Since we use the Global Catalog (GC) to find the TRMS_ groups we no longer need a connection string that points to the groups. We also don’t need a connection string to identify the groups' Domain since we’re querying the Forest Root in which the Carousel server resides.
The connection strings are still needed by the Membership Providers to retrieve users though. There needs to be a one to one mapping between the AspNet Membership Providers and the AD Connection Strings. Any domain controller on which a Carousel user can be found should have a connection string/membership provider pair.
For example:
connection strings:
<?xml version="1.0" encoding="utf-8"?>
<connectionStrings>
<add name="FrontDoorConnectionString" connectionString="data source=(local);Integrated Security=SSPI;initial catalog=FrontDoor50;" providerName="System.Data.SqlClient" />
<add name="CarouselConnectionString" connectionString="data source=(local);Integrated Security=SSPI;MultipleActiveResultSets=true;initial catalog=Carousel50;" providerName="System.Data.SqlClient" />
<add name="ADConnectionString1" connectionString="LDAP://test.com" />
<add name="ADConnectionString2" connectionString="LDAP://ad2.test.com" >
</connectionStrings>
matching membership config:
<membership defaultProvider="ADMembershipProvider1">
<providers>
<clear />
<add name="ADMembershipProvider1"
connectionStringName="ADConnectionString1"
applicationName="/FrontDoor"
connectionUsername="joe@test.com"
connectionPassword="12345678"
connectionProtection="Secure"
enableSearchMethods="true"
type="System.Web.Security.ActiveDirectoryMembershipProvider"/>
<add name="ADMembershipProvider2"
connectionStringName="ADConnectionString2"
applicationName="/FrontDoor"
connectionUsername="joe@test.com"
connectionPassword="12345678"
connectionProtection="Secure"
enableSearchMethods="true"
type="System.Web.Security.ActiveDirectoryMembershipProvider"/>
</providers>
</membership>
New configurable options
The following options can be set in the Carousel database’s Options table. Any changes to these options require an IIS restart.
Component |
Name |
Value |
---|---|---|
General |
ADCacheDurationInMinutes |
integer, if not specified we’ll default to 5 minutes |
General |
UserComponentLogging |
0 | 1 |
General |
ADGroup_Admins |
a string representing what would otherwise be the TRMS_Admins group |
General |
ADGroup_Users |
a string representing what would otherwise be the TRMS_Users group |
ADCacheDurationInMinutes
Carousel caches a list of all AD users who are members of the TRMS_Admins or TRMS_Users group. The list is rebuilt at regular intervals.
If the option is not in the database it will use the internal default value (keeps the old behavior).
By default, we will rebuild the list every 5 minutes. If you have many users, or if rebuilding the list is too costly, you can space it out by specifying a new value.
UserComponentLogging
Set to 1 to enable trace logs that can be viewed in DebugView.
If the option is not in the database it will default to False.
Restart IIS / w3wp.exe after changing this value. If you have DebugView opened when logging in after a restart of IIS it will output a trace of the detected logging state as being True or False.
ADGroup_Admins / ADGroup_Users
This allows you to remap the TRMS_Admins and TRMS_Users group to any custom group name.
If the option is not in the database it will use the default group names (keeps the old behavior).
Groups must be Universal Security Groups for the Group Scope.
New troubleshooting logs
A lot of logging has been added to the AD code path in the Trms.UserManagement.dll binary.
Set the UserComponentLogging to 1, restart IIS and open DebugView. Then attempt to login. You’ll see info on any AD queries made to resolve the users and groups.
General AD tips
-
The CarouselAPI, FrontDoor, and FrontDoorAPI web applications should run under the Carousel app pool
-
The Carousel app pool should run under an identity that has access to the AD domain.
-
The connection strings may specify the server name and domain controller. They should not specify any OUs or CNs (CN=Users has proven to be accepted, but unnecessary).
The following PowerShell script essentially executes the same queries as we do in c#. If it can find the user then we should be able to.
#
# searches for a user's DN using the GC
# if ADExplorer is able to find the user then this script should as well
#
$connectionString = "LDAP://test.com"
$dn = "CN=john,CN=Users,DC=ad2,DC=test,DC=com"
#
# the script runs under the logged-in user's creds, as does carousel (which needs IIS and service to be configured as such)
# we could use different creds here for troubleshooting purposes...
#
$forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$catalogs = $forest.FindAllGlobalCatalogs()
$gc = $catalogs[0]
$se = $gc.GetDirectorySearcher()
$se.Filter = "(&(&(objectClass=user)(!(objectClass=computer)))(distinguishedName=$dn))"
$se.PageSize = 2000
$se.PropertiesToLoad.AddRange((
"sAMAccountName",
"givenName",
"sn",
"userPrincipalName",
"userAccountControl",
"cn",
"distinguishedName",
"objectGuid",
"objectSid",
"pwdLastSet"))
$result = $se.FindOne()
if ($result -eq $null) {
Write-Host 'unsuccessful'
}
else {
Write-Host "found:" $result.Properties["sAMAccountName"][0]
Write-Host $result.Properties.Count / $se.PropertiesToLoad.Count "properties found"
Write-Host ------------
foreach ($item in $result.Properties.Keys) {
Write-Host $item : $result.Properties[$item]
}
}
Caching users for better performance
Carousel caches its list of users to increase performance. The user objects kept in that list are of the MembershipUser type. When looking up users in the cache we compare by UserNames. By default, the UserNames are of type UserPrincipalName (user@domain). If Carousel users log in using their SamAccountName, then the cache should be switched to use SamAccountNames instead. This can be done by adding the following line to each MembershipProvider in the membership.config file:
attributeMapUsername="sAMAccountName"
Forgoing this step will render the cache useless and incur a performance hit.
You cannot mix the two different username types. All users should use one or the other.
Debugging tip: open DebugView while Carousel is opened, and look for traces that indicate the user has been retrieved from the cache.
Things to be aware of
-
We’ll use the first Global Catalog enumerated by the Forest’s Root. We assume that the information we need is replicated across all catalogs and as such is safe to use only the first one we find.
-
We look at all TRMS_ groups we find. It's ok to have more than one of each group in the Forest, we’ll enumerate all users and ensure we don’t duplicate users.