Connecting to Workday WSDL with C#

118 Views Asked by At

I'm trying to get connected to a Workday instance via their WSDL endpoint with C#, and I keep getting "System.ServiceModel.FaultException: 'invalid username or password'" back for my call(s). The other side (I'm working through a number of layers to get to the actual implementer of WD) insists that the user is setup properly. Right now, I'm just trying to get a console app to pull a list of orders (since I want to work with orders more extensively later). So, that's what this code is trying to do. It is a .NET 4.8 application, so that's the target for my solution.

From Program.cs:

    internal class Program
    {
        static void Main( string[] args )
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            using( var clt = CreateWorkdayClient() )
            {
                var request = new Get_Purchase_Orders_RequestType()
                {
                    version = "v42.0",
                    Item = new Purchase_Order_Request_CriteriaType() { },
                    Response_Filter = new Response_FilterType() { Page = 1, PageSpecified = true, Count = 50, CountSpecified = true },
                    Response_Group = new Purchase_Order_Response_GroupType() { Include_Reference = true, Include_ReferenceSpecified = true },
                };

                var response = clt.Get_Purchase_Orders( request );

                Debug.WriteLine( JsonConvert.SerializeObject( response, Formatting.Indented, new JsonConverter[] { new StringEnumConverter() } ) );

            }
        }

        public static Resource_ManagementPortClient CreateWorkdayClient()
        {
            SecurityBindingElement sb = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
            sb.IncludeTimestamp = false;
            const int lim = Int32.MaxValue;
            var timeout = TimeSpan.FromMinutes( 2 );

            var cb = new CustomBinding(
                sb,
                new TextMessageEncodingBindingElement( MessageVersion.Soap11, Encoding.UTF8 )
                {
                    ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas
                    {
                        MaxDepth = lim,
                        MaxStringContentLength = lim,
                        MaxArrayLength = lim,
                        MaxBytesPerRead = lim,
                        MaxNameTableCharCount = lim
                    }
                },
                new HttpsTransportBindingElement
                {
                    MaxBufferPoolSize = lim,
                    MaxReceivedMessageSize = lim,
                    MaxBufferSize = lim,
                    Realm = string.Empty
                } )
            {
                SendTimeout = timeout,
                ReceiveTimeout = timeout
            };

            var client = new Resource_ManagementPortClient( cb, new EndpointAddress( "https://wd2-impl-services1.workday.com/ccx/service/tenant/Resource_Management" ) );

            client.ClientCredentials.UserName.UserName = "user@tenant";
            client.ClientCredentials.UserName.Password = "password";

            return client;
        }
    }

From app.config:

    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="Resource_ManagementBinding">
                    <security mode="Transport" />
                </binding>
                <binding name="Resource_ManagementBinding1" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://wd2-impl-services1.workday.com/ccx/service/tenant/Resource_Management"
                binding="basicHttpBinding" bindingConfiguration="Resource_ManagementBinding"
                contract="TestApp.Resource_ManagementPort" name="Resource_Management" />
        </client>
    </system.serviceModel>

Anyone see anything that I could/would/should be doing differently?

1

There are 1 best solutions below

0
On

Try using IEndpointBehavior and IMessageInspector interfaces to send the credentials in the request security header:

public class MessageBehavior : IEndpointMessageBehavior
{
    private string _username;
    private string _password;
    public MessageBehavior(string username, string password)
    {
        _username = username;
        _password = password;
    }

    void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters){ }

    void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new MessageInspector(_username, _password));
    }

    void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }

    void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { }
}

public class MessageInspector : IClientMessageInspector
{
    private string _username;
    private string _password;
    public MessageInspector(string username, string password)
    {
        _username = username;
        _password = password;
    }

    object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        string headerText = "<wsse:UsernameToken xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">" +
                    $"<wsse:Username>{_username}</wsse:Username>" +
                    "<wsse:Password Session=\"false\" Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">" +
                    $"{_password}</wsse:Password>" +
                "</wsse:UsernameToken>";

        XmlDocument headerDoc = new XmlDocument();
        headerDoc.LoadXml(headerText);
        XmlElement headerElement = headerDoc.DocumentElement;

        MessageHeader myHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", headerElement, false);

        request.Headers.Add(myHeader);
    }
}

Then create your client like this:

MessageBehavior securityBehavior = new MessageBehavior(username, password);
var cFactory = new ChannelFactory<Resource_ManagementPortClient>();
cFactory.Endpoint.EndpointBehaviors.Add(securityBehavior);

Resource_ManagementPortClient client = cFactory.CreateChannel(new EndpointAddress(yourAddress))