7

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.

1
  • Did you ever figure this out, setting on different zones. My code is almost identical to yours and has no effect. I run Peschka's tool against the zone after my code sets it and his tool says it is not there.
    – user19677
    Sep 20, 2013 at 19:45

1 Answer 1

1

Did you take a look at Steve Peschka's post of the subject?

I personally didn't try to custom code this but relied on his Custom Claim Activation tool, which has worked great so far.

By the way, I have the same "User not found" issue you have with a custom claim provider that resolves the same user as the default "Forms Auth" claim provider. I still haven't found a solution to that (the idea is to override the way the people picker displays results from an FBA datasource).

Hope this helps anyway.

1
  • yes, I read this post and used the tool, but regardless of which zone I set my custom claim provider to, it is always hit in any zone. And as you see in my code above, IsUsedByDefault is set to false. I fixed my issue by checking the issuer in the fill resolve. My question was why does my claim provider get called in any zone even if I only have it enabled in a specific zone. Nov 30, 2012 at 21:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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