We have a custom IdP/STS (Identity Provider/Security Token Service) used as a trusted provider in all Internet zones of our applications that fall under our umbrella of SSO enabled applications. Because we built this custom, we had to build a custom claims provider (CCP) to handle display issue in the people picker where you see the same user n amount of times where n is the number of claims exposed by the IdP/STS, http://msdn.microsoft.com/en-us/library/gg251994(v=office.14).aspx
This was built and then we discovered the CCP was breaking the farm (local only ;)) in the sense that we could no longer create site collections. Doing a little digging a la ilSPy, I decompiled the New-SPSite commandlet and discovered that internally SPUtility.ResolvePrincipal(...) was returning null. The exception in the commandlet thrown was "User Not Found".
I was a little confused as the custom claims provider was built only for the custom IdP/STS.
I discovered that all claim providers that are deployed to the farm are always called. The entire collection of them appears to run when EnsureUser is called and when SPUtility.ResolvePrincipal is called. There are probably other methods too that I am unaware of that call all of them as well. This made sense because as soon as I removed the CCP from the farm, everything worked again. I reactivated it to keep digging.
Our custom IdP/STS handles AD users in the Internet Zone. NTLM (claims-based) is used in the Default zone. When our CCP was hit, it would resolve the AD user, since it's one of the user bases in our IdP/STS but it would resolve the picker enitity with the correct claim prefix for our trusted provider. As soon as it came out of our custom claims provider, the SPUtility.ResolvePrincipal would resolve to null for a valid user in the default zone. I discovered when trying to run the New-SPSite commandlet. As I mentioned the error returned was "User not found". I found this confusing as the user was in the UserInfo table in the content database and would resolve properly when calling EnsureUser.
Searching Google was not resulting in much until I came across a post about "User is not found or is not unique" when saving something where a user was selected in a People Picker, like granting permissions or adding a user to a SharePoint group. I decided to go into our Default zone (NTLM). I tried to add my user to a SharePoint group. Boom, "User is not found or is not unique". I double checked the UserInfo table, only one user. Then I realized we're resolving AD users in our custom IdP/STS and the custom claim provider is hit all the time along with the other OOTB (Out of the box) claim providers. It seems that the fact our IdP/STS resolved the AD user caused a conflict with the OOTB claim provider that resolves the AD user in our Default Zone (NTLM).
So how to bypass our custom claims provider when it's not supposed to be used even if it's always called?
protected override void FillResolve(Uri context, string[] entityTypes, SPClaim resolveInput,
List<PickerEntity> resolved)
{
if (resolveInput.OriginalIssuer != null && resolveInput.OriginalIssuer != customClaimProviderOriginalIssuerName)
{
// Need to leave here. You cause issues with SPUtility.ResolvePrincipal with AD OOTB claim providers. It will complain about either a user not being found or
// a non-unique user.
return;
}
FillResolve(context, entityTypes, resolveInput.Value, resolved);
}
This is all the preamble to my question. OK, so I've handle my scenario now, then what is IsUsedByDefault = false do then? Apparently nothing.
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
ExecBaseFeatureActivated(properties);
var cpm = SPClaimProviderManager.Local;
foreach (var cp in cpm.ClaimProviders)
{
if (cp.ClaimProviderType == typeof(CustomClaim))
{
cp.IsUsedByDefault = false; // we don't want to use this claim provider by default.
cp.IsEnabled = true; // Needs to be enabled if we want to use it in the farm.
cpm.Update();
break;
}
}
}
As well, you can set the custom claim provider to be used in a specific zone, which I set as well, but it still seems to be called in all zones regardless of the zone I explicitly set it for.
### Functions
function Set-SPClaimProviderSettings {
param($claimProviderName, $isUsedByDefault = $false, $isEnabled = $true)
# Ensure that custom claim provider settings are correct.
$cpm = Get-SPClaimProviderManager
$customClaimProvider = $cpm.ClaimProviders | WHERE { $_.DisplayName -eq $claimProviderName }
$customClaimProvider.IsUsedByDefault = $isUsedByDefault
$customClaimProvider.IsEnabled = $isEnabled
$cpm.Update()
}
function Set-SPClaimProviderForWebApplicationZones
{
param($webApplicationUrl, $claimProviderName, [System.Collections.Generic.Dictionary[[Microsoft.SharePoint.Administration.SPUrlZone], Boolean]]$zonesConfiguration)
if ($zonesConfiguration -eq $null) {
throw { "Zones configuration cannot be null" }
}
$webApplication = Get-SPWebApplication -Identity $webApplicationUrl
foreach ($currentZoneConfiguration in $zonesConfiguration.GetEnumerator()) {
$zone = $currentZoneConfiguration.Key
$isClaimProviderEnabled = $currentZoneConfiguration.Value
$iisSettings = $webApplication.GetIisSettingsWithFallback($zone)
$hasRegisteredClaimProvider = $iisSettings.ClaimsProviders.Contains($claimProviderName)
if ($hasRegisteredClaimProvider -and !$isClaimProviderEnabled) {
$iisSettings.ClaimsProviders.Remove($claimProviderName)
}
else {
$iisSettings.ClaimsProviders.Add($claimProviderName)
}
$claimProvidersForZone = $iisSettings.ClaimsProviders
Set-SPWebApplication -Identity $webApplicationUrl -Zone $internetZone -AdditionalClaimProvider $claimProvidersForZone
}
}
# Enable the custom claim provider for the Internet zone only for all applications that fall under the SSO umbrella.
# TODO: Loop all applications in my deployment config XML.
Set-SPClaimProviderSettings -claimProviderName $CustomClaimsProviderName
$zonesConfiguration = New-Object 'System.Collections.Generic.Dictionary[[Microsoft.SharePoint.Administration.SPUrlZone], Boolean]'
$zonesConfiguration.Add([Microsoft.SharePoint.Administration.SPUrlZone]::Default, $false)
$zonesConfiguration.Add([Microsoft.SharePoint.Administration.SPUrlZone]::Internet, $true)
Set-SPClaimProviderForWebApplicationZones -webApplicationUrl http://myInternetZone.com -claimProviderName $CustomClaimsProviderName -zonesConfiguration $zonesConfiguration
I would think that enabling the custom claim provider for a specific zone would be all that is required, but this does not appear to be the case. From what I can tell tell, setting this does nothing.