1

I'm trying to connect to Workday API (SOAP) from Azure Functions in .NET Core, but I'm encountering some issues with authentication.

From what I've seen, the problem comes from using authentication over transport, so I tried to use custom bindings but looks like .NET Core doesn't support it yet.

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 XmlDictionaryReaderQuotas
        {
            MaxDepth = lim,
            MaxStringContentLength = lim,
            MaxArrayLength = lim,
            MaxBytesPerRead = lim,
            MaxNameTableCharCount = lim
        }
    },
    new HttpsTransportBindingElement
    {
        MaxReceivedMessageSize = lim,
        MaxBufferSize = lim,

    })
{
    SendTimeout = timeout,
    ReceiveTimeout = timeout
};

var client = new WorkDayAbsenceServiceReference.Absence_ManagementPortClient(cb, new EndpointAddress("https://wd3-impl-services1.workday.com/ccx/service/mytenant/Absence_Management/v33.0"));
client.ClientCredentials.UserName.UserName = "myusername";
client.ClientCredentials.UserName.Password = "mypassword";

var headers = new WorkDayAbsenceServiceReference.Workday_Common_HeaderType() {};

var requestType = new WorkDayAbsenceServiceReference.Get_Time_Off_Plan_Balances_RequestType()
{
    Request_Criteria = new WorkDayAbsenceServiceReference.Time_Off_Plan_Balance_Request_CriteriaType()
    {
        Employee_Reference = new WorkDayAbsenceServiceReference.WorkerObjectType()
        {
            ID = new WorkDayAbsenceServiceReference.WorkerObjectIDType[]
            {
                new WorkDayAbsenceServiceReference.WorkerObjectIDType
                {
                    type = "Employee_ID",
                    Value = "_0000028"
                }
            }
        }
    }
};


var test = await client.Get_Time_Off_Plan_BalancesAsync(headers, requestType);

And the error code I get :

System.Private.CoreLib: Exception while executing function: GetDaysOff. System.Private.ServiceModel: TransportSecurityBindingElement.BuildChannelFactoryCore is not supported.

2 Answers2

1

Thank you very much Mohit Verma, after spending few hours to build the SOAP Envelope, I was be able to get date from the Workday API.

There is my code using WSS Password Text security for authentication :

string uriBase = Environment.GetEnvironmentVariable("UriBaseWorkdayAbsenceManagement");
        string user = Environment.GetEnvironmentVariable("WorkdayUsername");
        string pass = Environment.GetEnvironmentVariable("WorkdayPassword");

        string xml;
        XmlWriterSettings settings = new XmlWriterSettings();
        settings.OmitXmlDeclaration = true;
        using (MemoryStream ms = new MemoryStream())
        {
            using (XmlWriter writer = XmlWriter.Create(ms, settings))
            {
                XmlSerializerNamespaces names = new XmlSerializerNamespaces();
                names.Add("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
                names.Add("bsvc", "urn:com.workday/bsvc");
                XmlSerializer cs = new XmlSerializer(typeof(Envelope));
                var myEnv = new Envelope()
                {
                    Header = new EnvelopeHeader()
                    {
                        Security = new Security()
                        {
                            UsernameToken = new SecurityUsernameToken()
                            {
                                Username = user,
                                Password = new SecurityUsernameTokenPassword()
                                {
                                    Type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText",//update type to match your case
                                    Value = pass
                                }
                            }
                        }
                    },
                    Body = new EnvelopeBody()
                    {
                        get_Time_Off_Plan_Balances_RequestType = new WorkDayAbsenceServiceReference.Get_Time_Off_Plan_Balances_RequestType()
                        {
                            Request_Criteria = new WorkDayAbsenceServiceReference.Time_Off_Plan_Balance_Request_CriteriaType()
                            {
                                Employee_Reference = new WorkDayAbsenceServiceReference.WorkerObjectType()
                                {
                                    ID = new WorkDayAbsenceServiceReference.WorkerObjectIDType[]
                                    {
                                        new WorkDayAbsenceServiceReference.WorkerObjectIDType
                                        {
                                            type = "Employee_ID",
                                            Value = workerId
                                        }
                                    }
                                }
                            }
                        }
                    }
                };


                cs.Serialize(writer, myEnv, names);
                ms.Flush();
                ms.Seek(0, SeekOrigin.Begin);
                StreamReader sr = new StreamReader(ms);
                xml = sr.ReadToEnd();
            }
        }

        SoapEnvelope responseEnvelope = null;
        using (var client = SoapClient.Prepare().WithHandler(new DelegatingSoapHandler()
        {
            OnHttpRequestAsyncAction = async (z, x, y) =>
            {
                x.Request.Content = new StringContent(xml, Encoding.UTF8, "text/xml");
            }
        }))
        {
            responseEnvelope = client.SendAsync(uriBase, "action", SoapEnvelope.Prepare()).Result;
        }

Body Envelope is specific to my case.

0

Message feature in security is not yet supported in asp.net core-2.0.

You can check a full list of supported feature here: https://github.com/dotnet/wcf/blob/master/release-notes/SupportedFeatures-v2.0.0.md

Alternatively you can try to create your own SOAP envelope kind of a Web api build on .Net Standard which will do security stuff for you and you can simple call it from your Azure function using httpclient. I haven't tried it but i got this idea from below post:

How Do I Call XML SOAP Service that Requires Signature from .Net Core?

private static HttpClient Client = new HttpClient(); 

Uri uri = new Uri("https://thirdparty.com/service.svc");
X509Certificate2 cert = // from some store etc
var envelope = BuildEnvelope(cert);

using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri))
{
    request.Content = new StringContent(envelope, Encoding.UTF8, "application/soap+xml");
    using (HttpResponseMessage response = Client.SendAsync(request).Result)
    {
        if (response.IsSuccessStatusCode)
        {
            response.Content.ReadAsStringAsync().ContinueWith(task =>
            {
                string thirdparty_envelope = task.Result;
                XElement thirdparty_root = XElement.Parse(thirdparty_envelope);
                // etc
            }, TaskContinuationOptions.ExecuteSynchronously);
        }
    }
}

Additional reference:

https://github.com/dotnet/wcf/issues/13

https://github.com/dotnet/wcf/issues/8

Alternatively, you can use Microsoft Logic app with Workday connector and then you can use the endpoint. Though it's in preview, but you can implement your custom connector as well.

https://learn.microsoft.com/en-us/connectors/custom-connectors/create-register-logic-apps-soap-connector

Hope it helps, Feel free to tag me in your conversation.

Mohit Verma
  • 5,140
  • 2
  • 12
  • 27