SOAP action error when using soapClient - php

i am getting this error when trying to make a soap call.
The SOAP action specified on the message, '', does not match the HTTP SOAP Action.
When i call $service->SearchTouristItems($sti); (this function is further below) i get the above error and i have no idea why.
The below is the code i am using.
// i used http://www.urdalen.no/wsdl2php/ to create TCS2Service which extends SoapClient
$service = new TCS2Service() ;
$sd = new ServiceDescriptor;
$sd->UniqueIdentifier = 'xxxxxxxxx-xxxxx-xxxx-xxxxx-xxxxxx';
$stic = new SearchTouristItemCriteria;
$stic->SearchString = array ('dublin') ;
$sti = new SearchTouristItems;
$sti->searchTouristItemCriteria = $sd;
$sti->serviceDescriptor = $stic;
$result = $service->SearchTouristItems($sti);
echo "<pre>";
print_r($result);
echo "</pre>";
SearchTouristItems looks like this
/**
*
*
* #param SearchTouristItems $parameters
* #return SearchTouristItemsResponse
*/
public function SearchTouristItems(SearchTouristItems $parameters) {
return $this->__soapCall('SearchTouristItems', array($parameters), array(
'uri' => 'http://tempuri.org/',
'soapaction' => ''
)
);
}
this is the initilization of the client
public function TCS2Service($wsdl = "http://www.example.com/services/TCS2Service.svc", $options = array( 'soap_version' => SOAP_1_2,
'exceptions' => true,
'trace' => 1,
'cache_wsdl' => WSDL_CACHE_NONE,)) {
foreach(self::$classmap as $key => $value) {
if(!isset($options['classmap'][$key])) {
$options['classmap'][$key] = $value;
}
}
parent::__construct($wsdl, $options);
}

Not sure though but what is the value of 'soapaction' => '' in your code is replaced with the provided parameter. I do not have that experience calling web services with PHP so just gave it a thought.

I think your ws-addressing is not turned on. Please turn on the ws-
addressing and check again.

What I would do :
check if the SOAP action is well defined in the WSDL: look for address location="
try another WSDL to php converter
send the WSDL url so I can try it by my side

Related

Adding WSSE security headers to PHP's SoapServer Response, using robrichards/wse-php

I am trying to add WSSE Security headers to a SOAP XML message that is created from PHP's SoapServer::handle(). This should be done using SoapServer::addSoapHeaders(new SoapHeaders(...)), but I am unsure how to set specific WSSE security headers to the response by using robrichards/wse-php package.
Firstly the SoapServer is created. Then the incoming request gets handled, which return some stdClass with data that the handle() function presumably automatically converts to a XML SOAP envelope. This is wrapped in Laravel's Illuminate\Http\Response object and returned.
ini_set('soap.wsdl_cache_enabled', 0);
ini_set('soap.wsdl_cache_ttl', 0);
ini_set('default_socket_timeout', 80);
header("Connection: close");
$soap = $this->createSoapServer();
ob_start();
// Response automatically becomes a XML, because of soap->handle() from PHP's SoapServer.
$response = new Response($soap->handle($xml_request), 200);
$response->header('Content-Type', 'text/xml');
return $response;
Within the createSoapServer() function I create a SoapServer and want to add WSSE Security headers to the SoapServer using addSoapHeaders(). The headers I need to add are all present withing an empty soap envelope in the headers_xml variable $headers_xml = $objWSSE->saveXML();.
I don't know how to get these headers separately.
I wish to know how to add these headers to the XML response created by the SoapServer. I should be able to add them using addSoapHeaders(), though I do not know how.
private function createSoapServer($soap_settings = []) {
$soap_settings = $this->assembleSoapSettings($soap_settings);
$wsdl_path = $soap_settings['wsdl_path'];
// Set soap's own options
$soap_settings['soap_options'] = array_merge([
WSDL_CACHE_NONE,
SOAP_SINGLE_ELEMENT_ARRAYS,
'trace' => !$this->isProduction,
'exceptions' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
'use' => SOAP_LITERAL,
'connection_timeout' => 80,
'soap_version' => SOAP_1_2,
], $soap_settings['soap_options']);
$soap = new SoapServer($wsdl_path,
array_merge([
'location' => $soap_settings['soap_location'],
'local_cert' => $soap_settings['ssl_cert_path'],
'passphrase' => $soap_settings['ssl_cert_password'],
], $soap_settings['soap_options'])
);
// Sets the server php class where the incoming request gets handled.
$soap->setClass($service_server);
// Retrieve empty XML envenlope to set headers in.
// NOTICE: This is not the correct approach,
$request = file_get_contents(app_path('Connect/Register/empty_soap.xml'));
$options = $this->soapclient_options;
$dom = new DOMDocument('1.0');
$dom->loadXML($request);
$objWSA = new WSASoap($dom, WSASoap::WSANS_2005);
Log::channel('soap-response')->info("Hit 3");
/** Add Addressing */
$objWSA->addFrom($options['wsa_addressing_from']);
$objWSA->addTo($options['wsa_addressing_to']);
$objWSA->addAction($options['wsaAction']);
/** Set needed soap header settings */
$objWSA->addMessageID();
$dom = $objWSA->getDoc();
/* Sign all headers to include signing the WS-Addressing headers */
$objWSSE = new WSSESoap($dom);
$objWSSE->signAllHeaders = true;
$objWSSE->addTimestamp();
/* create new XMLSec Key using RSA SHA256 and type is private key */
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type' => 'private'));
/* load the private key from file*/
if (isset($options['ssl_private_key_passphrase'])) {
$objKey->passphrase = $options['ssl_private_key_passphrase'];
}
$objKey->loadKey($options['ssl_private_key_path'], true);
/* Sign the message - also signs appropraite WS-Security items */
$objWSSE->signSoapDoc($objKey,
[
'algorithm' => XMLSecurityDSig::SHA256,
'insertBefore' => false,
]
);
/* Add certificate (BinarySecurityToken) to the message and attach pointer to Signature */
$token = $objWSSE->addBinaryToken(file_get_contents($options['ssl_cert_path']));
$objWSSE->attachTokentoSig($token);
/** NOTICE: Problem here! How to get correct type of headers to put into 'addSoapHeaders' of PHP's SoapServer */
$headers_xml = $objWSSE->saveXML();
$soap->addSoapHeaders(new SoapHeader("ns", $headers_xml, "value"));
return $soap;
}
(Please tell if this question is badly formatted or missing information, as this is my first time writing.)

Build correct SOAP Request Header (PHP)

I have to do requets to a SOAP API with PHP and I need the following SOAP-Header structure:
<soapenv:Header>
<ver:authentication>
<pw>xxx</pw>
<user>xxx</user>
</ver:authentication>
</soapenv:Header>
How can I build this header?
I tried
$auth = [
"ver:authentication" => [
"pw" => $this->pw,
"user" => $this->user
]
];
$options = [];
$options["trace"] = TRUE;
$options["cache_wsdl"] = WSDL_CACHE_NONE;
$options["compression"] = SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP;
$client = new SoapClient("www.my-url.com/wsdl", $options);
$header = new SoapHeader("www.my-url.com", "authentication", $auth, false);
$client->__setSoapHeaders($header);
but it does not work. The respons is "failure" which I get, when the header structure is incorrect...
please help
the solution could be object driven. In the following code an example is given. Please keep in mind, that the following code is not testet.
class Authentication
{
protected $user;
protected $pw;
public function getUser() : ?string
{
return $this->user;
}
public function setUser(string $user) : Authentication
{
$this->user = $user;
return $this;
}
public function getPw() : string
{
return $this->pw;
}
public function setPw(string $pw) : Authentication
{
$this->pw = $pw;
return $this;
}
}
The above shown class is a simple entity, which contains two properties $user fpr the username and $pw for the password. Further it contains the getter and setter functions for retrieving or setting the values for the two properties.
For the next step just fill the class with data and store it in a SoapVar object.
$authentication = (new Authentication())
->setUser('Username')
->setPw('YourEncodedPassword');
$soapEncodedObject = new \SoapVar(
$authentication,
SOAP_ENC_OBJECT,
null,
null,
'authentication',
'http://www.example.com/namespace'
);
As you can see above, your authentication class will be stored as soap var object. It is encoded as soap object. The only thing you have to do is setting the namespace for this object. In your given example it is ver:. With this namespace prefix somewhere in your wsdl file a namespace is noted. You have to find out this namespace url and just replace the example url http://www.example.com/namespace with the right url noted in your wsdl.
The next step is setting this as soap header. That 's quite simple.
try {
$client = new SoapClient('http://www.example.com/?wsdl', [
'trace' => true,
'exception' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
]);
// set the soap header
$header = new SoapHeader('http://www.example.com/namespace', 'authentication', $authentication, false);
$client->setSoapHeaders($header);
// send the request
$result = $client->someWsdlFunction($params);
} catch (SoapFault $e) {
echo "<pre>";
var_dump($e);
echo "</pre>";
if ($client) {
echo "<pre>";
var_dump($client->__getLastRequest());
echo "</pre>";
echo "<pre>";
var_dump($client->__getLastResponse());
echo "</pre>";
}
}
As you can see it 's a bit different from your given example. Instead of an array it 's the soap encoded authentication object, that is given to the soap header class. For failure purposes there is a try/catch block around your soap client. In that case you can identify the error and if the client was initiated correctly, you can also see the last request and last response in xml.
I hope, that I helped you. ;)
I would strongly advise you 2 things:
Use a WSDL to PHP generator in order to properly construct your request. In addition, it will ease you the response handling. Everything is then using the OOP which is much better. Take a look to the PackageGenerator project.
Use the WsSecurity project in order to easily add your dedicated SoapHeader without wondering how to construct it neither.

Webservice SOAP request - Wrong type of data sent

Context / What I want :
I'm facing an issue while calling a Webservice with SOAP. Here's an image the relevant part of the WS I want to call :
(I voluntarily hide the namespace part, not relevant here)
I want to send data through 'Demande_de_mot_de_passe' function and catch result from this request.
In the code below, this request is correct (the name of the function is good), I guess the problem is the formatting of the data I want to send. The call of the function is made with this part :
$client->Demande_de_mot_de_passe($soapVar);
What I've tried :
Here's the relevant part of the code I've tried ( I voluntarily change values of data but nothing else. There is no typo error with the brackets, it close the function and the class I didn't put here to keep the relevant part) :
$client = new \SoapClient('URL_OF_THE_WS?WSDL', array(
'trace' => 1,
'encoding' => 'UTF-8',
'soap_version' => SOAP_1_1,
'classmap' => array('Demande_de_mot_de_passe_Input' => 'Demande_de_mot_de_passe_Input')
));
$donnesUtilisateur = new Demande_de_mot_de_passe_Input;
$donnesUtilisateur->Code_societe = '000';
$donnesUtilisateur->Ident_type = 'A';
$donnesUtilisateur->Ident_code = 'xxxxxx';
$donnesUtilisateur->Dat_demande = '00000000';
$donnesUtilisateur->Adr_mail = 'xxxxxx';
$donnesUtilisateur->Adr_cpos = 'xxxxxx';
$donnesUtilisateur->Nom = 'xxxxxx';
$donnesUtilisateur->Prenom = 'xxxxxx';
$donnesUtilisateur->Dat_naiss = '00000000';
$namespace = 'URL_OF_NAMESPACE';
$soapVar = new \SoapVar($donnesUtilisateur, SOAP_ENC_OBJECT,'Demande_de_mot_de_passe_Input', $namespace);
$result = $client->Demande_de_mot_de_passe($soapVar);
print_r($result);
}
}
class Demande_de_mot_de_passe_Input {
public $Code_societe;
public $Ident_type;
public $Ident_code;
public $Dat_demande;
public $Adr_cpos;
public $Adr_mail;
public $Nom;
public $Prenom;
public $Dat_naiss;
}
I've also tried with passing array of casting an object with the array like this (without success) :
$donnesUtilisateur = [
'Code_societe' => '000',
'Ident_type' => 'A',
'Ident_code' => 'xxxxxx',
'Dat_demande' => '00000000',
'Adr_cpos' => 'xxxxxx',
'Adr_mail' => 'xxxxxx',
'Nom' => 'xxxxxx',
'Prenom' => 'xxxxxx',
'Dat_naiss' => '00000000',
];
and :
$donnesUtilisateur = (object) [
'Code_societe' => '000',
'Ident_type' => 'A',
'Ident_code' => 'xxxxxx',
'Dat_demande' => '00000000',
'Adr_cpos' => 'xxxxxx',
'Adr_mail' => 'xxxxxx',
'Nom' => 'xxxxxx',
'Prenom' => 'xxxxxx',
'Dat_naiss' => '00000000',
];
Error I get :
SoapFault: Did not receive a 'Demande_de_mot_de_passe_Input' object. in SoapClient->__call()
If I unterstand clearly, the formatting of data sent is not correct but when I try other way to send it, it still reporting the same error.
Docs I've read about without success :
http://www.fvue.nl/wiki/Php:_Soap:_How_to_add_attribute_to_SoapVar
http://grokbase.com/t/php/php-soap/066jkmcz2h/passing-objects-to-soap-server-complextype-classmap
EDIT
Here's a capture of the WS 'Demande_de_mot_de_passe' function call in SoapUI :
(Sorry for the long post, I hope it is clear enough, don't forget to ask about precisions if needed, thanks in advance for your help :) )
At your WSDL's type, there's a sequence named Demande_de_mot_de_passe which use a element named Demande_de_mot_de_passeRequest and not Demande_de_mot_de_passe_Input.
Your print from SoapUI describe the message request, but if it's document style, Demande_de_mot_de_passe is a type. On the other hand if it's RPC is the method name.
Starting if it's RPC you can do as showed below. You should use as native object as you can (SOAP will work better with they). A stdObject will be good enough:
$request = new stdClass();
$demande_de_mot_de_passeRequest->Code_societe = '000';
$demande_de_mot_de_passeRequest->Ident_type = 'A';
$demande_de_mot_de_passeRequest->Ident_code = 'xxxxxx';
$demande_de_mot_de_passeRequest->Dat_demande = '00000000';
$demande_de_mot_de_passeRequest->Adr_mail = 'xxxxxx';
$demande_de_mot_de_passeRequest->Adr_cpos = 'xxxxxx';
$demande_de_mot_de_passeRequest->Nom = 'xxxxxx';
$demande_de_mot_de_passeRequest->Prenom = 'xxxxxx';
$demande_de_mot_de_passeRequest->Dat_naiss = '00000000';
$request->Demande_de_mot_de_passeRequest = $demande_de_mot_de_passeRequest;
$response = $client->Demande_de_mot_de_passe($request);
If your SOAP binding is document, you just have to add a new upper level named Demande_de_mot_de_passe
/** The variable $demande_de_mot_de_passeRequest is created as above **/
$demande_de_mot_de_passe = new stdClass();
$demande_de_mot_de_passe->Demande_de_mot_de_passeRequest = $demande_de_mot_de_passeRequest;
$request->Demande_de_mot_de_passe = $demande_de_mot_de_passe;
$response = $client->Demande_de_mot_de_passe($request);
Your WSDL doesn't need a list/collections (it's not your case), so you don't need to create/parse variables with SoapVar. There's others examples that you can read about (one is mine, but it's in portuguese) and other is about the BOGUS node:
http://forum.imasters.com.br/topic/535213-enviar-xml-via-soap/?p=2137411
http://www.fischco.org/blog/2011/3/26/php-soapserver-objects-arrays-and-encoding.html

I need to sign outgoing soap requests

I am trying to send signed soap requests but seem to be missing something.
I tried the wso2 library but that won't work on ubuntu 14.04 with php7.0
I then tried this one : https://github.com/LinioIT/wse-php but keep getting
ERR_025: Verification failure: No signature in the WS-Security message for the configured soap actor/role ""! in SoapClient->__call()
I tested this in soapUI and got it working but can't translate it to PHP
Here's my code:
<?php
$wsdl = "/***/GeefOnderneming.wsdl";
$cafile = "/var/www/src/cert/CA_cat_inv.pem";
$location = "https://***.be/GeefOndernemingDienst-02.00";
$uri = "http://***.be";
$local_cert = "/var/www/src/cert/cert_priv_pub.pem";
$soap = new SoapWsController($wsdl, [
'local_cert' => $local_cert,
'cafile' => $cafile,
'location' => $location,
'uri' => $uri,
'connection_timeout' => 10,
]);
$theResponse = $soap->geefOnderneming(
$payload
);
SoapWsController is extended from the default SoapClient:
I am not sure wether the define's are correct, I've tried a lot of options
namespace Drupal\vlaio_dossiers\Controller;
use DOMDocument;
use SoapClient;
use XMLSecurity\WSSESoap;
use XMLSecurity\XMLSecurityKey;
define('PRIVATE_KEY', '/***/key_ecc_private.pem');
define('SERVICE_CERT', '/***/cert_priv_pub.pem');
define('CERT_FILE', '/***/certificate.pem');
class SoapWsController extends SoapClient {
public function __doRequest($request, $location, $saction, $version,$one_way = NULL)
{
$doc = new DOMDocument('1.0');
$doc->loadXML($request);
$objWSSE = new WSSESoap($doc);
/* add Timestamp with no expiration timestamp */
$objWSSE->addTimestamp();
/* create new XMLSec Key using AES256_CBC and type is private key */
$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'private'));
/* load the private key from file - last arg is bool if key in file (true) or is string (false) */
$objKey->loadKey(PRIVATE_KEY, true);
/* Sign the message - also signs appropiate WS-Security items */
$options = array("insertBefore" => false);
$objWSSE->signSoapDoc($objKey, $options);
/* Add certificate (BinarySecurityToken) to the message */
$token = $objWSSE->addBinaryToken(file_get_contents(CERT_FILE));
/* Attach pointer to Signature */
$objWSSE->attachTokentoSig($token);
$objKey = new XMLSecurityKey(XMLSecurityKey::AES256_CBC);
$objKey->generateSessionKey();
$siteKey = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type' => 'public'));
$siteKey->loadKey(SERVICE_CERT, true, true);
$options = array("KeyInfo" => array("X509SubjectKeyIdentifier" => true));
$objWSSE->encryptSoapDoc($siteKey, $objKey, $options);
$retVal = parent::__doRequest($objWSSE->saveXML(), $location, $saction, $version);
$doc = new DOMDocument();
$doc->loadXML($retVal);
$options = array("keys" => array("private" => array("key" => PRIVATE_KEY, "isFile" => true, "isCert" => false)));
$objWSSE->decryptSoapDoc($doc, $options);
return $doc->saveXML();
}
}
I have a similar problem as yours.
I finally solve it by using https://github.com/robrichards/xmlseclibs.
Please ensure that you are using the latest version of xmlseclibs.

PHP Soap is hell

If I run this
$HostTransactionInfo = new HostTransactionInfo(); // std Object
$HostTransactionInfo->SecurenetID = $cc->merchant->data[$this->name]['secure_net_id'];
$HostTransactionInfo->SecureKey = $cc->merchant->data[$this->name]['secure_key'];
$HostTransactionInfo->Test = self::TEST;
$securenet = new SoapClient(self::WSDL, array('features' => SOAP_SINGLE_ELEMENT_ARRAYS));
$host_trans_info = new SoapVar($HostTransactionInfo, SOAP_ENC_OBJECT);
var_dump($host_trans_info);
$save = $securenet->Process_Save($host_trans_info);
I receive this on every variation: "Server was unable to process request. ---> Object reference not set to an instance of an object."
My SoapClient::__getTypes() request gives me this:
array(
[2] => struct HostTransactionInfo {
string SecurenetID;
string SecureKey;
string Test;
}
[6] => struct Process_Save {
HostTransactionInfo oTi;
}
)
My SoapClient::__getFunctions() request gives me this:
array (
[2] => Process_SaveResponse Process_Save(Process_Save $parameters)
)
Does anyone have any clue as to what I'm doing wrong?
The error is returned by the securenet webservice. Why not contact their support?
But in any case, the server should return a more informative message than "Object reference not set to an instance of an object". The fact that their code dereferences null pointers when it gets some unexpected input doesn't bode well for something that's supposed to be a "secure" payment system.
Please check the XML request that is sent to the server and the XML response you get back:
// ...
$securenet = new SoapClient(self::WSDL, array(
'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
'trace' => true // that's important for the debugging methods to work
));
// ...
$save = $securenet->Process_Save($host_trans_info);
var_dump($securenet-> __getLastRequestHeaders());
var_dump($securenet-> __getLastRequest());
var_dump($securenet-> __getLastResponseHeaders());
var_dump($securenet-> __getLastResponse());
This will help to get you an overview of what's happening on the wire. If you can rule out any server-related problem, the error will most likely be related to a XML-SOAP-request that is not in the required format.
Hi this might be to late for the original asker, But for anyone who may have the same error ...
this is a trick i learned when i was working with M$.Net or C# (CVS) or any other flavor of M$ Soap servers... M$ changes something in the envelope and that is where things go wrong...
class MSSoapClient extends SoapClient {
function __doRequest($request, $location, $action, $version) {
$namespace = "http://tempuri.org/";
$request = preg_replace('/<ns1:(\w+)/', '<$1 xmlns="'.$namespace.'"', $request, 1);
$request = preg_replace('/<ns1:(\w+)/', '<$1', $request);
$request = str_replace(array('/ns1:', 'xmlns:ns1="'.$namespace.'"'), array('/', ''), $request);
// parent call
return parent::__doRequest($request, $location, $action, $version);
}
}
This will correct the envelope and correct the error in most cases... look at the variable $namespace = "http://tempuri.org/"; make sure this is correct based on the WSDL file
I dont know if this will fix the USER's error but it might help others with similar errors

Categories