Saturday, September 10, 2011

Consume a WCF service that uses Federated Security

This post is not about Active Directory Federated Security, but it is about using a custom Security Token Service (STS) to create a token.   I needed to connect to a third party web service that used Federated Security. Now they supported ADFS but I only needed to access using a single account and I really didn't want to set up ADFS just for that.  But I could use a custom STS service to create the token and avoid all the infrastruture overhead of ADFS.

Setup begins with the third party sending me the url to their STS and a copy of the public key certificate they will use to sign their token. I also need to have the url to their web service I want to call.

I'll say right away I'm no expert on this. There are examples in the Windows Identity SDK and that is a good starting point. The way it would work is my client application (a  Windows Service) would call a local STS and pass it a username and password.  My STS would check the credentials and if OK issue a token. Now the token is signed and encrypted by my X509 certificate so I have to get one of those to begin with.  I need to install my X509 certificate in the Personal  store of  the computer I run the code on.
Start MMC, add the Certficate add-in and select the Local Computer. Navigate to the Personal node, and Certificates, right click and import my X509 certificate.  Since I am running my Windows Service under the Network Service account I need to assign permissions.  On the Certificate, right click, select All Tasks then Manage Private key. On the Security tab, add   Network Service and give it full permissions. I can also add the thrid party's certificate here too - it is imported into the Personal Certificate store in the same way but since they gave me the public key, I don't need to bother with the permissions step (its not available anyway).

I also need to send the public key of my certificate to the third party.  Right click on the certificate, select Export and choose the option to export the public key only. 

Now the token issued by my STS contains a claim, in my case it is simply the role of Reader.  I then pass my token to the third party's STS. These guys will want to check the token is from me and they use the certificate I sent them to do so.  They check the claim, and if all is well issue me a token from their STS. 

I suppose I should validate their token with the certificate they sent me, but I'm not going to bother as I'm going to send it straight back to them when I call their web service. 
When I look at the WSDL of their STS service I can see it is different from a typical WCF service because it has a section at the bottom which looks like this.

<identity xmlns="http://schemas.xmlsoap.org/ws/2006/02/addressingidentity">
<keyinfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<x509data>
<x509certificate>MIIE8zCCA9ugAwIBAgILAQAAAAABLl3i0lAwDQYwSQY2ZNWvOf2k</x509certificate> <x509data>
</keyinfo>
</identity>

The trick is to find a way to send my token to their WCF web service. Add a Service Reference to their WCF web service in the usual way. My example uses MyWS.Service with the url of http://company.co.uk/service.svc.  Note that when I setup the binding to this WCF service I have to include a reference to the issuer of the token (the url to their STS). I also needed to include a reference to the DNS identity.  You can usually assume the DNS Identity from the Subject name on the third party certificate so if that is CN=company.co.uk then the DNS Identity will be company.co.uk.

// add constants for their STS service and DNS identity
public const string ServiceAddress = "http://company.co.uk/service.svc";

public const string DNSIdentity = "company.co.uk";
public const string STSAddress = "http://security.company.co.uk/security.svc";


 public void RetrieveData(SecurityToken smToken)
{
   // Instantiate the ChannelFactory as usual.
   //Be sure to set the DNS Identity on the Endpoint

   EndpointAddress endpointAddress = new EndpointAddress(new Uri(ServiceAddress), new DnsEndpointIdentity(DNSIdentity), new AddressHeaderCollection());

   ChannelFactory clientFactory = new ChannelFactory(GetServiceBinding(ServiceAddress), endpointAddress);
   clientFactory.Credentials.SupportInteractive = false;

   // Make sure to call this prior to using the
   //CreateChannelWith...()
   // extension methods on the channel factory that the Windows
   //Identity Foundation provides.
   clientFactory.ConfigureChannelFactory();
   ICommunicationObject channel = null;

   bool succeeded = false;

   try
   { // create an instance of the Pension Service client

      MyWS.Service client = clientFactory.CreateChannelWithIssuedToken(smToken);
      channel = (ICommunicationObject)client;
      // Now its plain sailing
      // I can call the method on the WCF service
      // in may case GetData returns an array of objects
      MyWS.MyObject[] psArray = client.GetData();
      // TO DO something with psArray
      channel.Close();
      succeeded = true;
   }

   catch (CommunicationException e)
   {  // TO DO log error
      channel.Abort();
   }
   catch (TimeoutException)
   {  // TO DO log error
      channel.Abort();
   }
   finally
   {
       if (!succeeded && channel != null)
       {
          channel.Abort();
       }
    }
   return ;
}


public Binding GetServiceBinding(string uri)
{
   // Use the standard WS2007FederationHttpBinding
   WS2007FederationHttpBinding binding = new WS2007FederationHttpBinding();
   binding.Security.Message.IssuerAddress = new EndpointAddress(STSAddress);
   binding.Security.Message.IssuerBinding = GetSMSecurityTokenServiceBinding(STSAddress);
   binding.Security.Message.IssuerMetadataAddress = new EndpointAddress(STSAddress + "/mex");
   return binding;


Good luck. In terms of difficulty on a scale of 1-10 this is a twelve. I wish you success.

Attach to Process is Greyed Out in Visual Studio

If you Google "Attach to Process is greyed out" as I have, all the responses will tell you to make sure you check the boxes "Show processs from all users" and "Show processs in all sessions".

What if you do that and it's still greyed out?

Answer: Use a different approach. Add the line
System.Diagnostics.Debugger.Launch();
to your code and it will launch the dialog box which allows you to connect to a new Visual Studio session or an existing one.

Once you do so, the debugger will stop at the line you just added and allow you to step through the code.

Tip: If you are using a timer in your windows service then disable the timer before you call the debugger. That way you stop the timer from kicking you back to the start every time it fires.

For example:
timer1.Dispose();
System.Diagnostics.Debugger.Launch();

Installing a Windows Service

There was a time when you could just create a new project in Visual Studio as a Windows Service and you could install it using the installutil command.

Somewhere along the way that changed so now you have to add a Project Installer class to the Windows Service.

So lets assume you've created your windows service and changed the name from Service1 to say MyService. Microsoft explain the steps for adding an installer and I repeat them here.

1. In Solution Explorer, access Design view for the service for which you want to add an installation component.

2. Click the background of the designer to select the service itself, rather than any of its contents.

3. With the designer in focus, right-click, and then click Add Installer.

4. A new class, ProjectInstaller, and two installation components, ServiceProcessInstaller and ServiceInstaller, are added to your project, and property values for the service are copied to the components.

5. Click the ServiceInstaller component and verify that the value of the ServiceName property is set to the same value as the ServiceName property on the service itself. In my example you would have to change Service1 to MyService.

6. Change the StartType to Manual, Automatic or Disabled.

7. Click on the ServiceProcesInstaller and set the User property to Local serice, Network Service, System or User.

That's it. Open the Visual Studio command prompt using "Run as Administrator", chage to the directory where your EXE file is and run

installutil MyWindowsService.exe

You might be interested in my next post about debugging a Windows Service. Frankly I find debugging Windows Services a pain, so I tend to write and test my code as a Console Application, and then I convert it to a Windows Service when its all working.

Sunday, August 21, 2011

Moving CRM Attachments to SharePoint

CRM 2011 allows you to store documents in SharePoint and view them from within CRM.  Someone called Maria left me a comment asking if you could move attachments from CRM to SharePoint. My previous blogs about creating SharePoint document locations from CRM were actually prerequisites for doing exactly this.  I wanted to take a CRM attachment and move it to SharePoint. In my case it was an attachment on a letter activity as the result of a mailmerge. To be accurate it is an attachment on an annotation (or note) on a letter activity. I needed to move the attachment to SharePoint and then leave a link to the document on the letter activity.  To achieve this we created a plugin on the PreCreate action of an annotation. Then in the execute method of the plugin you can instantiate the annotation entity

if (null != context && null != context.InputParameters)
{
      if (context.InputParameters.Contains("Target") &&
          context.InputParameters["Target"] is Entity)
     {
           // Obtain the target entity from the input parmameters.
          Entity entity = (Entity)context.InputParameters["Target"];
     }
}
Once you have the annotation then you can get the contents of the attachment as a byte array.

// Retrieve the base64Encoding document body
// and convert it to byte base64encoding
// see below for the DecodeFrom64 function
    byte[] documentBody = DecodeFrom64(entity.Attributes["documentbody"].ToString());
    // get the filename
   System.IO.FileInfo annotationFileInfo = new System.IO.FileInfo(fileName);
To remove the attachment you need to use this code

entity.Attributes["documentbody"] = null;
entity.Attributes["filename"] = null;
entity.Attributes["filesize"] = null;

The helper method for decoding the attachment to a byte array

internal static byte[] DecodeFrom64(string encodedData)
{
     byte[] encodedDataAsBytes = System.Convert.FromBase64String(encodedData);
     return encodedDataAsBytes;
}

To upload the document to SharePoint you should use the CopyIntoItems method of the Copy web service located at http://sharepointurl/_vti_bin/Copy.asmx. But before you do, you need to replace any illegal characters in the filename - in our case we replaced an illegal character with a hyphen.

internal static string ReplaceSpecialCharacters(string input)
{
   Regex r = new Regex("(?:[^a-z0-9 ]|(?<=['\"])s)", RegexOptions.IgnoreCase |                         RegexOptions.CultureInvariant | RegexOptions.Compiled);
   return r.Replace(input, "-");
}
I may have posted this code too late to help Maria, but I hope someone else finds it useful.

Same blog, different theme

I've grown bored of the theme that I use for the blog and somebody posted a comment about changing the theme so I've done so.

I hope this makes the blogs easier to read.

Monday, July 4, 2011

Transforming XML using BizTalk Mapper generated XSLT

If I have to write XSLT I try and make use of the BizTalk Mapper because it can generate XSLT in a fraction of the time.  It also supplies a test harness so you can use an input file to test out your map.
You can add functoids to your map and I make use of the Scripting functoid so that I can write inline C# script.  Here is an example of a function that takes a string source element and will truncate it if it is longer than the string length the target element can handle.  It writes out an Information message to the event log if the maximum length is exceeded.
The function takes two extra parameters apart from the source element:  the name of the source element and the maximum length.  Add them by clicking on the ellipses next to Configure Functoid Inputs.

Here is the inline C# script.
public string Transform(string param1, string fieldname, int32 maxlength)
{
       if (param1.Length > maxlength)
      {
             System.Diagnostics.EventLog.WriteEntry("Transformation", fieldname + " in excess of " +   maxlength.ToString() + " chars, truncating", System.Diagnostics.EventLogEntryType.Warning);
             return param1.Substring(0, 20);
      }
      else
     {
             return param1;
      }
}

What BizTalk does is to write this out as a function at the botton of the XSLT.  To produce the XSLT, use Validate Map and then navigate to where the output window has written the file. 

Now I struggled a bit to get this XSLT to work until I finally found the answer was to use an XPathDocument instead of an XmlDocument. That's it. A transformation using the output from the BizTalk Mapper.  Deep, deep joy. 

Add the following using statements
using System.Xml;
using System.Xml.Xpath;
using System.Xml.Xslt;

// Transforms an XML document
// using an XSLT generated by BizTalk
// note use of XPathDocument
private XmlDocument Transform()
{

     XslCompiledTransform xslt = new XslCompiledTransform();
     // load the xslt
     xslt.Load(@"C:\Projects\Import\MySchema.xsl", new XsltSettings(false, true), new XmlUrlResolver());
     string filePathName = @"C:\Projects\Import\MyXML.xml";
     //Load the XML data file.
     XPathDocument doc1 = new XPathDocument(filePathName);
     // create a memory stream
     MemoryStream ms = new MemoryStream();
    //Create an XmlTextWriter to write to the memory stream
    XmlTextWriter writer = new XmlTextWriter(ms, Encoding.Unicode);
    writer.Formatting = Formatting.Indented;

    //Transform the file.
    xslt.Transform(doc1, null, writer, null);
    ms.Seek(0, SeekOrigin.Begin);  // ** UPDATE changed from ms.Position=0 ****//
     if (ms.Length == 0)
    {
           Exception ex = new Exception ("Transform error , output is null");
           throw ex;
    }
    // load the memory stream into a XML document
    XmlDocument output = new XmlDocument();
    output.Load(ms);
    writer.Close();
    return output;
}

Monday, April 4, 2011

Darwin Awards 2010

A non-technical blog this month, dear reader, forced upon me because of a misleading blog I read today. Imagine that! What is the Internet coming too when you can't trust a blog?

Anyway, I Googled for the "Darwin Awards 2010" and stumbled on a blog that claimed to announce the Darwin Awards results for 2010. While some of the alleged awards were amusing - I particularly liked the guy who tried to siphon off diesel from a camper van but mistakenly put the hose into the sceptic tank - they were clearly not true Darwin Awards.  I need scarcely remind you dear reader that the Darwin Awards "commemorate those who improve our gene pool by removing themselves from it". In short, they are only ever awarded posthumously for unparalleled stupidity. 

The real Darwin Awards web site can be found at this location so please don't confuse it with imitations.

For reasons of good taste, I won't reproduce the winning entry here.  However I do recount this runner up which unfortunately has not been confirmed as true.

In the late fall and early winter months, snow-covered mountains become infested with hunters. One ambitious pair climbed high up a mountain in search of their quarry. The trail crossed a small glacier that had crusted over. The lead hunter had to stomp a foot-hold in the snow, one step at a time, in order to cross the glacier.

Somewhere near the middle of the glacier, his next stomp hit not snow but a rock. The lead hunter lost his footing and fell. Down the crusty glacier he zipped, off the edge and out of sight.

Unable to help, his companion watched him slide away. After a while, he shouted out, "Are you OK?"

"Yes!" came the answer.

Reasoning that it was a quick way off the glacier, the second hunter plopped down and accelerated down the ice, following his friend. There, just over the edge of the glacier, was his friend...holding onto the top of a tree that barely protruded from the snow.

There were no other treetops nearby, nothing to grab, nothing but a hundred-foot drop onto the rocks below. As the second hunter shot past the first, he uttered his final epitaph: a single word, which we may not utter lest our mothers soap our mouths.