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.
Impersonation
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
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
Careful
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.
What’s the solution?
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
Today I again faced an annoying SharePoint BCS deployment bug. I encountered it before in the beta but figured it would be solved in the release version so I forgot about it. Today it bit me in the rear again.
What's the issue?
As soon as you deploy a BCS solution from VS2010 and changed the model to much from the previous version you deployed, you get all sorts of errors. If you look closely to them, you'll notice they are caused by SharePoint trying to compile previous versions. If you however retract your solution and view Central Admin there are no models defined at all! Somehow these previous versions are stored somewhere we can't view or delete them!
The solution
Sometimes the solution can be very simple. Don't trust the UI, don't trust VS2010 but try the Object Model. After I ran the following code I could find a lot of left over models and entities.
using(var site = new SPSite("http://siteurl")){
var parentFarm = site.WebApplication.Farm;
var businessDataServices = parentFarm.Services.GetValue();
var context = SPServiceContext.GetContext(site);
var catalog = businessDataServices.GetAdministrationMetadataCatalog(context);
Console.WriteLine("Models:");
var models = catalog.GetModels("*");
foreach (var model in models) {
Console.WriteLine("\t{0}", model.DefaultDisplayName);
}
Console.WriteLine("\nEntities:");
var entities = catalog.GetEntities("*", "*", false);
foreach (var entity in entities) {
Console.WriteLine("\t{0}", entity.DefaultDisplayName);
}
}
Console.ReadLine();
After verrifying all models and entities were indeed left overs I adjusted the code a little bit to delete them.
using(var site = new SPSite("http://siteurl")){
var parentFarm = site.WebApplication.Farm;
var businessDataServices = parentFarm.Services.GetValue();
var context = SPServiceContext.GetContext(site);
var catalog = businessDataServices.GetAdministrationMetadataCatalog(context);
Console.WriteLine("Models:");
var models = catalog.GetModels("*");
foreach (var model in models) {
Console.WriteLine("\t{0}", model.DefaultDisplayName);
model.Delete();
}
Console.WriteLine("\nEntities:");
var entities = catalog.GetEntities("*", "*", false);
foreach (var entity in entities) {
Console.WriteLine("\t{0}", entity.DefaultDisplayName);
entity.Delete();
}
}
Console.ReadLine();
After running the code I could deploy my solution again and all worked fine!
Cheers,
Wes