Using Secure Store Service in a Custom Claims Provider with SharePoint 2010
I noticed an unusual wrinkle recently when using Secure Store Service (SSS) in a custom claims provider I was working on. This is actually an interesting scenario because I was doing what many folks want to do - custom claims augmentation. I needed to connect to a remote data source so I could query for some additional information about each user and then use that to determine what claims to augment or not.
As a general guideline for using data sources in custom claims providers, it's important to remember that your custom claim provider assembly is going to be kept alive in memory by the SharePoint STS process. That makes it a lot easier to retrieve "information" - whether that's a dataset, a set of credentials, etc. - by storing it in a class level variable and then it is available for use until the next IISRESET. The big limitation here is that not all SharePoint farm resources may be available to you at the time your custom claim provider class is instantiated, and that's the moral of today's story.
In this particular case I wanted to retrieve data from the SSS in the constructor for my custom claims provider, and then I was going to do "some other stuff" with it; in my case I was creating a WindowsIdentity from a domain across a one-way trust so I could use it to create an impersonation context that had permissions to query the remote Active Directory. Where the issue occurred is that when I tried to do anything with my reference to the SSS in the constructor, it ALWAYS timed out. It didn't matter what method was called on the SSS, it just always failed after 60 seconds with a timeout error.
The fix was simply to move the code out of the constructor. The same exact code worked perfectly when invoked from my override of the FillClaimsForEntity method. It was really just luck and trial and error that I figured this out so it seemed like a good tip to share.
As long as we're down this path of this particular problem (logging in to a remote domain and impersonating) it's probably worth throwing out one other pattern that I got out of this, and one other gotcha.
As described above, because your assembly stays loaded in the STS process, you can "keep alive" your class level variables. Since I obviously didn't want to be repeatedly logging into the remote domain when I needed to query it, I created a class level variable for my WindowsIdentity. The pattern went something like this:
- See if I've retrieved the SSS credentials yet
- If not, execute the code that:
- Retrieves the credentials from SSS
- Uses the LogonUser API to logon to the remote domain using the credentials I got from the SSS
- Instantiate my WindowsIdentity variable so it had the credentials of the remote user
- If not, execute the code that:
- Check to see if my WindowsIdentity variable is null or not
- If not, execute the code that:
- Creates a new instance of a WindowsImpersonationContext from WindowsIdentity.Impersonate()
- Query the remote domain
- Call Undo on my WindowsImpersonationContext
- If not, execute the code that:
That pattern seems to work well and is about as much performance as I can wring out of it so far. Now here's the gotcha - you do NOT want to call Impersonate() on your WindowsIdentity instance and then NOT call Undo on the resulting WindowsImpersonationContext afterwards. If you do not undo the impersonation then in my experience the site will no longer render. Add your Undo call back and everything starts working again.
Comments
Anonymous
January 01, 2003
thanksAnonymous
January 01, 2003
The comment has been removedAnonymous
April 29, 2011
Steve, thank you for all your great posts on Claims Provider. I have a client that is implementing custom claims provider and want to augment the claims to STS from Novell LDAP. When I hard code the LDAP credentials, everything works great. But when I try to read the values from appSettings or from SSS, it throws an error. The following is the code I am using to retrieve from Web.Config ServerPath = ConfigurationManager.AppSettings["LDAPServer"].ToString(); Username = ConfigurationManager.AppSettings["LDAPUser"].ToString(); Password = ConfigurationManager.AppSettings["LDAPPwd"].ToString(); Also, I tried the following code for Secure Store service Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(delegate() { var serviceContext = SPServiceContext.Current; var secureStoreProvider = new SecureStoreProvider { Context = serviceContext }; var credentialMap = new Dictionary<string, string>(); using (var credentials = secureStoreProvider.GetCredentials(applicationID)) { var fields = secureStoreProvider.GetTargetApplicationFields(applicationID); for (var i = 0; i < fields.Count; i++) { var field = fields[i]; var credential = credentials[i]; var decryptedCredential = ToClrString(credential.Credential); credentialMap.Add(field.Name, decryptedCredential); } } ...................... ........................... }); Would you please let me know what I am doing wrong? if you have any code that is retrieving the appSettings or credentials in a FillClaimsForEntity method of a SPClaimProvider, would you please share Thank you once again for the awesome posts. It helped us a great deal.Anonymous
September 18, 2014
The comment has been removed