Occassional posts about VoIP, SIP, WebRTC and Bitcoin.
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:
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.
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; } } }
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.
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 plaese post a comment if you are successful in connecting to the provisioning service.
Aaron