Wednesday, December 21, 2011

When SSL termination occurs at Load balancer we can use ClearBinding for passing UserName or Certificate over Http

We might encounter scenarios where the Load balancer takes care of securing the transport channel and terminates the SSL at the load balancer and decrypts the traffic and redirects to the appropriate server. Now when we want to implement username password authentication on a WCF Service in such scenarios we need to use clear binding. The following article explains on how to create clear binding and provides support for both custom username password and client certificate validation on such scenarios.

First create a class that inherits CustomBinding and let us call it ClearBinding.cs:


using System.ServiceModel;
using System.ServiceModel.Channels;
 
namespace ClearBindingSample
{
    public class ClearBinding : CustomBinding
    {
        private MessageVersion messageVersion = MessageVersion.None;
        private MessageCredentialType clientCredentialType;
 
        public void SetMessageVersion(MessageVersion value)
        {
            messageVersion = value;
        }
 
        public void SetClientCredentialType(MessageCredentialType messageCredentialType)
        {
            clientCredentialType = messageCredentialType;
        }
 
        public override BindingElementCollection CreateBindingElements()
        {
            var res = new BindingElementCollection {new TextMessageEncodingBindingElement() {MessageVersion = messageVersion}};
            
            var securityBindingElement = GetTransport();
            
            if (securityBindingElement != null)
            {
                res.Add(securityBindingElement);
            }
            
            res.Add(new AutoSecuredHttpTransportElement());
            
            return res;
        }
 
        public override string Scheme
        {
            get
            {
                return "http";
            }
        }
 
        private TransportSecurityBindingElement GetTransport()
        {
            if (clientCredentialType == MessageCredentialType.Certificate)
            {
                return SecurityBindingElement.CreateCertificateOverTransportBindingElement();
            }
            if (clientCredentialType == MessageCredentialType.UserName)
            {
                return SecurityBindingElement.CreateUserNameOverTransportBindingElement();
            }
            return null;
        }
    }
}

Now we need to create a class that inherits the StandardBindingElement and let us call it ClearBindingElement.cs:


using System;
using System.Configuration;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
 
namespace ClearBindingSample
{
    internal class ClearBindingElement : StandardBindingElement
    {
        private ConfigurationPropertyCollection properties;
 
        protected override void OnApplyConfiguration(Binding binding)
        {
            var clearBinding = binding as ClearBinding;
            clearBinding.SetMessageVersion(MessageVersion);
            clearBinding.SetClientCredentialType(ClientCredentialType);
        }
 
        protected override Type BindingElementType
        {
            get { return typeof(ClearBinding); }
        }
 
        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                if (this.properties == null)
                {
                    var properties = base.Properties;
                    properties.Add(new ConfigurationProperty("messageVersion", typeof(MessageVersion), MessageVersion.Soap11, new MessageVersionConverter(), null, ConfigurationPropertyOptions.None));
                    properties.Add(new ConfigurationProperty("clientCredentialType", typeof(MessageCredentialType), MessageCredentialType.None.ToString(), new ClientCredentialTypeConvertor(), null, ConfigurationPropertyOptions.None));
                    this.properties = properties;
                }
                return this.properties;
            }
        }
 
        public MessageVersion MessageVersion
        {
            get
            {
                return (MessageVersion)base["messageVersion"];
            }
            set
            {
                base["messageVersion"] = value;
            }
        }
 
        public MessageCredentialType ClientCredentialType
        {
            get { return (MessageCredentialType)base["clientCredentialType"]; }
            set { base["clientCredentialType"] = value; }
        }
    }
}

Now we inherit the StandardBindingCollectionElement and let us call it ClearCollectionElement.cs


using System.ServiceModel.Configuration;
 
namespace ClearBindingSample
{
    internal class ClearCollectionElement : StandardBindingCollectionElement<ClearBinding, ClearBindingElement>
    {
    }
}

Now we need a type convertor that converts the attribute client credentials to the appropriate type and let us call it ClientCredentialTypeConvertor.cs


using System;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Globalization;
using System.ServiceModel;
 
namespace ClearBindingSample
{
    internal class ClientCredentialTypeConvertor : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return ((typeof(string) == sourceType) || base.CanConvertFrom(context, sourceType));
        }
 
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return ((typeof(InstanceDescriptor) == destinationType) || base.CanConvertTo(context, destinationType));
        }
 
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (!(value is string))
            {
                return base.ConvertFrom(context, culture, value);
            }
            var str = ((string)value).ToUpper();
            switch (str)
            {
                case "USERNAME":
                    return MessageCredentialType.UserName;
                case "CERTIFICATE":
                    return MessageCredentialType.Certificate;
                case "NONE":
                    return MessageCredentialType.None;
                default:
                    return MessageCredentialType.None;
            }
        }
 
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if ((typeof(string) != destinationType) || !(value is MessageCredentialType))
            {
                return base.ConvertTo(context, culture, value, destinationType);
            }
            var credentialType = (MessageCredentialType)value;
            if (credentialType == MessageCredentialType.UserName)
            {
                return "UserName";
            }
            if (credentialType == MessageCredentialType.Certificate)
            {
                return "Certificate";
            }
            if (credentialType == MessageCredentialType.None)
            {
                return "None";
            }
            return "None";
        }
    }
}

Similarly, we need a convertor that translates message version into an appropriate type understood by the system and let us call it MessageVersionConvertor.cs:


using System;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Globalization;
using System.ServiceModel.Channels;
 
namespace ClearBindingSample
{
    internal class MessageVersionConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return ((typeof(string) == sourceType) || base.CanConvertFrom(context, sourceType));
        }
 
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return ((typeof(InstanceDescriptor) == destinationType) || base.CanConvertTo(context, destinationType));
        }
 
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (!(value is string))
            {
                return base.ConvertFrom(context, culture, value);
            }
            var str = (string)value;
            switch (str)
            {
                case "Soap11WSAddressing10":
                    return MessageVersion.Soap11WSAddressing10;
 
                case "Soap12WSAddressing10":
                    return MessageVersion.Soap12WSAddressing10;
 
                case "Soap11WSAddressingAugust2004":
                    return MessageVersion.Soap11WSAddressingAugust2004;
 
                case "Soap12WSAddressingAugust2004":
                    return MessageVersion.Soap12WSAddressingAugust2004;
 
                case "Soap11":
                    return MessageVersion.Soap11;
 
                case "Soap12":
                    return MessageVersion.Soap12;
 
                case "None":
                    return MessageVersion.None;
 
                case "Default":
                    return MessageVersion.Default;
            }
            throw new ArgumentOutOfRangeException("messageVersion", str, "The argument must be of type System.ServiceModel.Channels.MessageVersion");
        }
 
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if ((typeof(string) != destinationType) || !(value is MessageVersion))
            {
                return base.ConvertTo(context, culture, value, destinationType);
            }
            var version = (MessageVersion)value;
            if (version == MessageVersion.Default)
            {
                return "Default";
            }
            if (version == MessageVersion.Soap11WSAddressing10)
            {
                return "Soap11WSAddressing10";
            }
            if (version == MessageVersion.Soap12WSAddressing10)
            {
                return "Soap12WSAddressing10";
            }
            if (version == MessageVersion.Soap11WSAddressingAugust2004)
            {
                return "Soap11WSAddressingAugust2004";
            }
            if (version == MessageVersion.Soap12WSAddressingAugust2004)
            {
                return "Soap12WSAddressingAugust2004";
            }
            if (version == MessageVersion.Soap11)
            {
                return "Soap11";
            }
            if (version == MessageVersion.Soap12)
            {
                return "Soap12";
            }
            if (version != MessageVersion.None)
            {
                throw new ArgumentOutOfRangeException("messageVersion", value, "The argument must be of type System.ServiceModel.Channels.MessageVersion");
            }
            return "None";
        }
    }
}

In order for your Username password to be passed over the channel we need to create a class that supports ITransportTokenAssertionProvider and let us call it AutoSecuredHttpSecurityCapabilities.cs:


using System.Net.Security;
using System.ServiceModel.Channels;
 
namespace ClearBindingSample
{
    public class AutoSecuredHttpSecurityCapabilities : ISecurityCapabilities
    {
        public ProtectionLevel SupportedRequestProtectionLevel
        {
            get { return ProtectionLevel.EncryptAndSign; }
        }
 
        public ProtectionLevel SupportedResponseProtectionLevel
        {
            get { return ProtectionLevel.EncryptAndSign; }
        }
 
        public bool SupportsClientAuthentication
        {
            get { return true; }
        }
 
        public bool SupportsClientWindowsIdentity
        {
            get { return false; }
        }
 
        public bool SupportsServerAuthentication
        {
            get { return true; }
        }
    }
}


Now we need a HttpTransportBindingElement that supports the provision of specifying the transport capabilities and let us call it AutoSecuredHttpTransportElement.cs:


using System.ServiceModel.Channels;
 
namespace ClearBindingSample
{
    public class AutoSecuredHttpTransportElement : HttpTransportBindingElement, ITransportTokenAssertionProvider
    {
        public override T GetProperty<T>(BindingContext context)
        {
            if (typeof(T) == typeof(ISecurityCapabilities))
                return (T)(ISecurityCapabilities)new AutoSecuredHttpSecurityCapabilities();
 
            return base.GetProperty<T>(context);
        }
 
        public System.Xml.XmlElement GetTransportTokenAssertion()
        {
            return null;
        }
    }
}


Now we have created a new binding element called ClearBinding. Now open your web.config and add this new extension as shown below:


<system.serviceModel>
    <extensions>
        <bindingExtensions>
            <add name="clearBinding" type="ClearBindingSample.ClearCollectionElement, ClearBindingSample"/>
        </bindingExtensions>
    </extensions>
    <bindings>
        <clearBinding>
            <binding name="clearUsername" messageVersion="Soap11" clientCredentialType="Username"/>
            <binding name="clearCertificate" messageVersion="Soap11" clientCredentialType="Certificate"/>
        </clearBinding>
    </bindings>
</system.serviceModel>

Now you can use this binding in your endpoint and you have achieved passing username password over http without any issues.

How to flatten your Wsdl?

When creating web services that are to be interoperable (easily accessbile across various platforms) the wsdl cannot have any xsd:import elements. In order to achieve this we need to flatten the wsdl. We do this by creating our own ServiceHostFactory.

First we create a class that defines the new ServiceHostFactory called FlatWsdlServiceHostFactory :


using System;
 
namespace FlatWsdl
{
    public sealed class FlatWsdlServiceHostFactory : System.ServiceModel.Activation.ServiceHostFactory
    {
        public override System.ServiceModel.ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
        {
            return base.CreateServiceHost(constructorString, baseAddresses);
        }
 
        protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            return new FlatWsdlServiceHost(serviceType, baseAddresses);
        }
    }
}

Now we create a Custom ServiceHost that performs the action of flattening the wsdl:


using System;
using System.ServiceModel.Description;
 
namespace FlatWsdl
{
    public class FlatWsdlServiceHost : System.ServiceModel.ServiceHost
    {
        public FlatWsdlServiceHost() { }
 
        public FlatWsdlServiceHost(Type serviceType, params Uri[] baseAddresses) : base(serviceType, baseAddresses)
        {
            
        }
 
        public FlatWsdlServiceHost(object singletonInstance, params Uri[] baseAddresses) : base(singletonInstance, baseAddresses)
        {
            
        }
 
        /// <summary>
        /// Just Inject Flat WSDL
        /// </summary>
        protected override void ApplyConfiguration()
        {
            Console.WriteLine("ApplyConfiguration (thread {0})", System.Threading.Thread.CurrentThread.ManagedThreadId);
            base.ApplyConfiguration();
 
            InjectFlatWsdlExtension();
        }
 
        private void InjectFlatWsdlExtension()
        {
            foreach (ServiceEndpoint endpoint in this.Description.Endpoints)
            {
                endpoint.Behaviors.Add(new FlatWsdl());
            }
        }
    }
}

Now we have the actual class that inspects your wsdl and injects the xsd's into the wsdl by replacing the xsd:import elements in your wsdl.


using System.Collections;
using System.Collections.Generic;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Xml.Schema;
using ServiceDescription = System.Web.Services.Description.ServiceDescription;
 
namespace FlatWsdl
{
    public class FlatWsdl: IWsdlExportExtension, IEndpointBehavior
    {
        public void ExportContract(WsdlExporter exporter, WsdlContractConversionContext context) { }
 
        public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
        {
            XmlSchemaSet schemaSet = exporter.GeneratedXmlSchemas;
            foreach (ServiceDescription wsdl in exporter.GeneratedWsdlDocuments)
            {
                List<XmlSchema> importsList = new List<XmlSchema>();
                foreach (XmlSchema schema in wsdl.Types.Schemas)
                    AddImportedSchemas(schema, schemaSet, importsList);
                
                if (importsList.Count == 0)
                    return;
 
                wsdl.Types.Schemas.Clear();
 
                foreach (XmlSchema schema in importsList)
                {
                    RemoveXsdImports(schema);
                    wsdl.Types.Schemas.Add(schema);
                }
            }
        }
 
        private void AddImportedSchemas(XmlSchema schema, XmlSchemaSet schemaSet, List<XmlSchema> importsList)
        {
            foreach (XmlSchemaImport import in schema.Includes)
            {
                ICollection realSchemas = schemaSet.Schemas(import.Namespace);
                
                foreach (XmlSchema ixsd in realSchemas)
                {
                    if (!importsList.Contains(ixsd))
                    {
                        importsList.Add(ixsd);
                        AddImportedSchemas(ixsd, schemaSet, importsList);
                    }
                }
            }
        }
 
        private void RemoveXsdImports(XmlSchema schema)
        {
            for (int i = 0; i < schema.Includes.Count; i++)
            {
                if (schema.Includes[i] is XmlSchemaImport)
                    schema.Includes.RemoveAt(i--);
            }
        }
 
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
            
        }
 
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
 
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
 
        public void Validate(ServiceEndpoint endpoint) { }
    }
}

Now your FlatWsdlServiceHostFactory is ready which would flatten your wsdl. Create a new WCF Service called FaltWsdlService.svc and open the markup of this file which the following code


<%@ ServiceHost Language="C#" Debug="true" Service="Sample.FlatWsdlService" CodeBehind="FlatWsdlService.svc.cs" %>

Replace the above with the code below which states the Service needs to use the FlatWsdlServiceHostFactory instead of the default ServiceHostFactory from WCF.


<%@ ServiceHost Language="C#" Debug="true" Factory="FlatWsdl.FlatWsdlServiceHostFactory" Service="SampleService.FlatWsdlService" CodeBehind="FlatWsdlService.svc.cs" %>

Now when you browse to your wsdl you not find any xsd:import elements rather you would have one single wsdl.

Monday, December 5, 2011

Create custom config section handler

In order to create a custom config section handler we would use the following namespaces:

System.Configuration
System.Collections
System.Collections.Generic
System.Collections.Specialized

Now a sample xml for which we intend to create a config section handler:

<TransformerConfigurationSection MaxSlotsPerMachine="5">
    <!-- Remote Service Machines-->
    <servicemachine id="01">
      <Id value="1" />
      <Name value="server1" />     
    </servicemachine>
    <servicemachine id="02">
      <Id value="2" />
      <Name value="server2" />
    </servicemachine>
</TransformerConfigurationSection>

Now first we create a ConfigurationSection Class that reads this section from a config file. The code looks as follows:


public class TransformerConfigurationSection : ConfigurationSection 
{
    // 1. MaxSlotsPerMachine -- attribute
    // 2. serviceMachine -- Element collection
 
    public const string SectionXPath = "TransformerConfigurationSection";
    public TransformerConfigurationSection()
    {
        Properties.Add(new ConfigurationProperty("MaxSlotsPerMachine", typeof(string), null));
    }
 
    public static TransformerConfigurationSection GetSection()
    {
        return (TransformerConfigurationSection)ConfigurationManager.GetSection(SectionXPath);
    }
 
    [ConfigurationProperty("MaxSlotsPerMachine")]
    public string MaxSlotsPerMachine
    {
        get
        {
            return (string)this["MaxSlotsPerMachine"];
        }
    }
 
    [ConfigurationProperty("", Options = ConfigurationPropertyOptions.IsDefaultCollection)]
    public MachineElementCollection ServiceMachines
    {
        get
        {
            return (MachineElementCollection)this[""];
        }
    }
}

Now we create the class MachineElementCollection that represents a ConfigurationElementCollection.

public class MachineElementCollection : MachineConfigurationElementCollection<ServiceMachineElement>
{
    public MachineElementCollection()
        :base("servicemachine")
    {  
    }
 
    protected override object  GetElementKey(ConfigurationElement element)
    {
        return ((ServiceMachineElement) element).Id;
    }
}
Now create a base class that allows you to reuse the ConfigurationElementCollection to be of any type.

public abstract class MachineConfigurationElementCollection<T> : ConfigurationElementCollection where T: ConfigurationElement, new()
{
    private ConfigurationElementCollectionType collectionType;
    private string elementName;
    public override ConfigurationElementCollectionType  CollectionType
    {
        get 
        { 
                return collectionType;
        }
    }
 
    protected override string  ElementName
    {
        get
        {
            return String.IsNullOrEmpty(elementName) ? base.ElementName : elementName;
        }
    }
 
    public virtual T this[object key]
    {
        get
        {
            var configurationElementType = (T)BaseGet(key);
            return (object)configurationElementType != null ? configurationElementType : null;
        }
        set
        {
            if (IsReadOnly())
                Add(value);
            if (GetElementKey(value).ToString().Equals((string)key, StringComparison.Ordinal))
            {
                if (BaseGet(key) != null)
                    BaseRemove(key);
                Add(value);
            }
        }
    }
 
    public T this[int index]
    {
        get
        {
            return (T)BaseGet(index);
        }
        set
        {
            if (!IsReadOnly() && !ThrowOnDuplicate && BaseGet(index) != null)
                BaseRemoveAt(index);
            BaseAdd(index, value);
        }
    }
 
    internal MachineConfigurationElementCollection()
        : this(ConfigurationElementCollectionType.AddRemoveClearMap, null)
    {
    }
 
    internal MachineConfigurationElementCollection(string elementname)
        : this(ConfigurationElementCollectionType.AddRemoveClearMap, elementname)
    {
    }
 
    internal MachineConfigurationElementCollection(ConfigurationElementCollectionType collectionType, string elementName)
    {
        this.collectionType = collectionType;
        this.elementName = elementName;
        if (string.IsNullOrEmpty(elementName))
            return;
        AddElementName = elementName;
    }
 
    internal MachineConfigurationElementCollection(ConfigurationElementCollectionType collectionType, string elementName, IComparer comparer)
        : base(comparer)
    {
        this.collectionType = collectionType;
        this.elementName = elementName;
    }
 
    protected override void BaseAdd(ConfigurationElement element)
    {
      if (!IsReadOnly() && !ThrowOnDuplicate)
      {
        object elementKey = this.GetElementKey(element);
        if (ContainsKey(elementKey))
          BaseRemove(elementKey);
      }
 
      base.BaseAdd(element);
    }
 
    public void Add(T element)
    { 
      BaseAdd(element);
    }
 
    public void Clear()
    {
      BaseClear();
    }
 
    public virtual bool ContainsKey(object key)
    {
      if (key != null)
        return null != BaseGet(key);
      var list = new List<string>();
 
      foreach (PropertyInformation propertyInformation in (NameObjectCollectionBase) CreateNewElement().ElementInformation.Properties)
      {
        if (propertyInformation.IsKey)
          list.Add(propertyInformation.Name);
      }
        return false;
    }
 
    protected override ConfigurationElement CreateNewElement()
    {
      return Activator.CreateInstance<T>();
    }
}
Now since the ConfigurationElementCollection has been created now we create the actual ConfigurationElement.

public class ServiceMachineElement : ConfigurationElement
{
    public ServiceMachineElement()
    {
        Properties.Add(new ConfigurationProperty(ServiceMachineIdKey, typeof(string), ""));
        Properties.Add(new ConfigurationProperty(IdKey, typeof(IdElement), null));
        Properties.Add(new ConfigurationProperty(NameKey, typeof(NameElement), null));
    }
 
    private const string ServiceMachineIdKey = "id";
    [ConfigurationProperty(ServiceMachineIdKey, IsRequired = false)]
    public string ServiceMachineId
    {
        get { return (String)this[ServiceMachineIdKey]; }
        set { this[ServiceMachineIdKey] = value; }
    }    
 
    private const string IdKey = "Id";
    [ConfigurationProperty(IdKey, IsRequired = true)]
    public IdElement Id
    {
        get { return (IdElement)this[IdKey]; }
        set { this[IdKey] = value; }
    }
 
    private const string NameKey = "Name";
    [ConfigurationProperty(NameKey, IsRequired = true)]
    public NameElement Name
    {
        get { return (NameElement)this[NameKey]; }
        set { this[NameKey] = value; }
    }    
}
Since, we have the servicemachine element with child elements of type NameElement and IdElement we create these classes.

public class IdElement : ConfigurationElement
{
    public IdElement()
    {
        Properties.Add(new ConfigurationProperty(ValueKey, typeof(string), null));
    }
 
    private const string ValueKey = "value";
    [ConfigurationProperty(ValueKey, IsRequired = true)]
    public string Value
    {
        get { return (string)this[ValueKey]; }
        set { this[ValueKey] = value; }
    }
}

public class NameElement : ConfigurationElement
    {
        public NameElement()
        {
            Properties.Add(new ConfigurationProperty(ValueKey, typeof(string), null));
        }
 
        private const string ValueKey = "value";
 
        [ConfigurationProperty(ValueKey, IsRequired = true)]
        public string Value
        {
            get { return (string)this[ValueKey]; }
            set { this[ValueKey] = value; }
        }
    }

That is it. The config section reader is done. Now just add the above xml to your app.config or web.config and define the configsection as shown below:

<configuration>
  <configSections>
    <section name="TransformerConfigurationSection" type="Sample.TransformerConfigurationSection, Sample"/>
  </configSections>
</configuration>