1

I am having trouble getting WCF binding to work with Blackboard Java Web Services API.

(Simple answer would be if anyone has got this working could you please post a working binding for WCF to Blackboard)

I have spent hours trying different configurations and custom coded bindings.

Some unsuccessful attempts:

calling-a-ws-security-java-web-service-with-c-sharp-client

wcf-client-with-ws-security 12-common-wcf-interop-confusions

configure-wcf-for-ws-security-with-username-over-https

wcf-client-connecting-to-java-soap-web-service-using-ws-security

ClearUsernameBinding

There are many more to do with JAVA and WS-Security with WCF but I wont go on.

It seems that every time I get one thing working another breaks. Now I feel like I am going around in circles and just making myself even more confused.

As my first test what I am trying to do is simple Initialize the Context object and Login using an Admin test user account with a WCF proxy.

Blackboard Doc ContextWS

To make sure that all of this worked I first I downloaded the sample code for .Net WSE 2.0 and tested that, it worked perfectly.

Now when I use WCF and binding I cannot get this same behaviour.

First the Successful exchange with very old WSE 2.0 ===================================

WSE 2.0 ContextWS Initialization

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <soap:Header>
        <wsa:Action>initialize</wsa:Action>
        <wsa:MessageID>uuid:b975e989-a4ce-4e1e-abd6-500945346c40</wsa:MessageID>
        <wsa:ReplyTo>
            <wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address>
        </wsa:ReplyTo>
        <wsa:To>https://Blackboard.Server.Name/webapps/ws/services/Context.WS</wsa:To>
        <wsse:Security soap:mustUnderstand="1">
            <wsu:Timestamp wsu:Id="Timestamp-47d0d017-4fd1-46c2-b1b4-2431402cf847">
                <wsu:Created>2015-07-16T04:58:02Z</wsu:Created>
                <wsu:Expires>2015-07-16T05:03:02Z</wsu:Expires>
            </wsu:Timestamp>
            <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-1b71e23a-2d84-40a5-9509-b75902ec8b76">
                <wsse:Username>session</wsse:Username>
                <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">nosession</wsse:Password>
                <wsse:Nonce>lAW2qXrXZ1maNNkCEzlHGA==</wsse:Nonce>
                <wsu:Created>2015-07-16T04:58:02Z</wsu:Created>
            </wsse:UsernameToken>
        </wsse:Security>
    </soap:Header>
    <soap:Body />
</soap:Envelope>

WSE 2.0 ContextWS Initialization Success Response

<?xml version='1.0' encoding='utf-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <ns:initializeResponse xmlns:ns="http://context.ws.blackboard">
            <ns:return>c2762f357bbc42a4a88d33e4e42486b8</ns:return>
        </ns:initializeResponse>
    </soapenv:Body>
</soapenv:Envelope>

WSE 2.0 ContextWS Login Request

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <soap:Header>
        <wsa:Action>login</wsa:Action>
        <wsa:MessageID>uuid:a823128b-efb4-49e1-87d9-fd35167f0bfc</wsa:MessageID>
        <wsa:ReplyTo>
            <wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address>
        </wsa:ReplyTo>
        <wsa:To>https://Blackboard.Server.Name/webapps/ws/services/Context.WS</wsa:To>
        <wsse:Security soap:mustUnderstand="1">
            <wsu:Timestamp wsu:Id="Timestamp-c38daf19-6b39-4391-a3f8-bcc030064a3e">
                <wsu:Created>2015-07-16T04:58:15Z</wsu:Created>
                <wsu:Expires>2015-07-16T05:03:15Z</wsu:Expires>
            </wsu:Timestamp>
            <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-65948746-e616-436a-85f4-d2e1023e39be">
                <wsse:Username>session</wsse:Username>
                <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">c2762f357bbc42a4a88d33e4e42486b8</wsse:Password>
                <wsse:Nonce>T0xs8aiaiODMK3sfKgDQtg==</wsse:Nonce>
                <wsu:Created>2015-07-16T04:58:15Z</wsu:Created>
            </wsse:UsernameToken>
        </wsse:Security>
    </soap:Header>
    <soap:Body>
        <login xmlns="http://context.ws.blackboard">
            <userid>test_admin</userid>
            <password>TestPassword</password>
            <clientVendorId>TestClient</clientVendorId>
            <clientProgramId>TestPOC</clientProgramId>
            <loginExtraInfo xsi:nil="true" />
            <expectedLifeSeconds>10000000</expectedLifeSeconds>
        </login>
    </soap:Body>
</soap:Envelope>

WSE 2.0 ContextWS Login Success Response

<?xml version='1.0' encoding='utf-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <ns:loginResponse xmlns:ns="http://context.ws.blackboard">
            <ns:return>true</ns:return>
        </ns:loginResponse>
    </soapenv:Body>
</soapenv:Envelope>

===================================

So I know that this works to our environment and I know the user can login.

Using WCF I am able to get the Initialization working but then it looses the session. It does not put the Returned Session ID into the Password field for the next message. I have tried to do this manually of course; but I get an error stating the Password field is read only.

Now for my WCF Configuration and code that has gotten me closest to the above communication.

WCF App.Config Binding

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    </system.serviceModel>
        <bindings>
          <customBinding>
                <binding name="WCFSoapInteropJavaWS"  closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"  >
                  <textMessageEncoding messageVersion="Soap11" writeEncoding="utf-8" />
                  <security authenticationMode="UserNameOverTransport" enableUnsecuredResponse="true" allowSerializedSigningTokenOnReply="true"
                            messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
                            includeTimestamp="true" allowInsecureTransport="true" canRenewSecurityContextToken="false" >
                  </security>
                  <httpsTransport  authenticationScheme="Anonymous"  />
                </binding>

              </customBinding>
         </bindings>

        <client>

            <endpoint 
                address="https://Blackboard.Server.Name:443/webapps/ws/services/Context.WS"
                binding="customBinding" bindingConfiguration="WCFSoapInteropJavaWS"
                contract="ContextWS.ContextWSPortType" name="Context.WCFSoapInteropJavaWS" />

        </client>

    </system.serviceModel>
</configuration>

WCF C# code

 public bool testWrapper(String userId, String userPassword){


             try
             {
                 context = new ContextWrapper("Context.WCFSoapInteropJavaWS");

                 context.ClientCredentials.UserName.UserName = "session";
                 context.ClientCredentials.UserName.Password = "nosession";

                 context.initialize();

                 //context.ClientCredentials.UserName.Password = "886d935527944f94a3526288e39a555e";  // SessionGUID_HERE Throws a Read Only Error for Pasword

                 bool retval = context.login(userId, userPassword, vendorId, programId, null, expectedLife);

                 return retval;
             }
             catch (System.Exception e)
             {
                 lastError = e;
                 return false;
             }
        }

This is what the SOAP communication looks like.

WCF ContextWS Initialization Request

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <s:Header>
        <VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPo+FmveflwUtMgSATRu3Ht9EAAAAAmYVJsX+bhUeYcTDsFqFktkqe8xmMiA1MpXouaouXgJwACQAA</VsDebuggerCausalityData>
        <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <u:Timestamp u:Id="_0">
                <u:Created>2015-07-16T07:15:05.109Z</u:Created>
                <u:Expires>2015-07-16T07:20:05.109Z</u:Expires>
            </u:Timestamp>
            <o:UsernameToken u:Id="uuid-1237f56c-7c68-4d40-a756-7ff2c19a3235-1">
                <o:Username>session</o:Username>
                <o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">nosession</o:Password>
            </o:UsernameToken>
        </o:Security>
    </s:Header>
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
</s:Envelope>

WCF ContextWS Initialization Success Response

<?xml version='1.0' encoding='utf-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <ns:initializeResponse xmlns:ns="http://context.ws.blackboard">
            <ns:return>886d935527944f94a3526288e39a555e</ns:return>
        </ns:initializeResponse>
    </soapenv:Body>
</soapenv:Envelope>

WCF ContextWS Login Request

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <s:Header>
        <VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPo+JmveflwUtMgSATRu3Ht9EAAAAAmYVJsX+bhUeYcTDsFqFktkqe8xmMiA1MpXouaouXgJwACQAA</VsDebuggerCausalityData>
        <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <u:Timestamp u:Id="_0">
                <u:Created>2015-07-16T07:15:14.033Z</u:Created>
                <u:Expires>2015-07-16T07:20:14.033Z</u:Expires>
            </u:Timestamp>
            <o:UsernameToken u:Id="uuid-1237f56c-7c68-4d40-a756-7ff2c19a3235-1">
                <o:Username>session</o:Username>
                <o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">nosession</o:Password>
            </o:UsernameToken>
        </o:Security>
    </s:Header>
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <login xmlns="http://context.ws.blackboard">
            <userid>Test_admin</userid>
            <password>TestPassword</password>
            <clientVendorId>TestClient</clientVendorId>
            <clientProgramId>TestPOC</clientProgramId>
            <loginExtraInfo xsi:nil="true"/>
            <expectedLifeSeconds>10000000</expectedLifeSeconds>
        </login>
    </s:Body>
</s:Envelope>

WCF ContextWS Login Failed Response

<?xml version='1.0' encoding='utf-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
        <soapenv:Fault>
            <faultcode>soapenv:Server</faultcode>
            <faultstring>[WSFW001]Invalid session</faultstring>
            <detail />
        </soapenv:Fault>
    </soapenv:Body>
</soapenv:Envelope>

As you can see the session return id has not been added to the password field on the login request so there is a "Invalid Session:

It all seemed to be going so well.

In short if anyone knows how to achieve a binding from a WCF Client to the Blackboard Java Webservice API and example would be fantastic. Other wise I am hoping that someone else that knows more about WCF Bindings to Java than I do might be able to take a look at the above see where I am going wrong.

Any help anyone can give me to get this working would be much appreciated so thanks. I really am hoping it is just something silly that I am missing.

Sorry for such a long detailed question.

INK
  • 13
  • 5
  • Have to ask the question: I know WSE2 is really old, but it works. Why not use that instead of WCF? This post seems to suggest that too http://stackoverflow.com/questions/5833539/how-to-add-security-header-to-a-soap-message – tom redfern Jul 17 '15 at 09:08
  • I have called Java services from WCF before and I needed to add an array of SoapHeader to the code generated from the service metadata. Then I had to manually populate the header XML as expected by the service – tom redfern Jul 17 '15 at 09:14
  • Thank you Tom I have seen this post and a number of others that have also given up, and I am nearly at that point, but I keep coming across articles of people saying they have it working and that it is simple changes to the behaviors. I feel that getting the new WCF working now will mean less work in the future when Blackboard does change its web services. – INK Jul 21 '15 at 23:44
  • I came across these two links [Wcf WS-Security server](http://stackoverflow.com/questions/18489788/wcf-ws-security-server) and [Incoming message does not contain required Security header](http://forums.edugarage.com/forums/p/1849/8487.aspx) the latter seems to have solve the problem but I can not find his example code any more. – INK Jul 22 '15 at 05:24

1 Answers1

0

Thanks to heaps of reading and some useful samples I was able to get this working. Blackboard with WCF.

Thanks go to both: Ajadex Lopez

http://www.isyourcode.com/2010/08/attaching-oasis-username-tokens-headers.html

Johnny Lockhart "Incoming message does not contain required Security header"

BB is buggie so you might do best to search the forum for WCF to find the post

Sample class

using System;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel;
using System.Xml;
using System.Security.Cryptography;
using System.Text;


namespace BBWcfWrapper
{


    /// <summary>
    /// Coupled with the additional classes below, allows for injecting the WS-Security header into a WCF Service call without requiring SSL on the server.
    /// </summary>
    /// <remarks>http://isyourcode.blogspot.com/2010/08/attaching-oasis-username-tokens-headers.html</remarks>
    public class BBWSSecurityBehavior : IEndpointBehavior
    {
        public MessageInspector MessageInspector { get; set; }

        public BBWSSecurityBehavior(MessageInspector messageInspector)
        {
            MessageInspector = messageInspector;
        }

        public void Validate(ServiceEndpoint endpoint)
        { }
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        { }
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            if (this.MessageInspector == null) throw new InvalidOperationException("Caller must supply ClientInspector.");
            clientRuntime.MessageInspectors.Add(MessageInspector);
        }

    }

    public class MessageInspector : IClientMessageInspector
    {
        public MessageHeader[] Headers { get; set; }
        public MessageInspector(params MessageHeader[] headers)
        {
            Headers = headers;
        }
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            if (Headers != null)
            {
                for (int i = Headers.Length - 1; i >= 0; i--)
                {
                    request.Headers.Insert(0, Headers[i]);
                }
            }

            return request;
        }
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
        }
    }


    public class SecurityHeader : MessageHeader
    {

        public string SystemUser { get; set; }
        public string SystemPassword { get; set; }
        public SecurityHeader(string systemUser, string systemPassword)
        {
            SystemUser = systemUser;
            SystemPassword = systemPassword;
        }
        public override string Name
        {
            get { return "Security"; }
        }
        public override string Namespace
        {
            get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
        }

        protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            writer.WriteStartElement("wsse", Name, Namespace);
            writer.WriteXmlnsAttribute("wsse", Namespace);
            writer.WriteAttributeString("soap", "mustUnderstand", Namespace, "1");
        }

        protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            WriteHeader(writer);
        }

        private void WriteHeader(XmlDictionaryWriter writer)
        {
            var createDate = DateTime.Now;

            //Start Parent Elements 
            writer.WriteStartElement("wsu","Timestamp","http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
            writer.WriteAttributeString("wsu","id","http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd","Timestamp-6557466");

            #region Start Child Elements

            writer.WriteStartElement("wsu", "Created", XmlConvert.ToString(createDate, "yyyy-MM-ddTHH:mm:sszzzzzz"));
            writer.WriteEndElement();   //End Created

            writer.WriteStartElement("wsu", "Expires", XmlConvert.ToString(createDate.AddDays(1), "yyyy-MM-ddTHH:mm:sszzzzzz"));
            writer.WriteEndElement();   //End Expires

            #endregion

            writer.WriteEndElement();   //End Timestamp

            //Start Parent Elements 
            writer.WriteStartElement("wsse", "UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
            writer.WriteXmlnsAttribute("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

            #region Start Child Elements

            writer.WriteStartElement("wsse", "Username", null);
            writer.WriteString(SystemUser);
            writer.WriteEndElement();//End Username 

            writer.WriteStartElement("wsse", "Password", null);
            writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
            writer.WriteString(SystemPassword);
            writer.WriteEndElement();//End Password 

            // unique Nonce value - encode with SHA-1 for 'randomness'
            // in theory the nonce could just be the GUID by itself
            // This is used to stop Replay attacks 
            writer.WriteStartElement("wsse", "Nonce", null);
            writer.WriteString(GetSHA1String(Guid.NewGuid().ToString()));
            writer.WriteEndElement();//Nonce 

            writer.WriteStartElement("wsu", "Created", null);
            writer.WriteString(XmlConvert.ToString(createDate, "yyyy-MM-ddTHH:mm:sszzzzzz"));
            writer.WriteEndElement();   //End Created

            #endregion

            writer.WriteEndElement();//End UsernameToken
            writer.Flush(); 

        }

        protected string GetSHA1String(string phrase)
        {
            SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
            byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
            return Convert.ToBase64String(hashedDataBytes);
        }

    }

}

Example Use

  calendar = new CalendarWrapper("Calendar.BB_WSSecurity_Binding");

        //This adds a custom security Headder for WCF and Java WS-Security Interop
        calendar.Endpoint.Behaviors.Add(new BBWSSecurityBehavior(new MessageInspector(BbWsAuth.SecurityHeader)));

        calendar.initializeCalendarWS(false);

Simple Wrapper class

    public class CalendarWrapper : CalendarWSPortTypeClient
{
    public CalendarWrapper() : base() { }

    public CalendarWrapper(string endpointConfigurationName) : base(endpointConfigurationName) { }
    public CalendarWrapper(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { }
}

Config

 <bindings>

    <basicHttpsBinding>
      <binding name="BB_WSSecurity_Binding" messageEncoding="Text" textEncoding="utf-8" maxReceivedMessageSize="4000000" >
        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
          maxBytesPerRead="4096" maxNameTableCharCount="16384" />
      </binding>
    </basicHttpsBinding>

  </bindings>

  <client>

  <endpoint address="https://xxxxxxxxxx/webapps/ws/services/Calendar.WS"
           binding="basicHttpsBinding" bindingConfiguration="BB_WSSecurity_Binding"
            contract="CalendarWS.CalendarWSPortType" name="Calendar.BB_WSSecurity_Binding"  />
INK
  • 13
  • 5