SAML Authentication and the People Picker
Out of the box SharePoint 2010 claims provider in SAML mode (SPTrustedClaimProvider) resolves any string a user enters in the People Picker. And this is a good thing, since there's no "standard" user store for this authentication method.
In a live system (that end-users should learn to love) this presents several usability problems, but fortunately they are relatively easy to overcome. You guessed it - a Custom Claims Provider should be used.
There are several sample implementations of Custom Claims Providers already available, so you are not alone.
Let's look at a real-life implementation of a localized Custom Claims Provider with a User Profile service application as a back-end user / claims store.
Before we start, there's a few prerequisites and good-to-haves:
- SharePoint Server 2010 license: any SKU that includes the User Profile service is suitable. You can still benefit from the Localization section if your SKU is SharePoint Foundation.
- Multilingual users: if you don't have any multilingual requirements, skip to the User Profile App as a User Store section
- SharePoint Web application in Claims mode: you'll get the most benefits if SAML authentication is used, and some benefits in Forms/Claims or Windows/Claims mode
- I will assume you have the User Profile Application running smoothly and your global audiences are compiled and contain members, based on a chosen criteria
- Please see the References and Credits section and familiarize yourself with the basics of Claims-based identity and access control, as well as the User Profile Application considerations
Localization
Thanks to good folks at Microsoft with the SharePoint Service Pack 1 it is now possible to fully localize a Custom Claims Provider.
To localize the DisplayName of a Custom Claims Provider, override FillDefaultLocalizedDisplayName, and for everything else there's your good old friend SPUtility.GetLocalizedString()
protected override void FillDefaultLocalizedDisplayName(CultureInfo culture, out string localizedName) {
localizedName = SPUtility.GetLocalizedString ("$Resources:DisplayNameText",
"ClaimsProviderResource", (uint)CultureInfo.CurrentUICulture.LCID);
}
Place the referenced resource files into a mapped SharePoint Layouts/Resources folder, for example:
ClaimsProviderResource.en-US.resx, ClaimsProviderResource.fr-FR.resx and ClaimsProviderResource.resx for an invariant culture
Now getting SPClaimProvider.DisplayName will result in a GetLocalizedDisplayName() call and the code in FillDefaultLocalizedDisplayName will run if you choose to override this method.
User Profile App as a User Store
The User Profile app can be synchronized with your company's User Directory of choice and augmented with other information from multiple sources. There's API to help retrieving information from this source, and almost no configuration required to make this work.
Besides the Custom Claims Provider, there's no other code to write, isn't it great?
Make sure the User Profile service app contains the objects you need and then use the UserProfileManager.Search() and UserProfileManager.ResolveProfile() methods to ensure scalability of the solution. These methods allow searching in several built-in User Profile attributes at once. The searchable profile properties are FirstName, LastName, PreferredName, UserName, Office, Title, Department, WorkEmail, SPS-SipAddress, and AccountName.
The search algorithm is quite robust and the beginning of every word is searched. This is very useful in a scenario when a profile property contains concatenated data: "John English | Jean Français".
private ProfileBase[] GetUserProfiles (string searchPattern, SPServiceContext ctxt, Boolean resolveMode) {
ProfileBase[] Users;
UserProfileManager profileManager = new UserProfileManager(ctxt);
if (resolveMode) { Users = profileManager.ResolveProfile(searchPattern); }
else { Users = profileManager.Search(searchPattern); }
returnUsers;
}
Please note that there are scenarios when keeping the ability to resolve any claim is still useful:
- The User Profile service app may not be available
- The user might be too new and the User Profile synchronization has not been yet completed
- The user may not even be in the user store that is accessible from a SharePoing application (yes, you may need to access more than one user store in the Claims Provider)
- The Custom Claims Provider is a core solution and there are many places where the resolution should work, including non claims-based apps like the Central Administration.
So consider the following logic - if there's no exact match of the identity, you may still offer users a choice to resolve any string that they enter.
Identity Claim Value Type
As specified in the SharePoint Security Token Service Web Service Protocol Specification, the unique user identifier (the Identity Claim) is in the format similar to i:05.t|trusted-sts|user_identifier
Character at position 4 in the above example is "5", which corresponds to the "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" URI.
Most of the examples that you will encounter use this custom claim type, and for a good reason - there are only a handful URIs that are not reserved and result in a ASCII character:
- Email address: "5", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" - use this claim type if you can
- UPN: "e", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" - also possible to use in some scenarios (Office 365)
- Role: "-", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" - may already be used, and can cause conflicts potentially
The majority of the other URIs or the URIs that are not listed in the spec will produce a UNICODE character at position 4, and this will cause problems with mapping User Profiles to records in the SharePoint Access Control Lists.
So once again, try to use an email address as an identifier if possible.
Audiences as a Flexible Claims Mechanism
Audiences are above the scope of a site collection and can be used for targeting in OOTB SharePoint. With SharePoint 2010 in claims mode, the audiences can also be used to make authorization decisions, just like user identities. This is a very flexible mechanism for creating compound or simple claims, and the possibilities on how to use the Audiences are almost endless.
SharePoint already includes most of the plumbing for using Audiences, so it's another Simply Working Solution™!
AudienceManager.Search() and AudienceManager.Resolve() methods should be used to ensure scalability of the solution, similar to the UserProfileManager methods.
Using the above methods requires elevating privileges and adding the Application pool identity to the Administrators list in the User Profile Service application.
Assign "Manage Audiences" permission to the account.
Please note with the SAML trusted provider (or Forms) SharePoint may still not allow to execute AudienceManager.Search() and AudienceManager.Resolve(). The workaround is to save the HttpContext, clear it and elevate privileges, then restore the HttpContext after the code finishes running.
Without the workaround using the methods results in an exception: System.UnauthorizedAccessException {"Access Denied")} ... Microsoft.SharePoint.SPSecurity.<>c__DisplayClass4.<RunWithElevatedPrivileges>b__2() at Microsoft.SharePoint.Utilities.SecurityContext.RunAsProcess(CodeToRunElevated secureCode)
//IB: null http context is required in SAML (and Forms) mode so the AudienceManager.Search could return results
HttpContext httpCtxt = HttpContext.Current; HttpContext.Current = null;
//IB: Make sure the application pool accounts for the claims applications are in the User Profile service application administrators for the ProfileManager and AudienceManager methods to work.
Permissions required: Manage Profiles, Manage Audiences
SPSecurity.RunWithElevatedPrivileges (delegate() {
using (SPSite ca = new SPSite (SPAdministrationWebApplication.Local.AlternateUrls[0].IncomingUrl)) {
SPServiceContext ctxt = SPServiceContext.GetContext(ca);
//IB: Users
profileMatches = GetUserProfiles(searchPattern, ctxt, false);
//IB: Audiences
AudienceManager mgr = new AudienceManager(ctxt);
audiences = mgr.Search(searchPattern); }
});
//IB: restoring the HTTP context
HttpContext.Current = httpCtxt;
//... Now profileMatches and audiences collections contain the items we need.
Claims Augmentation
You don't need to add the identity claim, as it is already present when a user authenticates. However, in order for the audiences claim or another custom claim to be useful, a set of claims need to be added after a user authenticates to SharePoint, and this is called "claims augmentation".
We will override FillClaimsForEntity() method and add Global Unique Identifiers for each audience the user belongs to by calling AudienceManager.GetUserAudienceIDs().
Please note the code will run in a context of a Security Token Service Application when an actual user authenticates, and in the context of the tested Web application when a call to SPClaimProviderOperations.ClaimsForEntity() is made (for example, a site owner uses "Check Permissions" function).
To debug the FillClaimsForEntity() code after deployment: reset IIS and attach your Visual Studio debugger to the SecurityTokenServiceApplicationPool w3wp process and the Web application w3wp process if you are debugging administration functionality.
To find out which w3wp process to attach to, run the commands below from the command prompt (on a Windows Server 2008 R2):
cd %systemroot%\system32\inetsrv
appcmd list wp
In the code sample below remember to provide the identityClaim in a suitable format (see the Identity Claim section). Please also check my post about the administration functionality to see a code sample: Windows/Claims and Trusted Provider/Claims configurations are considered.
using (SPSite ca = new SPSite (SPAdministrationWebApplication.Local.AlternateUrls[0].IncomingUrl)) {
SPServiceContext ctxt = SPServiceContext.GetContext(ca);
AudienceManager mgr = new AudienceManager(ctxt);
ArrayList userAudiences = mgr.GetUserAudienceIDs (identityClaim, false, ca.RootWeb);
foreach (AudienceNameID audienceNameId in userAudiences) { claims.Add (CreateClaim (AudienceClaimType, audienceNameId.AudienceID.ToString(), StringClaimType)); }
}
References and Credits
Implementing Claims-Based Authentication with SharePoint Server 2010 (whitepaper)
Claims-Based Identity and Access Control with a foreword by Steve Peschka: see SharePoint-related sections
Creating Custom Claims Providers in SharePoint 2010 by Ted Pattison and Scot Hillier on a sample Custom Claims Provider with Audience claim augmentation
Custom claims providers for People Picker (SharePoint Server 2010) on TechNet
Claims Based Identity & Access Control Guide with a sample Claims Provider implementation for SharePoint
If you must read only one* article on the User Profile setup and synchronization, read "Stuck on Starting": Common Issues with SharePoint Server 2010 User Profile Synchronization by Spencer Harbar! *Well, you ought to follow the links.
Special thanks to John Holliday and Spencer Harbar for validating the scalability of the solution.
Conclusion
Hopefully by this time you've got enough information to implement your own production-quality Custom Claims Provider.
Now move on to Claims Administration article to get an end-to-end working solution.
Please contact me with questions and suggestions on how to improve the articles (it's work in progress) or leave a comment.