SAML Authentication and the People Picker
Out of the box SharePoint 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.
No comments:
Post a Comment