Tuesday, April 22, 2014

WCF - Service Authentication


This post will help you to implement Message Credential Security on WCF Services over HTTP/SOAP 1.1.

I have seen in many services implementations that require authentication for consume service's operations, providing the user's credential in message body, as a class's property. Eg:

   [DataContract]
    public class PersonType
    {
        #region Primitive Properties
 
        [DataMember]
        public int PersonTypeID
        {
            get;
            set;
        }
 
        [DataMember]
        public string PersonTypeName
        {
            get;
            set;
        }
 
        [DataMember]
        public string UserName
        {
            get;
            set;
        }
 
        [DataMember]
        public string Password
        {
            get;
            set;
        }
    }

The consumer should define the UserName and Password properties to invoke a service operation. Each service operation need to read this properties and validate then:

public PersonType AddPersonType(PersonType personType)
{
    if(!AuthenticateUser(personType.UserName, personType.Password))
        throw new Exception("Login Failed")
 
    //Method implementation
}

This will work, but is not a SOA best practice. The canonical model will contain your domain model mixed with technical attributes.

Microsoft WCF provides in System.IdentityModel namespace, classes to automate and standardize according of the SOAP 1.1 security specifications.

Create your own UserIdentity class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Principal;
 
namespace Contoso.Security.Principal
{
    /// <summary>
    /// Defines the basic atributtes of user identity.
    /// </summary>
    public class UserIdentity : IIdentity
    {
        #region Constructor
 
        /// <summary>
        /// Creates a new instance of UserIdentity class.
        /// </summary>
        /// <param name="name">The user name.</param>
        /// <param name="login">The user login.</param>
        /// <param name="ipAdress">The user ip.</param>
        public UserIdentity(int id, string name, string login, string ipAddress)
        {
            this.ID = id;
            this.Name = name;
            this.Login = login;
            this.IPAddress = ipAddress;
        }
 
        #endregion
 
        #region Properties
 
        public int ID
        {
            get;
            private set;
        }
 
        /// <summary>
        /// Indicates if user is athenticated.
        /// </summary>
        public bool IsAuthenticated
        {
            get { return true; }
        }
 
        /// <summary>
        /// Gets the user name.
        /// </summary>
        public string Name
        {
            get;
            private set;
        }
 
        /// <summary>
        /// Gets the user login name.
        /// </summary>
        public string Login
        {
            get;
            private set;
        }
 
        /// <summary>
        /// Client Address
        /// </summary>
        public string IPAddress
        {
            get;
            set;
        }     
 
        public string AuthenticationType
        {
           get { return "ContosoSecurity"; }
        }
 
        #endregion
    }
}

Now, you need create a class that implements IPrincipal interface:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Principal;
using System.Threading;
using System.Diagnostics;
using System.Security;
 
namespace Contoso.Security.Principal
{
    /// <summary>
    /// Represents service principal.
    /// </summary>
    public class ContosoPrincipal : IPrincipal
    {
        #region Fields
 
        private WindowsPrincipal _impersonationPrincipal;
        private UserIdentity _identity;
 
        #endregion
 
        #region Constructors
 
        /// <summary>
        /// Default constructor
        /// </summary>
        public ContosoPrincipal()
        {
        }
 
        /// <summary>
        /// Initializes an instance of the <see cref="Contoso.Security.ContosoPrincipal"/> class.
        /// </summary>
        /// <param name="identity">User identity.</param>
        /// <param name="impersonatePrincipal">Principal to be impersonated.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="identity"/>is null</exception>
        public ContosoPrincipal(UserIdentity identity, WindowsPrincipal impersonatePrincipal)
        {
            if (identity == null)
                throw new ArgumentNullException("identity");
 
            _impersonationPrincipal = impersonatePrincipal;
            _identity = identity;
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// Gets the impersonate identity.
        /// </summary>
        public WindowsIdentity ImpersonationIdentity
        {
            get
            {
                WindowsIdentity identity = null;
 
                if (_impersonationPrincipal != null)
                {
                    identity = _impersonationPrincipal.Identity as WindowsIdentity;
                }
 
                return identity;
            }
        }
 
        /// <summary>
        /// Gets the identity of the current principal.
        /// </summary>
        public IIdentity Identity
        {
            get { return _identity; }
        }
 
        /// <summary>
        /// Windows principal to be used in case of impersonation.
        /// </summary>
        public WindowsPrincipal ImpersionationPrincipal
        {
            get { return _impersonationPrincipal; }
        }
 
        #endregion
 
        #region Public Methods
 
        /// <summary>
        /// Determines whether the current principal belongs to the specified role.
        /// </summary>
        /// <param name="role">The name of the role for which to check membership.</param>
        /// <returns><c>true</c> if the current principal is a member of the specified role; otherwise, <c>false</c>.</returns>
        public bool IsInRole(string role)
        {
            UserIdentity userIdentity = GetCurrent().Identity as UserIdentity;
 
            //Implement your own security and policy class to manage the access
            return new SecurityManager().CheckAuthorization(userIdentity.ID, role);
        }
 
        /// <summary>
        /// Returns the current princpal thread as a ContosoPrincipal class instance.
        /// </summary>
        public static ContosoPrincipal GetCurrent()
        {
            ContosoPrincipal principal = Thread.CurrentPrincipal as ContosoPrincipal;
 
            return principal;
        }
 
        #endregion
 
    }
}

Create the Service Authentication class, responsible for receive the UserName and Password to validate:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IdentityModel.Selectors;
using System.ServiceModel;
using System.Diagnostics;
using System.Threading;
using System.Security.Principal;
using Microsoft.Win32;
using Contoso.Security.Principal;
 
namespace Contoso.Security.ServiceModel
{
    /// <summary>
    /// Validates user credentials of the running services.
    /// </summary>
    public class ServiceAuthentication : UserNamePasswordValidator
    {
        #region Public Methods
 
        /// <summary>
        /// Validates the specified username and password across the AD.
        /// </summary>
        /// <param name="userName">User name to be checked.</param>
        /// <param name="password">Password to be checked.</param>
        public override void Validate(string userName, string password)
        {
            try
            {
                if (string.IsNullOrEmpty(userName))
                    throw new ArgumentNullException("userName");
 
                //Implement your own security and policy class to manage the access
                SecurityManager security = new SecurityManager();
 
                //throw an exception when login fails 
                UserIdentity identity = security.ValidateUser(userName, password);
 
                ContosoPrincipal principal = new ContosoPrincipal(identity, Thread.CurrentPrincipal as WindowsPrincipal);
                Thread.CurrentPrincipal = principal;
            }
            catch (Exception ex)
            {
                throw new FaultException(new FaultReason("Login Failed"));
            }
        }
 
        #endregion
 
    }
}

Service Authorization Policy:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.IdentityModel.Policy;
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Contoso.Security.Principal;
 
namespace Contoso.Security.ServiceModel
{
    /// <summary>
    /// Defines a set of rules for authorizing a user.
    /// </summary>
    public class ServiceAuthorizationPolicy : IAuthorizationPolicy
    {
        #region Constructors
 
        /// <summary>
        /// Creates a new instance of ServiceAuthorizationPolicy class
        /// </summary>
        public ServiceAuthorizationPolicy()
        {
            this.Id = Guid.NewGuid().ToString();
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// Gets a claim set that represents the issuer of the authorization policy.
        /// </summary>
        public System.IdentityModel.Claims.ClaimSet Issuer
        {
            get { return System.IdentityModel.Claims.ClaimSet.System; }
        }
 
        /// <summary>
        /// Gets a string that identifies this authorization component.
        /// </summary>
        public string Id
        {
            get;
            private set;
        }
 
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Evaluates whether a user meets the requirements for this authorization policy.
        /// </summary>
        /// <param name="evaluationContext">
        /// An System.IdentityModel.Policy.EvaluationContext that contains the claim
        /// set that the authorization policy evaluates.
        /// <param name="state">
        /// A System.Object, passed by reference that represents the custom state for this authorization policy.
        /// </param>
        /// <returns>
        /// false if the System.IdentityModel.Policy.IAuthorizationPolicy.Evaluate(System.IdentityModel.Policy.EvaluationContext,System.Object@)
        /// method for this authorization policy must be called if additional claims are added by other authorization policies 
        /// to evaluationContext; otherwise, true to state no additional evaluation is required by this authorization policy.
        /// </returns>
        public bool Evaluate(EvaluationContext evaluationContext, ref object state)
        {
                ContosoPrincipal principal = System.Threading.Thread.CurrentPrincipal as ContosoPrincipal;
 
                if (principal != null)
                {
                    UserIdentity _userIdentity = principal.Identity as UserIdentity;
 
                    _userIdentity.IPAddress = GetIPAddress();
 
                    evaluationContext.Properties["Principal"] = new ContosoPrincipal(_userIdentity, principal.ImpersionationPrincipal);
                }
 
                return true;
            }            
        }
 
        private string GetIPAddress()
        {
            OperationContext context = OperationContext.Current;
            MessageProperties messageProperties = context.IncomingMessageProperties;
            RemoteEndpointMessageProperty endpointProperty = messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
 
            return endpointProperty.Address;
        }
 
        #endregion
    }
}

To finalize server-side implementation, remains only the Web.config's setup. We will need to configure the Bindings, Behaviors and the Service's Endpoints:


  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" name="HttpsBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:01:00" sendTimeout="00:01:00">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </basicHttpBinding>
     
    </bindings>
 
    <behaviors>
      <serviceBehaviors>
        <behavior name="SecureBehavior">
          <dataContractSerializer maxItemsInObjectGraph="2147483647" />
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
          <serviceThrottling maxConcurrentCalls="5000" />
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Contoso.Security.ServiceModel.ServiceAuthentication, Contoso.Security.ServiceModel" />
          </serviceCredentials>
          <serviceAuthorization principalPermissionMode="Custom">
            <authorizationPolicies>
              <add policyType="Contoso.Security.ServiceModel.ServiceAuthorizationPolicy, Contoso.Security.ServiceModel" />
            </authorizationPolicies>
          </serviceAuthorization>
        </behavior>    
      </serviceBehaviors>
 
    </behaviors>
 
    <services>
      <service behaviorConfiguration="SecureBehavior" name="Contoso.Global.Services.Business.BrandService">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="HttpsBinding" contract="Contoso.Global.Services.Contract.IBrandContract" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="/Global/v1/Brand.svc" />
          </baseAddresses>
        </host>
      </service>
    </services>
 
    <serviceHostingEnvironment>
      <serviceActivations>
        <add relativeAddress="Global/v1/Brand.svc" service="Contoso.Global.Services.Business.BrandService" />
      </serviceActivations>
    </serviceHostingEnvironment>
  </system.serviceModel>

Your service will able to be consumed. Remember, this comunication must be over the SSL protocol and credential's informations, such username and password, must be encrypted.

.NET consumers need to set credential informations to invoke the service:



using (BrandService.BrandClient client = new BrandService.BrandClient())
{
    client.ClientCredentials.UserName.UserName = "CONTOSO_USER";
    client.ClientCredentials.UserName.Password = "BNQ1bQWap4Qh2GslOyJojFB3dLV0FGDkfGY13yTfFV3O7eHCWHL04h9P==";
 
    client.AddBrand(new Entity.Global.Brand());
}

You can invoke the service sending a message like below:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v1="http://www.contoso.com/global/v1/">
  <soapenv:Header>
    <o:Security xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <o:UsernameToken>
        <o:Username>CONTOSO_USER</o:Username>
        <o:Password>BNQ1bQWap4Qh2GslOyJojFB3dLV0FGDkfGY13yTfFV3O7eHCWHL04h9P==</o:Password>
      </o:UsernameToken>
    </o:Security>
  </soapenv:Header>
  <soapenv:Body>
    <v1:addBrand>
     ...
    </v1:addBrand>
  </soapenv:Body>
</soapenv:Envelope>

Monday, May 13, 2013

There is already a listener on IP endpoint error


The error message "There is already a listener on IP endpoint 0.0.0.0:9999" is a characteristic error on WCF services when another instance of host or other service is running on the same port, causing a conflict.

The problem cause appears to be simple, if in fact there is another service running on the same port, otherwise, this post will help you to find the real cause of the problem.

Check the symptoms bellow, everyone should occur:
  1. The process hosting the service is interrupted.
  2. Apparently the requests are been attended, but never returns, until a timeout exception is thrown.
  3. Whenever you restarts the Service Host, an exception is thrown: There is already a listener on IP endpoint 0.0.0.0:10101.  Make sure that you are not trying to use this endpoint multiple times in your application and that there are no other applications listening on this endpoint. 
  4. Probably you have Microsoft Visual Studio installed on the machine, check on Windows Process (Task Manager) if exists a process called “vsjitdebbuger.exe” running.
The priority is reestablish your services, then kill the “vsjitdebbuger.exe” process, see bellow:

Now your services should run normally, but you need identify why the service stayed unavailable.

The “vsjitdebbuger.exe” is a Visual Studio tool for debug .NET applications, and runs automatically when a process throws an unhandled exception killing the current process, capturing the instance for debug it.

This process should be terminated, because it keeps active the instance of failed process in debug mode, because of this, the service accepts requests still, however no have return and prevents that another service instance is created.

Possible Causes of the problem:
  1. Out of Memory. The service may be using much resources, make sure that you are releasing resources, disposing variables correctly and using the appropriate instance context mode.
  2. Implementations on service class Construtor. Any fail in service class constructor, without a proper treatment, rethrowing the exception, will cause a fatal fault in the service, killing him.
  3. Threads. Any fail in thread scope, without a proper treatment, rethrowing the exception, will cause a fatal fault in the service, killing him.
I don't recommend implementations in service class constructor, except in a very specific case.

In both cases, service class constructor and threads, always put your code into a try-catch block. In catch scope you need handle the exception and make the appropriated treatment, like log it. Never allow the exception to be rethrow.

Friday, May 10, 2013

Consuming WCF Services in IBM Lombardi BPM



Recently I had a critical problem in Lombardi to discover the WSDL generated from WCF (Windows Communication Foundation) - A communication framework from .NET platform.

WCF is Microsoft’s unified programming model for building service-oriented applications. It enables developers to build secure, reliable, transacted solutions that integrate across platforms and interoperate with existing investments and allows exposure services in many endpoint types, such, NET.TCP (native from .Net Applications), BasicHTTP (SOAP), REST, and others.

Eg:
       

<service behaviorConfiguration="DefaultBehavior" name="CustomerService">

  <endpoint address="" binding="basicHttpBinding" bindingConfiguration="SecureHttpBinding"  contract="Services.Contracts.ICustomer" />

  <endpoint address="" binding="netTcpBinding" bindingConfiguration="SecureTcpBinding" contract="Services.Contracts.ICustomer" />

  <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />

  <host>

    <baseAddresses>

      <add baseAddress="/Customer.svc" />

    </baseAddresses>

  </host>

</service> 


 

The above configuration will expose the service over HTTP and NET.TCP Protocols and will generate a WSDL for the "basicHttpBinding".

The problem is than the WSDL, automatically generated, contains references to SOAP 1.2, not supported by IBM Lombardi, causing error in import the service specifications.

Because of the automatically WSDL generation by WCF you can't modify or manipulate him.

The IBM's support was unable to solve this problem on your tool, and then we had to created our own solution, consisting basically intercepting the request to the service WSDL, changing him.

How does this? Our services are hosted on IIS (Microsoft Information Services), then we would have to create a HTTP Handler to do the necessary transformation on the WSDL and return him so that the Lombardi so that can interpret him, removing SOAP 1.2 references.

We create a .NET Library responsible for getting the original service's WSDL and transform him. After it must be registered as HTTP Handler in IIS:


       

public class WsdlTransformationHandler : IHttpHandler
    {
        /// <summary>
        /// Gets a value indicating whether another request can use the WsdlTransformationHandler instance.
        /// </summary>
        public bool IsReusable
        {
            get { return true; }
        }

        /// <summary>
        /// Enables processing of HTTP Web requests by the WsdlTransformationHandler.
        /// </summary>
        /// <param name="context">
        /// An System.Web.HttpContext object that provides references to the 
        /// intrinsic server objects used to service HTTP requests.
        /// </param>
        public void ProcessRequest(HttpContext context)
        {
            string url = context.Request.Url.OriginalString;
            string wsdlContent = string.Empty;

            try
            {
                url = url.Replace(".wsdlt", ".svc?wsdl");

                wsdlContent = GetWsdlFromService(url);
                wsdlContent = TransformWsdl(wsdlContent);

                Write(context, wsdlContent);
            }
            catch (Exception ex)
            {
                Write(context, FormatError(ex, new Dictionary<string, string>()
                {
                     {"URL", url}
                }));
            }
        }

        /// <summary>
        /// Request the original Wsdl from a service
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private string GetWsdlFromService(string url)
        {
            ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

            HttpWebRequest webRequest = WebRequest.Create(url) as HttpWebRequest;
            webRequest.AllowAutoRedirect = true;
            webRequest.Method = "GET";

            using (WebResponse webResponse = webRequest.GetResponse())
            {
                using (StreamReader _responseStream = new StreamReader(webResponse.GetResponseStream()))
                {
                    return _responseStream.ReadToEnd();
                }
            }
        }

        /// <summary>
        /// Transform Wsdl to soap 1.1 definitions without NetTcp references.
        /// </summary>
        /// <param name="originalWsdl"></param>
        /// <returns></returns>
        private string TransformWsdl(string originalWsdl)
        {
            XmlDocument xmlDoc = new XmlDocument();


            xmlDoc.LoadXml(originalWsdl);

            RemoveInvalidAttributes(xmlDoc);
            RemoveInvalidElements(xmlDoc);

            return xmlDoc.OuterXml;
        }

        /// <summary>
        /// Remove invalid attributes from Wsdl
        /// </summary>
        /// <param name="xmlDoc"></param>
        private void RemoveInvalidAttributes(XmlDocument xmlDoc)
        {
            XmlAttribute soap12Attribute = null;

            foreach (XmlAttribute attrItem in xmlDoc.DocumentElement.Attributes)
            {
                if (attrItem.Name.Contains("xmlns:soap12"))
                    soap12Attribute = attrItem;
            }

            if (soap12Attribute != null)
                xmlDoc.DocumentElement.Attributes.Remove(soap12Attribute);
        }

        /// <summary>
        /// Remove NetTcp elements from Wsdl
        /// </summary>
        /// <param name="xmlDoc"></param>
        private void RemoveInvalidElements(XmlDocument xmlDoc)
        {
            List<XmlElement> netTcpElements = new List<XmlElement>();

            foreach (XmlElement element in xmlDoc.DocumentElement.ChildNodes)
            {
                if (element.Name.Contains("wsdl:service"))
                    RemoveInvalidPorts(element);

                foreach (XmlAttribute attribute in element.Attributes)
                {
                    if (attribute.Value.ToUpper().Contains("NETTCP"))
                    {
                        netTcpElements.Add(element);
                        break;
                    }
                }
            }

            foreach (XmlElement netTcpElement in netTcpElements)
                xmlDoc.DocumentElement.RemoveChild(netTcpElement);
        }

        /// <summary>
        /// Remove NetTcp port definition from Wsdl
        /// </summary>
        /// <param name="xmlElement"></param>
        private void RemoveInvalidPorts(XmlElement xmlElement)
        {
            List<XmlElement> netTcpElements = new List<XmlElement>();

            foreach (XmlElement portElement in xmlElement.ChildNodes)
            {
                foreach (XmlAttribute attribute in portElement.Attributes)
                {
                    if (attribute.Value.ToUpper().Contains("NETTCP"))
                    {
                        netTcpElements.Add(portElement);
                        break;
                    }
                }
            }

            foreach (XmlElement netTcpElement in netTcpElements)
                xmlElement.RemoveChild(netTcpElement);
        }

        /// <summary>
        /// Write the transformed Wsdl to Response
        /// </summary>
        /// <param name="context"></param>
        /// <param name="content"></param>
        private void Write(HttpContext context, string content)
        {
            context.Response.Clear();
            context.Response.ContentType = "text/plain";
            context.Response.ContentEncoding = Encoding.UTF8;
            context.Response.ContentType = "text/plain; charset=utf-8";

            context.Response.Write(content);
        }

        /// <summary>
        /// Format exception message to display
        /// </summary>
        /// <param name="ex"></param>
        /// <param name="additionalData"></param>
        /// <returns></returns>
        private string FormatError(Exception ex, Dictionary<string, string> additionalData = null)
        {
            StringBuilder _sb = new StringBuilder();

            _sb.AppendLine("ERROR");
            _sb.AppendLine();

            _sb.AppendLine("Message: " + ex.Message);
            _sb.AppendLine("Source: " + ex.Source);
            _sb.AppendLine("TargetSite: " + ex.TargetSite.Name);
            _sb.AppendLine("StackTrace: " + ex.StackTrace);
            _sb.AppendLine();
            _sb.AppendLine("Additional Data");

            if (additionalData != null)
            {
                foreach (string key in additionalData.Keys)
                {
                    _sb.AppendLine(key + ": " + additionalData[key]);
                }
            }

            return _sb.ToString();
        }
    }


 


Observe in the code, which in addition to removing the SOAP 1.2 references, also we removed the Net.TCP endpoint to not duplicate the operations in Lombardi.

The above implementation must be compiled on .NET Framework 2.0 to be registered at the GAC and used in IIS.

Configuring IIS:

1 - Register your assembly in GAC (Global Assembly Cache), move the DLL to into the folder (C:\Windows\Assembly) or use GACUTIL tool.

2 - On IIS Console, open the Handler Mappings Settings







3 - On the upper right, select the "Add Managed Handler" action



4 - Into "Edit Managed Handler" window, make the following settings:


Type "*.wsdlt" into request path box, this is the extensions that your handler will intecept. Choose your assembly on Type dropdown box, it will apear after it put into the GAC.


Click on "Request Restrictions" button and make the above settings.

5 - Restart the IIS.


It's Done!

Now, you must change the call for your service in Lombardi, making pass to the Handler, eg:

Original Request: http://localhost/Services/Customer.svc?wsdl
New Request: http://localhost/Services/Customer.wsdlt