We're currently re-developing a legacy project that makes use of a SOAP web service from Lumesse, called over HTTPS.
http://developer.lumesse.com/Getting_Started
This is being consumed in a ASP.NET application; the original developer (who has long since left) never took advantage of using the provided WSDL, preferring to manually construct the request and parse the response. Whilst this is utter madness, this is what Lumesse's documentation actually recommends when being consumed from .NET, as their service uses obsolete WSSE plain text security.
Whilst we would not usually go against the grain, we'd much prefer to use the in-built support for consuming SOAP web services as opposed to rolling our own solution as the previous developer has.
We've had a few issues already, such as the failure to generate temporary classes that we have hacked around.
Unfortunately, we are now stuck when it comes to sending a successful SOAP request.
The exception that is thrown when we make a request is:
The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
Digging deeper, the actual response is as follows:
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header />
<env:Body>
<env:Fault xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<faultcode xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">wsse:FailedCheck</faultcode>
<faultstring>Expired message.</faultstring>
</env:Fault>
</env:Body>
</env:Envelope>
Which appears to be that 'the signature or decryption was invalid'. Using the other developer's hobbled together solution, with the same credentials works as expected.
Why would it be failing here and what can we do to fix it?
Is it possible without resorting to rolling our own request and response (or even recommended?) service?
http://schemas.xmlsoap.org/specs/ws-security/ws-security.htm
The WSDL we have used, supplied from Lumesse:
https://api3.lumesse-talenthub.com/CareerPortal/SOAP/FoAdvert?WSDL
The endpoint we are hitting:
https://api3.lumesse-talenthub.com/CareerPortal/SOAP/FoAdvert?api_key=xxx
Here is our code so far, heavily based off With C#, WCF SOAP consumer that uses WSSE plain text authentication? - which looks like the same issue.
Correct way communicate WSSE Usernametoken for SOAP webservice has the same issue, but the answer has us storing the binding details in the web.config.
using (LumesseSoapTest.FoAdvert.FoAdvertWebServiceClient client = new LumesseSoapTest.FoAdvert.FoAdvertWebServiceClient())
{
client.ClientCredentials.UserName.UserName = "xxxx";
client.ClientCredentials.UserName.Password = "xxxx";
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
client.Endpoint.Binding = binding;
var response = client.getAdvertisements(new LumesseSoapTest.FoAdvert.getAdvertisements());
}
Example request Lumesse expects, taken from the part of the previous developers home-rolled solution - works as expected:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.mrted.com/">
<soapenv:Header>
<wsse:Security soapenv:mustUnderstand="1" 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">
<wsse:UsernameToken wsu:Id="UsernameToken-11">
<wsse:Username>xxxx</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
xxxx
</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">5Xhsv3Yp2l1xGpL3pNYy6A==
</wsse:Nonce>
<wsu:Created>2012-06-22T09:07:26.631Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
<getAdvertisements xmlns="http://ws.mrted.com/">
<firstResult>0</firstResult>
<maxResults>0</maxResults>
</getAdvertisements>
</soapenv:Body>
</soapenv:Envelope>
Example request we are currently sending:
<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>
<ActivityId CorrelationId="d309ce44-ed91-4314-87ee-e3abee4f531e" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">dd9a8c26-e673-464d-87e4-5cb8b76989c3</ActivityId>
<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>2014-09-30T16:15:47.426Z</u:Created>
<u:Expires>2014-09-30T16:20:47.426Z</u:Expires>
</u:Timestamp>
<o:UsernameToken u:Id="uuid-c3275c63-6d98-4ae3-a7a7-afe314d23d6c-3">
<o:Username>xxxx</o:Username>
<o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">xxxx</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">
<getAdvertisements xmlns="http://ws.mrted.com/">
<firstResult>0</firstResult>
<maxResults>0</maxResults>
</getAdvertisements>
</s:Body>
</s:Envelope>
Any help will be greatly appreciated.
Update
Right, using this hacked together XML, I can get a response from the API.
I just don't have a clue how to generate this from our request. Again any help will be greatly appreciated.
<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>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:UsernameToken u:Id="UsernameToken-11">
<o:Username>xxx</o:Username>
<o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">xxx
</o:Password>
<o:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">
5Xhsv3Yp2l1xGpL3pNYy6A==
</o:Nonce>
<o:Created>2012-06-22T09:07:26.631Z</o:Created>
</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">
<getAdvertisements xmlns="http://ws.mrted.com/"><firstResult>0</firstResult><maxResults>10</maxResults>
</getAdvertisements>
</s:Body>
</s:Envelope>
Update 2 (got it working!)
Finally, after about 9 hours we've got somewhere. I'll leave this here in for anybody else who has the utter misfortune of working with Lumesse (or another similar Java web service).
The main issue with the XML we are sending above is the Timestamp node under the Security node. The extraneous Nonce node it'll handle, presumably because it's not the first node under the Security node? Who knows (this is actually my first experience with SOAP / WCF in any form haha!).
So, the Timestamp node needs to go. If you need to use a standard binding like basicHttpBinding
or wsHttpBinding, you'll need to create a custom binding. Here's an example that mimics basicHttpBinding, apparently, taken from http://www.mikeobrien.net/blog/removing-wss-timestamp-from-wcf/
Example config:
<customBinding>
<binding name="MyBinding">
<security authenticationMode="UserNameOverTransport" includeTimestamp="false" />
<textMessageEncoding messageVersion="Soap11" />
<httpsTransport maxReceivedMessageSize="26214400" />
</binding>
</customBinding>
Then just call the service as would, passing in the credentials (you can probably stores these in the web.config alongside the above, but I'm currently damned if I know how).
using (LumesseSoapTest.FoAdvert.FoAdvertWebServiceClient client = new LumesseSoapTest.FoAdvert.FoAdvertWebServiceClient())
{
client.ClientCredentials.UserName.UserName = "xxxx";
client.ClientCredentials.UserName.Password = "xxxx";
foreach (var ad in response.advertisementResult.advertisements)
{
#ad.jobTitle <br />
}
}
Thanks again.
I too am working with the same api and had the same issue. If you scroll down to the bottom of this article: http://www.hanselman.com/blog/BreakingAllTheRulesWithWCF.aspx Scott Hanselman removes the time-stamp through code rather than through the config.
Related
I've boiled my problem down to the <Action s:mustUnderstand="1" bit below.
<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"></Action>
<ActivityId CorrelationId="ec9c7c4e-2e7e-4310-8ad1-99e453b29560" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">45abb8ea-8d9f-4fc2-aeb7-580884cae02e</ActivityId>
</s:Header>
<s:Body>
[redacted]
</s:Body>
</s:Envelope>
When mustUnderstand=1 is there the service barfs, and returns a web server 500 error page, not xml; with it mustUnderstand=0 or the entire <Action removed it works like a dream.
I've tried both standard basicHttpBinding as well as the following customBinding trying to simplify the messages from here:
<binding name="httpSoap12">
<textMessageEncoding messageVersion="Soap12" />
<httpTransport />
</binding>
I've seen lots of people struggling with this particular issue; but nobody seems to have a solution that I can find.
Removal of the Action bit wholesale, or setting mustUnderstand="0" both allow the service to process my incoming message. I'd welcome a solution to do either of those, or an alternative I have yet to come up with. I should specify I've done that removal/modification manually. I cannot find a way to do it from my code.
I am converting a legacy web service to a WCF service. This service currently is being used by front end application. But as of now the front end has intention to consume the new WCF service so instead we plan on rerouting the request to our new service from IIS. The catch here is that the new WCF service should be able handle the old input request and should be able to send back the response in the exact same format.
I didnt have any trouble handling the input request but I am facing issues trying to return message in old format. My current service is returning SoapResponse in below format (I am nout using any message contracts/DataContracts as I am using the same return type&Input parameter used in the Old legacy code):
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<UserCheckResponse xmlns="Abc.SomeNamespace">
<UserCheckResult a:Direction="Response" a:Purpose="UserCheck" xmlns:a="Abc.SomeNamespace">
<a:KV a:Key="TraceID" a:Value="546546565" />
<a:KV a:Key="Response" a:Value="78954" />
<a:KV a:Key="UserVerified" a:Value="N" />
<a:KV a:Key="TryAgain" a:Value="Y" />
<a:KV a:Key="DataSource" a:Value="NA" />
</UserCheckResult>
</UserCheckResponse>
I Want it to look like below (I dont want the UserCheckResult node in the response instead i want the key value pairs directly under UserCheckResponse node):
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<UserCheckResponse a:Direction="Response" a:Purpose="UserCheck" xmlns:a="Abc.SomeNamespace">
<a:KV a:Key="TraceID" a:Value="546546565" />
<a:KV a:Key="Response" a:Value="78954" />
<a:KV a:Key="UserVerified" a:Value="N" />
<a:KV a:Key="TryAgain" a:Value="Y" />
<a:KV a:Key="DataSource" a:Value="NA" />
</UserCheckResponse>
I would say using
XMLSerialize instead of
DataContractSerializer(default for WCF).
Add "[System.ServiceModel.XmlSerializerFormatAttribute()]" for your operations.Then use XmlElementAttribute for you response node.
When I'm not busy, I will give some sample. Because I'm current working on the taking a legacy web service call and use the legacy request to call WCF service the return legacy response.
I have downloaded WsdlUI, a open source C# SOAP client that is able to consume and process WCF messages without needing to add a reference. I can post to a method that has simple types(like strings and ints). When the WCF method has a complex type the XML envelope is incorrect. I am somewhat a noob to WCF and am a little lost with all the code... someone please help; how do I get the XML to return the correct XML?
I enabled tracing on my server and here is the valid xml envelope from a console app that has the service reference added:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://host.com/BG.WCF/Service.svc</To>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/MobileInterface/CreateQueuedMsg</Action>
</s:Header>
<s:Body>
<CreateQueuedMsg xmlns="http://tempuri.org/">
<Token>add568d9-45e0-0000-98a5-762b84989797</Token>
<BGSMSMessage xmlns:a="http://schemas.datacontract.org/2004/07/BG.Bus.Mobile.Classes" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:BusinessID>Test</a:BusinessID>
<a:CommsGUID>eca403e7-1116-4bc3-a476-31e91c4ca33f</a:CommsGUID>
<a:DestinationAddress>+27000000000</a:DestinationAddress>
<a:Msg>test msg</a:Msg>
<a:MsgEncodingType>Default</a:MsgEncodingType>
<a:SendDT>2014-02-24T07:21:45.3187492+02:00</a:SendDT>
<a:SystemID>Test</a:SystemID>
<a:ValidityDT>2014-02-27T07:21:45.3187492+02:00</a:ValidityDT>
</BGSMSMessage>
<smsRoute xmlns:a="http://schemas.datacontract.org/2004/07/BG.Bus.Mobile.Classes" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:SMSRoute>BGWASP</a:SMSRoute>
</smsRoute>
</CreateQueuedMsg>
</s:Body>
here is the envelope WsdlUi returns:
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://bgvm08mbl01.businessgenetics.co.za/BG.Bus/BG.Bus.Mobile.WCF/MobileService.svc</To>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/MobileInterface/CreateQueuedMsg</Action>
</s:Header>
<soap:Body>
<CreateQueuedMsg xmlns="http://tempuri.org/">
<Token>ADD568D9-45E0-498E-98A5-762B84989797</Token>
<BGSMSMessage>
<BusinessID>Test</BusinessID>
<CommsGUID>559eb322-7b01-438d-93f1-74b1aafccc4f</CommsGUID>
<DestinationAddress>0000000000</DestinationAddress>
<Msg>Testers2014-02-24 07:37:53</Msg>
<MsgEncodingType>Default</MsgEncodingType>
<SendDT>2014-02-24 07:37:53</SendDT>
<SystemID>Test</SystemID>
<ValidityDT>2014-02-27 07:37:53</ValidityDT>
</BGSMSMessage>
<smsRoute>
<SMSRoute>BGWASP</SMSRoute>
<SMSRoute>GV</SMSRoute>
</smsRoute>
</CreateQueuedMsg>
</soap:Body>
I am the developer of WsdlUI so thanks for using it.
The application currently does not work with complex types due to the bug that you are referring to.
I should have a release out mid June that will fix this problem.
Thanks for the detailed description of the problem in your post.
A third party in charge of developing Java based webservice came back to us with requirement that message header needs to look like this:
<soapenv:Header>
<wsse:Security>
<xenc:ReferenceList>
<xenc:DataReference URI="#EncDataId-1"/>
</xenc:ReferenceList>
<wsse:UsernameToken>
<wsse:Username>[snip]</wsse:Username>
<xenc:EncryptedData Id="EncDataId-1" Type="http://www.w3.org/2001/04/xmlenc#Element">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyName>[snip]</ds:KeyName>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>[snip]</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
Given my very surfacish understanding of this security voodoo magic I am having trouble figuring out how configure my client to produce such header. Right now my code looks like this:
client.ClientCredentials.UserName.UserName = "[snip]";
client.ClientCredentials.UserName.Password = "[snip]";
and the header:
<s:Header>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:UsernameToken u:Id="uuid-e906a1ca-aa63-474c-b4ac-cf9b90ab2435-1">
<o:Username>[snip]</o:Username>
<o:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">[snip]</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
and binding:
<binding name="SMSSoap11">
<security mode="TransportWithMessageCredential" />
</binding>
WCF will not produce this output for you. You will have to write your own token for this and maybe even more. WCF supports only username token with plain password out-of-the-box and your code example even doesn't look like any part of username token specification. If the goal is to use encrypted password with WS-Security then the security header seems incomplete.
You should ask Java developers what are security requirements in terms of WS-SecurityPolicy?
I am trying to modify and add custom headers to a soap message in the BeforeSendRequest method like this:
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
{
MessageHeader header = MessageHeader.CreateHeader("name", "http://myname", "Oscar");
request.Headers.Add(header);
return null;
}
Everything is ok at this point, the header is added in the soap headers like this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<ActivityId CorrelationId="51efbfdb-2187-45b9-81fc-6a38815d5bed" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">d1351a78-a53a-4d32-ad35-fca6d4262adf</ActivityId>
<name xmlns="http://myname">Oscar</name>
</s:Header>
The problem is that I want to add something bigger in the headers and the final result has to be like this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<ActivityId CorrelationId="51efbfdb-2187-45b9-81fc-6a38815d5bed" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">d1351a78-a53a-4d32-ad35-fca6d4262adf</ActivityId>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:Username xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">username</wsse:Username>
<wsse:Password xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</s:Header>
Is there a way to achieve this?
PS. Please guys :-) do not criticize me, I am in the following situation:
When adding this username and password auth, you need to be over https, the problem is that this Java based webservice which I am trying to communicate is under http and I think it is not compliant because according to the 3rd link "It is considered bad practice to pass credentials over an unsecured connection" but they solved it adding the headers in the config file (something I did without success).
I read these links too and tried them. Also when doing things from links 1 and 2, I got a kind of exception in Visual Studio which says something like "It is expected https and not http" that's why I am trying MessageInspectors using BeforeSendRequest method.
Configure WCF for WS-Security with Username over https
http://www.west-wind.com/weblog/posts/2007/Dec/09/Tracing-WCF-Messages
http://www.fransson.net/blog/credentials-over-plain-http-with-wcf/
ClearUsernameBinding is the solution
The 3rd parameter of CreateHeader is an object. By default this object is serialized using the DataContractSerializer so if you create a data contract that models the header structure you can pass an instance as the 3rd parameter and it will create the header you want
I had the same problem, though my service was https. And it should be a configuration matter, you shouldn't need to handle this manually.
Anyway, you don't have to use the MessageInspectors using BeforeSendRequest.
You should try and change the config generated code adding some security configuration.
WCF will then create what headers you need. Then in code add the credentials and you should be ok.
I imagine that the generated proxy is creating you a customBinding or basicHttpBinding.
basicHttpBinding though not supporting the WS-Security specifications, so you should change this either to custom or wsHttpBinding. You can play with and find the correct configuration to gain the desired results.
What I added was "Sample config syntax":
<customBinding>
<binding name="MessageServerBinding">
<security defaultAlgorithmSuite="Default" authenticationMode="UserNameOverTransport"
requireDerivedKeys="true" includeTimestamp="true" messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
<secureConversationBootstrap />
</security>
........
</binding>
</customBinding>
This is a sample configuration, e.g. you might not need to include a timestamp, or you could need a Nonce which if I remember correctly needs some more effore, not much though.
For check this discussion: WCF: Adding Nonce to UsernameToken
Hope this helps.