SIPSorcery SOAP Provisioning Service

One thing I’ve been meaning to do for a while is a post on how to programatically connect to the SIP Sorcery provisioning service. The service is exposed over a SOAP 1.1 interface. This post provides a brief C# code example which demonstrates how to connect using WCF. At some stage I’d also like to provide a javascript based code sample using jquery or a similar library. That may motivate someone to write an alternative user interface to the Silverlight one and appease the people who dislike it for whatever reason.

The full source code for the C# sample can be dowloaded from here.

The SOAP standard does not include any mechanisms for authentication. There is a web service extension available that includes a mechanism in the form of WS-Security specifications. The problem is that the specification is quite bulky and the classes provided by WCF to make it easy to use are not supported by Silverlight.

An alternative to using a SOAP authentication mechanism is to use an HTTP one such as OAuth. The HTTP approach is appealing for at least two reasons: it’s light weight and easy to implement compared to WS-Security and it can be used for different application protocols which means if/when a REST provisioning endpoint is added the same authentication mechanism can be used.

Incorporating REST and OAuth into the SIPSorcery provisioning interface is a little way down the road and at the moment the authentication mechanism used has been designed to work easily with the Silverlight client. The existing mechanism is SOAP specific and involves two steps:

  • Step 1 – Call the Login method and if successful a token will be returned. The token is a 384 bit random number returned as a 96 byte string.
  • Step 2 – In all subsequent provisioning SOAP requests the token needs to be included in the SOAP header in an authid element.
  • Each token is only good for one hour or until it is passed to the Logout method. While the token is current it’s a critical piece of information that allows full access to a specific user’s account as such it should never be transmitted over an unencrypted connection. The SIPSorcery provisioning interface is only exposed via HTTPS which means there is no opportunity to send a token on anything but an encrypted connection.

    Below is an example of a SOAP envelope that includes the authentication token in the authid SOAP header. The SOAP request in this case is GetCustomer and the username parameter in the body is actually the username of the customer record being requested and nothing to do with authentication.

    <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
      <s:Header>
        <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://www.sipsorcery.com/provisioning/IProvisioningService/GetCustomer</Action>
        <authid>8c441993d8df81d42bb5a6757f4370a504d972a21c5812166a3cc36292ccb53ab1cac2750e74815707b37ddea21cdae7</authid>
      </s:Header>
      <s:Body>
        <GetCustomer xmlns="http://www.sipsorcery.com/provisioning">
          <username>username</username>
        </GetCustomer>
      </s:Body>
    </s:Envelope>
    

    With authentication out of the way on to the C# sample.

  • Step 1 – Create a new C# console application in Visual Studio
  • Step 2 – In the Solution Explorer right click on the References folder and choose Add Service Reference.

    addserviceref

  • Step 3 – Set the service address to https://www.sipsorcery.com/provisioning.svc. To work properly with the sample code below set the namespace to Provisioning.

    setserviceref

  • Step 4 – Add the classes below to your console application. These classes are required to add a custom SOAP authentication header to each request sent to the service.

    (Code sample updated 7 Feb 2010 to adjust for a new security header format.)

    public class SIPSorceryProvisioningBehavior : IEndpointBehavior {
    
        private string m_authId;
    
        public SIPSorceryProvisioningBehavior(string authId) {
            m_authId = authId;
        }
    
        public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime clientRuntime) {
            clientRuntime.MessageInspectors.Add(new SIPSorceryProvisioningMessageInspector(m_authId));
        }
    
        public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) {   return; }
        public void Validate(ServiceEndpoint serviceEndpoint) { return; }
        public void AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection bindingParameters) { return; }
    }
    
    public class SIPSorceryProvisioningMessageInspector : IClientMessageInspector {
            
        private string m_authId;
    
        public SIPSorceryProvisioningMessageInspector(string authId) {
            m_authId = authId;
        }
    
        public object BeforeSendRequest(ref Message request, IClientChannel channel) {
            request.Headers.Add(new SIPSorcerySecurityHeader(m_authId));
            return null;
        }
    
        public void AfterReceiveReply(ref Message reply, object correlationState) { }
    }
    
     public class SIPSorcerySecurityHeader : MessageHeader
        {
            private const string SECURITY_NAMESPACE = "http://www.sipsorcery.com/security";
            private const string SECURITY_HEADER_NAME = "Security";
            private const string SECURITY_PREFIX = "sssec";
            private const string AUTHID_ELEMENT_NAME = "AuthID";
    
            private static ILog logger = AppState.logger;
    
            public string AuthID;
    
            public override bool MustUnderstand { get { return true; } }
            public override string Name { get { return SECURITY_HEADER_NAME; } }
            public override string Namespace { get { return SECURITY_NAMESPACE; } }
    
            public SIPSorcerySecurityHeader(string authID)
            {
                AuthID = authID;
            }
    
            protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
            {
                writer.WriteStartElement(SECURITY_PREFIX, AUTHID_ELEMENT_NAME, SECURITY_NAMESPACE);
                writer.WriteString(AuthID);
                writer.WriteEndElement();
            }
    
            protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
            {
                writer.WriteStartElement(SECURITY_PREFIX, this.Name, this.Namespace);
            }
    
            public static SIPSorcerySecurityHeader ParseHeader(OperationContext context)
            {
                try
                {
                    int headerIndex = context.IncomingMessageHeaders.FindHeader(SECURITY_HEADER_NAME, SECURITY_NAMESPACE);
                    if (headerIndex != -1)
                    {
                        XmlDictionaryReader reader = context.IncomingMessageHeaders.GetReaderAtHeader(headerIndex);
    
                        if (reader.IsStartElement(SECURITY_HEADER_NAME, SECURITY_NAMESPACE))
                        {
                            reader.ReadStartElement();
                            reader.MoveToContent();
    
                            if (reader.IsStartElement(AUTHID_ELEMENT_NAME, SECURITY_NAMESPACE))
                            {
                                string authID = reader.ReadElementContentAsString();
                                return new SIPSorcerySecurityHeader(authID);
                            }
                        }
                    }
                     return null;
                }
                catch (Exception excp)
                {
                    logger.Error("Exception SIPSorcerySecurityHeader ParseHeader. " + excp.Message);
                    throw;
                }
            }
        }
    
  • Step 5 – Now everything is ready to use the service. The code sample below shows how to do that.
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;
    using System.Text;
    using System.Xml;
    
    namespace SIPSorcerySOAPConsole {
    
        class Program {
            static void Main(string[] args) {
                try {
                    Console.WriteLine("Starting SIP Sorcery SOAP Console");
    
                    // First step is to login and acquire an authid security token.
                    Provisioning.ProvisioningServiceClient client = new Provisioning.ProvisioningServiceClient();
                    string authID = client.Login("username", "password");
                    Console.WriteLine("authid=" + authID + ".");
    
                    // Once the security token has been acquired it needs to be set on all subsequent requests.
                    Provisioning.ProvisioningServiceClient authenticatedClient = new Provisioning.ProvisioningServiceClient();
                    authenticatedClient.ChannelFactory.Endpoint.Behaviors.Add(new SIPSorceryProvisioningBehavior(authID));
                    Customer customer = authenticatedClient.GetCustomer("username");
                    Console.WriteLine("First Name=" + customer.FirstName + ".");
                }
                catch (Exception excp) {
                    Console.WriteLine("Exception Main. " + excp.Message);
                }
                finally {
                    Console.WriteLine("finished, press any key to exit...");
                    Console.ReadLine();
                }
            }
        }
    }
    
  • If you’ve made it this far the next question you’ll have is “now that I can connect what can I do with the it?”. For the answer the best place to go is the source.

  • The interface is in IProvisioningService.
  • All but one of the classes for the returned objects are contained in the SIPSorcery.SIP.App assembly and the SIPAssets folder. The SIPAccount class for example.
  • The exception is the Customer class which is contained in the SIPSorcery.CRM assembly.
  • Finally we would request that the interface is used sensibly. It can be used to create new sipsorcery accounts (when they are enabled again) and as they are in tight supply there may be a temptation to automate their creation. At this point we do request users stick to one account each and while we are very reluctant to suspend or remove accounts and only do so as a last resort if one user’s actions have a large impact on everyone else we will do so.

    Enjoy and plese post a comment if you are successful in connecting to the provisioning service.

    Aaron

    1. Tuketu’s avatar

      Very cool stuff. Love the exposure of sipsorcery as a web service. Now if I can find some time I may be tempted to write a client.

      Reply

    2. Hori’s avatar

      Hi there,

      I downloaded your code as I am very interested in the SIP stuff. I cannot go any further as I don’t have an account setup in your cloud. I have applied thru sipsorcery.com but didn’t receive the confirmation email yet.

      Is the system open for general public, invitation only or how? I think I might have had 2 attempts (usernames hgoller and horatiu), one of them from a mac computer. I thought I had an issue with Silverlight on the mac.

      Thanks
      Hori

      Reply

      1. sipsorcery’s avatar

        Yes it’s open for all comers. The confirmation emails “should” have been sent. In the meantime I’ve manually activated your hgoller account.

        Reply

      2. A. Shore’s avatar

        Any hope for a Javascript example client (SOAP or JSON)? (I’m just not into C) Thanks a lot. AS

        Reply

        1. sipsorcery’s avatar

          Last time I worked with SOAP and javascript it was pretty painful and was actually a big motivation for using Silverlight. I am still planning on introducing a JSON REST interface (that uses a JSON format) for sipsorcery and once i get that done I’ll see about putting together a rudimentary javascript sample for it.

          Reply

        2. Sergey’s avatar

          Please replace rd00155d3167d9:20001 with http://www.sipsorcery.com in your service definition.
          https://rd00155d3167d9:20001/Provisioning.svc?wsdl
          I can’t add reference because rd00155d3167d9:20001 doesn’t exist.

          Thanks,
          Sergey

          Reply

          1. sipsorcery’s avatar

            Unforutnately the WSDL is generated automatically by IIS so I can’t edit it. The sipsorcery web site was recently moved to Windows Azure and a quick search around shows there are some issues generating references for services hosted on Windows Azure http://code.msdn.microsoft.com/wcfazure/Wiki/View.aspx?title=KnownIssues.

            I’ve updated the SOAP client sample at http://sipsorcery.codeplex.com/releases/view/40318 and it contains the latest service reference.

            Reply

    Reply

    Your email address will not be published. Required fields are marked *