I have been working on this custom workflow action which allows you to post data to another servicer. This can be used to send messages to a web service for example. One thing I really wanted to have in there is security. And with security I mean the Secure Store. It is however very difficult to use the Secure Store from inside a workflow action.
Although SharePoint actions are so called “impersonated” to the initiator of the user, the process really runs under its own account. Depending on how busy your server is, and the type of workflow is executed by the SPUserCodeHost, OWSTimer or W3WP process. And your code actually runs under the account of that process. The so called “impersonation” is for SharePoint actions only. This is accomplished by handing out the SPContext of the initiating step to the workflow.
Secure Store stores the credentials for users or groups and only while running under the user, or group account, can you get these credentials out of the secure store and that’s great of course, but that’s also where the trouble starts. We have no way of impersonating the actual initiator of the workflow and thus no way to get the credentials per user. One option would be to get a ticket in the Initialize method and use that ticket during the Execute method to retrieve the user credentials and yes this does work, most of the time. Because most of the time, the Initialize method will run inside the W3WP process which has a HttpContext which in turn has a WindowsIdentity we can use to impersonate. Unfortunately, if the server gets busy, the Initialize method might just as well run inside the OWSTimer process. Another problem with the ticketing system is that tickets are valid for a specified amount of time only. You should think in minutes instead of hours, but workflows well… they sometimes take a few days or weeks before they finally arrive at your workflow action.
private static ICredentials GetSecureStoreCredentials(string applicationId, SPServiceContext context) { string username = String.Empty; string password = String.Empty; ISecureStoreProvider provider = SecureStoreProviderFactory.Create(); if (provider == null) { throw new InvalidOperationException("Unable to get an ISecureStoreProvider"); } ISecureStoreServiceContext providerContext = provider as ISecureStoreServiceContext; providerContext.Context = context; using (var credentials = provider.GetCredentials(applicationId)) { foreach (var credential in credentials) { switch (credential.CredentialType) { case SecureStoreCredentialType.UserName: case SecureStoreCredentialType.WindowsUserName: username = credential.Credential.ToClrString(); break; case SecureStoreCredentialType.Password: case SecureStoreCredentialType.WindowsPassword: password = credential.Credential.ToClrString(); break; } } } return new NetworkCredential(username, password); }
Sample code for retrieving network credentials from secure store
One thing I would really like to stress out is that you really should not store the credentials in workflow variables during Initiate. Workflows can get stored anywhere (depending on the implementation of the WorkflowPersistenceService) and probably not secure. The other problem is that these credentials might not be valid anymore by the time your custom action executes.
There simply is no solution. The only thing you can do is create a so called Application Account (=Group account used by all users) in Secure Store and add the service accounts of the SPUserCodeHost, OWSTimer and W3WP processes in there. Problem is that every workflow action with the same Secure Store ApplicationId has to use the same credentials.
Cheers,
Wes