Consuming a WCF Service from PHP - php

first off let me "warn" you that i am not a PHP developer and am pretty clueless about PHP. I'm the developer of the WCF Service in question and am trying to support the PHP developer on staff who is trying to consume this service.
He doesn't have a Stackoverflow login and is to busy beeing pissed off at WCF to type anything without profanity ;-)
Anyhow, the service is using the following security configuration:
<security mode="Message">
<message clientCredentialType="UserName" negotiateServiceCredential="true" algorithmSuite="Default" />
</security>
This means the message is encrypted over the line, i believe this requires a certificate which has been installed on the webserver and when consuming the service from .NET works without any problems at all.
We've looked at fiddler communication and suspect RequestSecurityTokenResponse to be of import. I'm suspecting a handshake where a securitytoken is requested by the client, this is generated with a GUID as reference, the value is used to encrypt the request and the GUID is send as a reference.
This is all speculation though, so far we have been unable to get the requests to even remotely look the same.
Any pointers in the right direction would be much appreciated.
So far we're trying this with WSE-PHP, which can be found through google.
EDIT:
We've been able to confirm our thoughts with Fiddler and working clients do seem to do a handshake, there are three requests (and responses) in total which seem to exchange only security information, they are calling the following actions:
http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue
http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue
http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT
After this last call a request is made with an action which seems to indicate a call to the actual webservice method:
http://XXXXXXXXXXXXXX/WCF/ICompany/TestConnection
This seems to have to do with SAML (i love you google) so i added this as a tag.

Yes, there is support for WS-Security in PHP. Some assembly is required. See, for example, Secured Web Services with PHP. The WS-* specifications are written and implemented for interoperability, that is, so that the will operate across different platforms, vendors, transports, and languages. It is widely adopted, and the 1.1 version has been an OASIS standard since 2006.
Perhaps it would help defuse the tenion and be instructive if your PHP developer would show the ability to consume any web service that uses WS-Security, just to take your service and WCF out of the equation for a little while.

After my long workout for Email WCF service with attachment. Now i am able to send an email from PHP using WCF service. Here is code snippet.
<?php
ini_set('display_errors',1);
require_once ('nusoap.php');
$parameters= new StdClass();
$parameters->emailinfo = new StdClass();
$fileAttachmentPath = 'example.csv';
$data = base64_encode(file_get_contents($fileAttachmentPath));
$parameters->emlinfo->NoFileInfo = true;
$parameters->emlinfo->FileNameWithExt = "example.csv";
$parameters->emlinfo->FileContentBase64 = $data;
//$parameters->emlinfo->FileAttachment = $parameters->ArrayOfFileAttachment;
//$parameters->emlinfo->FileAttachment = (array) $parameters->emlinfo->FileAttachment;
$parameters->emlinfo->MailTo='';
$parameters->emlinfo->MailFrom='';
$parameters->emlinfo->MailMessage='';
$parameters->emlinfo->MailBody='';
$parameters->emlinfo->MailSubject='';
$parameters->emlinfo->MailType='';
$parameters->emlinfo->UserId='';
$parameters->emlinfo->Password='';
$parameters->emlinfo->SmtpId='';
$parameters->emlinfo->Token='';
$parameters->emlinfo->ApplicationId='';
$parameters->emlinfo->VisitorName='';
$parameters->emlinfo->VendorId='';
$parameters->emlinfo->ReplyTo='';
try {
$braspag = new SoapClient('WSDL Sevice',
array(
'trace' => 1,
'exceptions' => 1,
'style' => SOAP_DOCUMENT,
'use' => SOAP_LITERAL,
'soap_version' => SOAP_1_1,
'encoding' => 'UTF-8'
)
);
//$SendEmailResponse = $braspag->__getTypes();
$SendEmailResponse = $braspag->SendEmail($parameters);
}
catch(SoapFault $fault) {
//echo 'Ocorreu um erro: ' , $fault->getMessage();
}
var_dump($parameters->emlinfo);
?>
Regards,
Rahul Soni.

Why dont you setup a platform independent UI project using something like SOAP UI where it will enable you to have automated tests against your WCF service, guaranteeing your contracts are sound while benefiting the PHP consumer. The reason this benefits the PHP consumer is that he will have automated test cases which he can profile/run/precident he can use to make his php side of things.
One thing to note is that .net namespacing can cause issues for other languages with SOAP. I really really recommend you investigate SOAP UI or a free alternative. Possibly an alternative that is free is fiddler, however I believe that is not automated.
Here are some other helpers for your PHP consumer:
http://weblogs.asp.net/gunnarpeipman/archive/2007/09/17/using-wcf-services-with-php.aspx
https://github.com/geersch/WcfServicesWithPhp5

I don't know a lot about WCF but it seems that is SOAP based. You can start by looking at php Soap functions. I see that also supports JSON communication, check JSON functions from php.

(this is a bit long to add as comment to Visual Stewart's answer)
clientCredentialType="UserName"....This means the message is encrypted over the line
Not really. The message is not encrypted, but it will be sent via SSL. From the published docs:
Username
Allows the service to require that the client be authenticated with a user name credential. Note that WCF does not allow any cryptographic operations with user names, such as generating a signature or encrypting data. WCF ensures that the transport is secured when using user name credentials.
--
Note the difference between the message and the transport.
Authentication may be derived from a client certificate used for the transport, or negotiated (NTLM).

If the server is configured with clientCredentialType="UserName" PHP consumer must use some WS-Security implementation to send Username and Password SOAP security headers to the server.
<soapenv:Envelope>
<soapenv:Header>
<wsse:Security >
<wsse:UsernameToken>
<wsse:Username>bob</ wsse:Username>
<wsse:Password Type="PasswordText">bob1</ wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body> ... </soapenvBody>
</soapenv:Envelope>
Keep in mind that the server can be configure to acept plain or hashed password. So the client must send plain or hashed password.

Related

PHP SOAP-ERROR: Parsing Schema: element already defined When Connecting to D365 WSDL

I am connecting to dynamics 365. It used to work perfectly, i curl to get the token then i use it as an authorization header along with php soapclient and it works, i connect i create a client and i can call my methods.
All of a sudden it decided not to work, and where it used to connect as SOAP 1.1 now it enforced SOAP 1.2
After changing from SOAP 1.1 to SOAP 1.2 ( because I got the error of binding mismatch where it said expecting application/soap+xml and text/xml was found ) So I changed versions and that error disappeared and got replaced with ERROR Fetching HTTP Headers.
That error got stuck for the longest time, people suggested to increase timeout but i put it as high as 500 800 5000 all the same.
Then all of a sudden, it started giving me SOAP ERROR Parsing schema element already defined. I did not change my code, i played for awhile with the headers but to no avail, I even removed the authorization header just to see what is going on and that did nothing i kept getting the same error.
SOAP-ERROR: Parsing Schema: element 'http://schemas.datacontract.org/2004/07/Microsoft.Dynamics.Ax.Xpp:XppObjectBase' already defined [string:Exception:private]
everytime I try to connect I get different kind of parsing schema error even though i am not changing anything in my code:
SOAP-ERROR: Parsing Schema: element 'http://schemas.microsoft.com/2003/10/Serialization/:anyType' already defined [string:Exception:private]
and another
SOAP-ERROR: Parsing Schema: element 'http://schemas.datacontract.org/2004/07/Microsoft.Dynamics.AX.KernelInterop:ProxyBase' already defined [string:Exception:private]
and then sometimes it does get through for a second but with fetching http header error again..
so i can not create a client instance anymore now..
where before i was able to create a client instance but i get an error when I call the method of "Error Fetching HTTP Headers"
something is definitely not stable because my errors are not one.
now some stated the wsdl could be faulty, but this is microsoft and the person i am in contact keeps saying he can not doing anything about it.
Help is this a PHP problem or a dynamics problem or wsdl custom made problem .
And how to solve this.
Thank you.
UPDATE
I'm sorry I mentioned earlier it is Dynamics AX , it turns out it is Dynamics 365 D365. I will keep dynamics ax tag in case it helps someone who needs the solutions provided.
UPDATE
Following is the connection code I am using:
function getAuthenticationHeader()
{
//Each variable has the values for our server
//resource
$appResource = urlencode($appADResource);
//clientID
$appClientID = urlencode($appADClientId);
//appSecret
$appSecret = urlencode($appADSecret);
//username
$appUserID = urlencode($appUserID);
// Password
$appUserPassword = urlencode($password);
// Construct the body for the STS request
$authenticationRequestBody = 'resource='.$appResource.'&client_id='.$appClientID.'&client_secret='.$appSecret.'&grant_type=password&username='.$appUserID.'&password='.$appUserPassword.'&scope=openid';
//Using curl to post the information to STS and get back the authentication response
$ch = curl_init();
// set url
$stsUrl = 'https://login.microsoftonline.com/'.$appTenantId.'/oauth2/token';
curl_setopt($ch, CURLOPT_URL, $stsUrl);
// Get the response back as a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// Set the parameters for the request
curl_setopt($ch, CURLOPT_POSTFIELDS, $authenticationRequestBody);
// By default, HTTPS does not work with curl.
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// read the output from the post request
$output = curl_exec($ch);
// close curl resource to free up system resources
curl_close($ch);
// decode the response from sts using json decoder
$tokenOutput = json_decode($output);
return $tokenOutput->{'token_type'}.' '.$tokenOutput->{'access_token'};
}
try
{
//WSDL Link
$url = "https://urlToOurServer/services/webservice?wsdl";
$authorizationToken = getAuthenticationHeader();
$context = stream_context_create(array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
),
'https' => array(
'curl_verify_ssl_peer' => false,
'curl_verify_ssl_host' => false
),
'http' => array(
'header' =>'Authorization: '.$authorizationToken
)
));
//Create array of Soap Options
$arrOpt = array(
"soap_version" => SOAP_1_2,
"cache_wsdl" => WSDL_CACHE_NONE,
"exceptions" => true,
'trace' => true,
'encoding' => 'UTF-8',
'stream_context' => $context
);
}catch(Exception $e)
{
print_r($e);
}
I also found this in my wsdl
<sp:IssuedToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<sp:RequestSecurityTokenTemplate>
<trust:TokenType xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0
</trust:TokenType>
<trust:KeyType xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer
</trust:KeyType>
</sp:RequestSecurityTokenTemplate>
<wsp:Policy>
<sp:RequireInternalReference/>
How can I connect to SAML for Token ?
If everything is pretty much the same, but it's not working, the first thing to do is rule out the most basic AX issues. These may not solve your issue but will be a good first step.
now some stated the wsdl could be faulty, but this is microsoft and the person i am in contact keeps saying he can not doing anything about it.
Whomever that person is, you need to confirm they've done the following:
Confirm the environment and specifically the CIL is fully compiled. Do a full AXBuild and a full CIL to be sure during non-business hours and ensure the output is good. It's basically saying "recompile everything".
Refresh the WCF configuration in the client configuration you are using to connect to AX. This client configuration may be a *.axc file or it may just be the active one. Also refresh the business connector WCF. This is separate and may be what you are using to connect to AX. This is what most people are talking about.
Here's a little article that talks about creating a configuration, but I'll discuss below.
An AX client configuration ultimately is a bunch of text. It's either stored in an .axc file or stored in the registry in a few locations. The Business Connector client config may be the one that is getting missed in your scenario.
If you follow the link above and create a new .axc configuration file and ensure you've clicked "Refresh Configuration" before exporting, when you open the file up in Notepad, you'll see wcfconfig and a bunch of XML following it. That XML is what you're trying to get updated. Creating a new AXC here is just an exercise to help you understand what it is. You can delete the file after you're done looking.
Now, you've basically created a specific configuration file, but that doesn't mean anything is using it. If you call AX32.exe it will default to the one that is loaded in that config screen. Using a file is a way to very specifically choose one. Your code is probably using a specific AXC somewhere that needs either replaced or refreshed OR it's using one that's saved in this window:
It is very likely it is using one of the two that are saved in that configuration window. When you refresh in that window, it ultimately saves the WCF XML in the windows registry on the machine that is hosting the client and/or the AOS in subfolders in HKLM\SOFTWARE\Microsoft\Dynamics\6.0\Configuration. The key(s) is wcfconfig paired with wcfconfigversionid, which just stores a GUID to see if it's up-to-date.
When I say two, I mean most people don't even bother to look at the Business Connector AXC. It's what is highlighted in yellow in my image, and you need to specifically choose and refresh it. This could be important for you. In my image, I do not have it chosen. You need to drop the menu down and choose it.
On a dev machine, you can just clear both of those keys and refresh and you should see whatever configuration you're working on update.
This is a long post, but it's important to rule this part out first. If you have someone who's reasonably experienced administering AX they should know how to ensure these are refreshed.
Since you're saying this is not Dynamics AX, but one of the Dynamics 365 versions. The AX version used to be called Dynamics 365 for Finance and Operations Enterprise Edition but they've changed the licensing/naming again, so I don't even know what it's technically called. Most people call it Dynamics 365 for Operations or some variant.
Either way, you should test the service following the below method. We would need to see more information about the service details and call, so following the below is most likely best.
https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/data-entities/third-party-service-test

WS-Security, and how to Authenticate

Documentation of the service, says I need to use WS-Security.
From they support, i got a p12 file, which I should be using.
What I did so far
I ran up SoapUI application, configured it, added wsdl etc, and got message
<faultstring>These policy alternatives can not be satisfied: (...)</faultstring>
So I found I need to add basic Auth to the request. And i got my proper answer.
What I need now
I need to use this to SOAP requests, on my PHP application.
First, i changed p12 file into pem, and tried :
$soapClient = new SoapClient('https://int.pz.gov.pl/pz-services/tpSigning?wsdl',
array('location' => 'https://int.pz.gov.pl/pz-services/tpSigning?wsdl',
'trace' => 1,"exceptions" => 1,
'local_cert'=>'path/cert_file.pem',
'passphrase'=>'cert_password'
));
But I am still getting same fail message about policies not being satisfied:
These policy alternatives can not be satisfied:
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}AsymmetricBinding: Received Timestamp does not match the requirements
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}X509Token: The received token does not match the token inclusion requirement
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}SignedParts: Soap Body is not SIGNED in (...)
Help?
Is it even possible with just PHP? I tried several solutions, found some class extending SoapClient (using user/password, not p12/pem file), found even solution in c# (which I am too ready to use if that's what I need to do - sending xml to c# with WebSocket, and sending it back to browser), but non of those worked.
Changing the cert into pem format is the right way. The native PHP soap client class can not handle p12 certs. Have you tried the following?
try {
$oClient = new SoapClient(
'https://int.pz.gov.pl/pz-services/tpSigning?wsdl',
[
'authentication' => SOAP_AUTHENTICATION_DIGEST,
'exceptions' => true,
'local_cert' => dirname(__FILE__) . 'mycert.pem',
'passphrase' => 'my_passphrase',
'trace' => true,
]
);
} catch (SoapFault $oSoapFault) {
echo "<pre>";
var_dump($oSoapFault);
echo "</pre>";
}
The PHP soap client class uses SOAP_AUTHENTICATION_BASIC by default. Perhaps the DIGEST auth is the right way? Normally the cert includes all the data.
You don 't need the location option. The location option is just required, when using non-wsdl conversation with target namespace as uri option without a direct wsdl file.
And always keep in mind: What works with SoapUI isn 't supposed to run with the native PHP client. ;)

SOAP Server side certificate verification on PHP

Previously I have created system that was sending SOAP messages using certificate.
Example shows how it was done:
$client_options = array();
$client_options['local_cert'] = '<path to .pem file>';
$client_options['passphrase'] = '<.pem file password>';
$client = new SoapClient('<address to wsdl file>', $client_options);
$tmp_res = $client->some_function($params);
But now I have to create server that receives messages like these.
I found many resources, all suggesting this code:
$classmap = array('<class_name2>'=>'<class_name2>');
$server = new SoapServer('<path to wdsl file>',array('classmap'=>$classmap));
$server->setClass("<class_name>");
$server->handle();
Code itself works, but what I can't understand is - how certificates work within this client-server communication.
As I understand, for secure communication two pairs of private key and certificate are required. One for each of parties.
There is way to add .pem file with one private key and certificate pair for SoapClient.
Incoming messages (at the both sides) comes unencrypted.
.
Mustn't there be a way to pass certificate and key pair for SoapServer?
How exactly verification of certificates are performed (at what point and what is checked)?
How I can filter out messages only from the user I'm interested in, provided, I can get his certificate?
Sorry for such childish questions, but can You point me out, where I am wrong and provide appropriate resources to find information about this?
Thanks.

Strangely, can't convert simple php soap call into python

This seemed easy at first sight but I can't get it to work at all
We are moving a website from wordpress to django. I have to use the payline api to make the website accept online payments. I know there are other solutions but this is a client requirement.
In their php library (works fine) :
$client = new SoapClient('/.../spec.wsdl', ['login' => 'xxx', 'password' => 'yyy']);
$client->__setLocation("https://homologation.payline.com/V4/services/WebPaymentAPI");
$response = $client->doWebPayment($WSRequest);
In Python, I tried this :
from suds.client import Client
client = Client(url='https://homologation.payline.com/V4/services/WebPaymentAPI', headers={'login': 'xxx', 'password': 'yyy'})
request = client.factory.create('doWebPaymentRequest')
# filling up request parameters
result = client.service.doWebPayment(request)
But I get a http 401 : unauthorized access.
I have tried desperately a couple of things in the last 3 hours but none worked : I tried to use pysimplesoap, tried to use a custom transport object, tried to do ?wsdl in the client url (which worked a little) and setting the login/password via set_options
Any idea ? Thanks
suds documentation is lacking so here is something that worked, at least for the server setup I had to work with (unknown to me)
client = Client(url='file://path/to/wsdl')
client.set_options(param1="xxx", param2="yyy", ...)
in my case, param1 was "username", param2 was "password" etc, but these are specific to the server's authentication scheme

Speeding up a soap powered website

We're currently looking into doing some performance tweaking on a website which relies heavily on a Soap webservice. But ... our servers are located in Belgium and the webservice we connect to is locate in San Francisco so it's a long distance connection to say the least.
Our website is PHP powered, using PHP's built in SoapClient class.
On average a call to the webservice takes 0.7 seconds and we are doing about 3-5 requests per page. All possible request/response caching is already implemented so we are now looking at other ways to improved the connection speed.
This is the code which instantiates the SoapClient, what i'm looking for now is other ways/methods to improve speed on single requestes. Anyone has idea's or suggestions?
private function _createClient()
{
try {
$wsdl = sprintf($this->config->wsUrl.'?wsdl', $this->wsdl);
$client = new SoapClient($wsdl, array(
'soap_version' => SOAP_1_1,
'encoding' => 'utf-8',
'connection_timeout' => 5,
'cache_wsdl' => 1,
'trace' => 1,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS
));
$header_tags = array('username' => new SOAPVar($this->config->wsUsername, XSD_STRING, null, null, null, $this->ns),
'password' => new SOAPVar(md5($this->config->wsPassword), XSD_STRING, null, null, null, $this->ns));
$header_body = new SOAPVar($header_tags, SOAP_ENC_OBJECT);
$header = new SOAPHeader($this->ns, 'AuthHeaderElement', $header_body);
$client->__setSoapHeaders($header);
} catch (SoapFault $e){
controller('Error')->error($id.': Webservice connection error '.$e->getCode());
exit;
}
$this->client = $client;
return $this->client;
}
So, the root problem is number of request you have to do. What about creating grouped services ?
If you are in charge of the webservices, you could create specialized webservices which do multiple operations at the same time so your main app can just do one request per page.
If not you can relocate your app server near SF.
If relocating all the server is not possible and you can not create new specialized webservices, you could add a bridge, located near the webservices server. This bridge would provide the specialized webservices and be in charge of calling the atomic webservices. Instead of 0.7s * 5 you'd have 0.7s + 5 * 0.1 for example.
PHP.INI
output_buffering = On
output_handler = ob_gzhandler
zlib.output_compression = Off
Do you know for sure that it is the network latency slowing down each request? 0.7s seems a long round time, as Benoit says. I'd look at doing some benchmarking - you can do this with curl, although I'm not sure how this would work with your soap client.
Something like:
$ch = curl_init('http://path/to/sanfrancisco/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);
$info = curl_getinfo($ch);
$info will return an array including elements for total_time, namelookup_time, connect_time, pretransfer_time, starttransfer_time and redirect_time. From these you should be able to work out whether it's the dns, request, the actual soap server or the response that's taking up the time.
One obvious thing that's just occurred to me is are you requesting the SOAP server via a domain or an IP? If you're using a domain, your dns might be slowing things down significantly (although it will be cached at several stages). Check your local dns time (in your soap client or php.ini - not sure) and the TTL of your domain (in your DNS Zone). Set up a static IP for your SanFran server and reference it that way if not already.
Optimize the Servers (not the client!) HTTP response by using caching and HTTP compressing. Check out the tips at yahoo http://developer.yahoo.com/performance/rules.html
1 You can assert your soap server use gzip compression for http content, as well as your site output does. A 0,7s roundup to SF seems a bit long, it's either webservice is long to answer, either there is an important natwork latency.
If you can, give a try to other hosting companies for your belgium server, in France some got a far better connectivity to the US than others.
I experienced to move a website from one host o another and network latency between Paris and New york has almost doubled ! it's uge and my client with a lot of US visitors was unhappy with it.
The solution of relocating web server to SF can be an option, you'll get a far better connectivity between servers, but be careful of latency if your visitors are mainly located in Europe.
2 You can use an opcode cache mecanism, such as xcache or APC. It wil not change the soap latency, but will improve php execution time.
3 Depending if soap request are repetitive, and how long could a content update could be extended, you can give it a real improvement using cache on soap results. I suggest you to use in-memory caching system (Like xcache/memcached or other) because they're ay much faster than file or DB cache system.
From your class, the createclient method isn't the most adapted exemple functionality to be cached, but for any read operation it's just the best way to perf :
private function _createClient()
{
$xcache_key = 'clientcache'
if (!xcache_isset($key)) {
$ttl = 3600; //one hour cache lifetime
$client = $this->_getClient(); ///private method embedding your soap request
xcache_set($xcache_key, $client, $ttl);
return $client;
}
//return result form mem cache
return xcache_get($xcache_key);
}
The example is for xcache extension, but you can use other systems in a very similar manner
4 To go further you can use similar mecanism to cache your php processing results (like template rendering output and other ressource consumming operations). The key to success with this technic is to know exactly wich part is cached and for how long it will stay withous refreshing.
Any chance of using an AJAX interface.. if the requests can be happening in the background, you will not seem to be left waiting for the response.

Categories