Wesley Bakker

Interesting things I encounter doing my job...

Sponsors

News

Wesley Bakker
motion10
Rivium Quadrant 151
2909 LC Capelle aan den IJssel
Region of Rotterdam
The Netherlands
Phone: +31 10 2351035

(feel free to chat with me)
 

Add to Technorati Favorites

October 2010 - Posts

WorkflowAction and the Secure Store

CallWebServiceWorkFlowActionI 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

SharePoint 2010 BCS model deploy errors

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

Posted: Oct 20 2010, 04:40 PM by webbes | with 3 comment(s)
Filed under:
More Posts