Today we are going to look at a very advanced topic, namely how to use protocol transition with constrained delegation in a K2 process. Wow, that was a mouth full already, so let's break it down and make it as simple as possible before I lose you all in the opening paragraph. The questions from that first sentence are:
- What is protocol transition and why should I care?
- What is constrained delegation?
- Why would I want to use all of this in a K2 process?
- This sounds so complicated - can it bring about world piece?
PS, this is quite a marathon post so before we begin, get a good cup of coffee and make your self comfy. Promise it's worth it if you are into hardcore K2!
What is Protocol Transition?
Very short and sweet this allows certain servers that have been correctly configured to access a certain service using credentials for a domain user without knowing that users password. Now I know, I know the minute you read the "without a password" part everyone will freak out and think this is not secure so I will never use it. Well, fear not dear ninjas because this is actually quite secure because of all the configuration necessary and I know that this is used in some of the most security conscience organizations in the world. I won't go into all the security implications, the advantages and disadvantages in this post since that is not really my objective, but I will give you home work so that you can go read up about it your self here:
What is Constrained Delegation?
In Windows 2000 computers could be trusted for delegation to any servers or services, but this obviously represents a security risk, so in Win2003 this delegation is constrained to only certain services on specified servers or user accounts. Again, this is quite a long story so here is your homework for further reading:
Ok I see what it is, but what has this got to do with K2?
There are many valid scenarios where protocol transition with constrained delegation can be real handy in the K2 world. Imagine the following scenario.
You have a workflow process where a user receives a client event to fill in a form or grant some approval. After he completes his step the workflow process has to call a web service or a smart object or a COM object or SharePoint or other DMS or anything that updates the back end system and it has to do so with the credentials of the user in the previous step. Now I know some of you will say that this should be done directly from the UI via a smart object or web service, and yes in some scenarios that will be the suggested way, but let's imagine there is some business reason why the next call has to come from the workflow (besides, otherwise there is no reason for this post! ;-) ) A valid example could be if you created a custom K2 event template that interacts with your back end systems or you have a custom SharePoint or DMS event template where the action has to be executed in the context of the previous user.
This is awesome, but can it bring about world peace?
No. Sorry.
Okey, so now we know more or less what it is, and we know that we want to do this, so next question is HOW?! Well, let me show you.
Step 1
Let's build a nice little process that will demonstrate this in principle.
The process I used looks as follows. The Approval Activity will contain a client event where someone does the approval. In the server event after the client event we record the destination user's username in a data field and we encode his FQN (Fully Qualified Name : domain\username) username into a UPN (User Principle Name: username@domain.tld).

BTW, I defined the destination rule to use a data field so that I can dynamically change the destination user, and then I also had to go into the Advanced Settings for the destination rule to "Plan per Destination - All at Once"

So the Data fields I'm using are as follows:
- ApprovalUser - will contain the UPN of the approval user. This field will be updated by the Set Datafield event.
- DestUser - the destination user for the approval event as defined above.
- ImpersonatedUser - This will contain the result from our web service call at the end of the process and will be used to prove that the process works.
The C# code in the Set Datafield server event looks like this:
public void Main(Project_e3303dda4c5840e8b2e2a3796d32c9af.EventItemContext_1c17873688724ecd966f2d3c7eebe67f K2)
{
K2.ProcessInstance.DataFields["ApprovalUser"].Value = GetUPN(K2.ActivityInstanceDestination.User.FQN.Remove(0, K2.ActivityInstanceDestination.User.FQN.IndexOf(":") + 1));
}
public static string GetUPN(string FQN)
{
string result = "";
if (FQN == null) return "";
else if (FQN != "")
{
string[] tmp = FQN.Split('\\'); // FQN format would be domain\username
System.DirectoryServices.ActiveDirectory.Domain d = System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
result = tmp[1] + "@" + d.Name; // UPN result should be username@domain.tld
}
return result;
}
Note that in the above code we are simply capturing who the destination user was who finished the client event and saving this in a process data field for later use in the next activity. Also, while we are at it, we are changing that username from it's FQN to the UPN which is the format in which we will need it later on when we do the impersonation. This is very important because if you try to impersonate as the FQN you will get a little error message telling you to go away.
Next we get to the Call Webservice Activity and in there we have just one server event with the following code:
public void Main(Project_e3303dda4c5840e8b2e2a3796d32c9af.EventItemContext_48b7e867c6364d379e1548f9e8098cf3 K2)
{
K2.Synchronous = true;
string UserToImpersonate = K2.ProcessInstance.DataFields["ApprovalUser"].Value.ToString();
WindowsIdentity wi = new WindowsIdentity(UserToImpersonate);
WindowsImpersonationContext wic = null;
string username = "";
try
{
// ==============================================
// start impersonation... (From now on everything happens with the credentials of the impersonated user!!)
// ==============================================
wic = wi.Impersonate();
ImpersonateService.myService yourService = new ImpersonateService.myService();
yourService.Url = K2.StringTable["ImpersonateWSUrl"];
yourService.Credentials = System.Net.CredentialCache.DefaultCredentials;
username = yourService.WhoAmI();
}
finally
{
// ========================================================
// Stop impersonating
// ========================================================
wic.Undo();
wic.Dispose();
}
K2.ProcessInstance.DataFields["ImpersonatedUser"].Value = username;
}
A few important things to note about the above code. We get the UserToImpersonate value from the data field that we set in the previous activity. We then use the System.Security.Principle namespace to impersonate this windows identity. This impersonation uses the WindowsImpersonationContext class the that same namespace to do its magic. Once you call wi.Impersonate(); everything is happening under the context of this impersonated user.
At this point we call our little web service as the impersonated user. We'll talk more about this web service in just a minute.
Then, very very important is once the party is over to clean up all this impersonation stuff. So to make sure that your guests ALWAYS clean up its best to place this in a finally clause after wrapping all the previous stuff in a try. The finally will make sure this clean up gets executed even if there was exceptions. Obviously you can add your own catch blocks if you want to. It's quite important that this wic.Undo() code gets called because if not your K2 server will continue running with the impersonated user credentials and in most cases I can tell you that almost all your processes will fail because that impersonated user does not have the rights to do the magic that your K2 service account was doing.
Also note that we created an environment variable to store the URL for the web service. You don't really have to do it this way but I prefer to make things as configurable as possible.
Lastly, we save the result from the web service into a data field, just so that we can test if this whole evil little plan will work.
You can deploy this process to the server and set rights for it, etc, I'm not going to step you through this if you're K2 blackbelt you should know how to do that by now.
Step 2:
Ok now lets look at the web service that we're calling from the workflow above. Nothing fancy about this if you know your webservices already. I created stock standard webservice and called it (very originally) myService.asmx. In the class there is one method that we're going to call from the workflow, so I called that method WhoAmI() . And amazingly this method needs only one line of code to do its mojo, so without further ado here is this very explicit piece of code:
[WebMethod]
public string WhoAmI()
{
return System.Security.Principal.WindowsIdentity.GetCurrent().Name;
}
Ok rocket scientists, I'm sure you all see what that is all about?! We're simply returning the username of the user account making the request.
However, although the code is real simple, this needs a little bit of configuring. So first go into your web.config file for the web service (or create one if you don’t have one yet) and add this magical line somewhere in the <system.web> section:
<identity impersonate="true" />
K, next open IIS Admin and browse to the virtual application for your web service. Right click on the folder and select properties. Click on the Directory Security tab. Edit the Authentication and access control setting and make sure that anonymous access is Deselected and only integrated windows authentication is selected. For certain complicated distributed environments you might want to have a look at the identity that the application pool is running, but this wont be necessary for our example. Have a look at the links in the homework section at the top for more info directly from Microsoft on this.
Everything should be ready now for us to test the web service and make sure it runs a-ok outside of the workflow. Run the web service in your browser and make sure that it correctly returns your currently logged on user.

Step 3
Ok we've done all the fun bits of code, so now starts the real rocket science with all the big words we talked about at the start.
You will need to know people who know people who KNOW people, OR you can just be a domain admin your self because most of the steps in this section needs domain admin rights.
First we need to tweak the local computer settings on the K2 application server. Since we will be editing the local machine policies you must be careful if there are ever Domain policies created that will over ride these settings (by default it wont).
- Start -> Admin Tools -> Local Security Policy
- Local Policy -> User Rights Assignment
- Double click on "Act as part of the operating system" and add the K2 service account (the service account under which your K2 service is running). Click ok.
- Double click on "Impersonate a client after authentication" and again add the K2 service account. Click ok.
- At this point it seems like a good idea to restart the K2 server (I did not think it would be necessary but things only worked after a restart).
Next we're going to create SPNs for the K2 service account, so you need to have Windows Support Tools installed on the server to use the setspn.exe utility and still be a domain admin dude.
In console window type the following to check which SPNs (Service Principle Names) has been created for our K2 service account guy:
Setspn -l domain\K2serviceaccountdude
This will bring back something looking vaguely (or not) like this:

Note that in my case the SPNs has been created already so if you do not see SPNs for http/hostname and http/hostname.FQN then you will create it by running the following commands:
Setspn -a http/hostname
Setspn -a http/hostname.FQN
PS. Hostname refers to the hostname of the website where your Web service is hosted. If that website is using host headers then you will use the host header name in place of hostname.
Once the above commands executed, you can run setspn -l domain\K2account again just to check that everything is created.
Lekker!
Ok next comes the constrained delegation part. Open up Active Directory users and computers and browse to the K2 service account. Right click on him and select properties. You will now see there is an added delegation tab available. Go there. (PS. If you do not see the delegation tab after creating the above SPNs then you need to raise the domain's functional level to 2003 ).
Click on trust this user for delegation to specified services only (sounds familiar after the constrained delegation speech I gave above, ne?) and click on use any authentication protocol.
Now click on expanded and click on Add...

Click Users or computers and search for the K2 service account dude again and click ok.
This will display the available services for this user as in the picture above.
Click on the http services (for hostname and hostname.FQN) that we created in the command prompt above and then click ok.
It'll now look something like this. Click ok.

Remember that if you have some crazy huge AD with many replicated servers then it might take a while for your domain controllers to replicate all these cool settings we did just now. So in that case go grab a cup of coffee and check your mail or something. However if you have just a single domain, then you're not off the hook buddy, lets keep moving forward! You can get your coffee later. :)
Everything should be setup and ready for show time! Only thing left to do is to test all of this together to see if it actually works.
So I'm too lazy to build complicated front ends for this workflow so I just kick it off from the workspace. If you want to define the destination user for the approval activity then remember to specify that data field.

This should go to the defined users work list. Again, since I'm too lazy for any front ends for this I'm just going to approve this action from the work list without opening the item.

At this second the assigned destination user should be recorded in the data field and if all is going well the process is moving on to call the web service. So now it's time for the ultimate test - lets go look at what is recorded in the data fields in the reports to verify that it worked.
Go to the Process Overview report. Drill down into the process and the process instance that we just started. In the process instance click on Data and we should see the results as follows:

Awesome!! So ImpersonatedUser is the value that we got back from the webserivce and should be the same user (in FQN format) as in ApprovalUser and DestUser! Great!
If you got an access denied error then go back and make sure all the settings above are configured properly. Or if your result returned the user account of the K2 server then erm… ehm well.. You clearly are not transitioning your protocols and some troubleshooting will have to be done.
Ok that is it for today. Happy constraining your delegation !!