I have a client call with a WSSE Security Header:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header><wsse:Security 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-7BCCD9337425FBA038149772606059420"><wsse:Username>USERNAME</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">PASSWORD</wsse:Password><wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">NONCE</wsse:Nonce><wsu:Created>2017-06-17T19:01:00.594Z</wsu:Created></wsse:UsernameToken></wsse:Security></soapenv:Header>
<soapenv:Body>
<ns:FUNCTION/>
</soapenv:Body>
</soapenv:Envelope>
As SoapServer I have a simple one:
// the SOAP Server options
$options=array(
'trace' => true
, 'cache_wsdl' => 0
, 'soap_version' => SOAP_1_1
, 'encoding' => 'UTF-8'
);
$wsdl = 'http://localhost/index.php?wsdl';
$server = new \SoapServer($wsdl, $options);
$server->setClass('ServerClass');
$server->handle();
$response = ob_get_clean();
echo $response;
As soon the property MustUnderstand = 1, then I become from the server the exception: Header not understood.
How to understand the header?
How to make the WSSE validation on the SoapServer side?
The solution is very tricky! I don't know why is this handled by this way from the SoapServer, but here is the solution:
class ServerClass {
public function Security($data) {
// ... do nothing
}
public function myFunction(){
// here the body function implementation
}
}
We need to define a function in our class, which is handling the soap request with the name of the header tag, which is holding the soap:mustUnderstand property. The function doesn't need to be implemented in some way.
That's all!
Mutatos' question / answer got me on the right track. I was working outside of a class structure so what worked for me was the following:
function Security($data)
{
$username = $data->UsernameToken->Username;
$password = $data->UsernameToken->Password;
//check security credentials here
}
$server = new SoapServer("schema/wsdls/FCI_BookingPullService.wsdl", array('soap_version' => SOAP_1_2));
$server->addFunction("Security");
$server->handle();
Essentially a function with the same name as the SOAP header "<wsse:Security>" (ignore the namespace) is being defined, then telling the server to use that to process the header with the 'addFunction' method.
Not ideal from a scope point of view, if that's an issue, try the class approach.
Related
I am trying to connect to a SOAP webservice using php. I am very new to using php.
I can connect to the service, the test below returns a list of all the available functions of the webservice.
$url = "http://...client_ip.../dkServiceDefault/dkWSItemsCGI.exe/wsdl/IItemService";
$client = new SoapClient($url);
var_dump($client->__getFunctions());
If I try to access one of these functions(ex. NumberOfModifiedItems) then I get an error stating that I need to supply a SOAP header with a username and password.
According to the documentation of the SOAP service the header needs to look like this:
<soap:Header>
<q1:BasicSecurity id="h_id1" xmlns:q1="urn:dkWSValueObjects">
<Username xsi:type="xsd:string">username</Username>
<Password xsi:type="xsd:string">password</Password>
</q1:BasicSecurity>
</soap:Header>
How can I make this header in php? How do I attach it to the SoapClient? I have a username and password but I can't figure out how to create the exact header to send to the webservice. I have tried following several tutorials, but I just can't seem to get it to work.
You may pass SOAP headers with SoapHeader class and SoapClient::__setSoapHeaders method:
<?php
$url = "http://...client_ip.../dkServiceDefault/dkWSItemsCGI.exe/wsdl/IItemService";
$client = new SoapClient($url);
$namespace = "urn:dkWSValueObjects";
$authentication = array(
'Username' => 'yourname',
'Password' => 'yourpassword'
);
$header = new SoapHeader($namespace, 'BasicSecurity', $authentication, false);
$client->__setSoapHeaders($header);
var_dump($client->__getFunctions());
?>
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.
I have to implement a SOAP Web Service using PHP.
I did it by using the SoapServer class and all works fine.
I need to use a specific format for the request: they have to contain a "Header" tag with an "Authentication" tag in which there is a token that I have to use to authenticate the client that performed the request.
I used "file_get_contents('php //input')" to get the entire request that I received and then parsed it to retrieve the token that I needed.
This works fine if I try to simulate a SOAP request by using SoapUI. But, if I try to do the request by using PHP SoapClient and use the function SoapHeader to set the header, on the server side "file_get_contents('php //input')" returns only the fields of the entire request (contained in the XML tags of the XML request) merged together in a string, instead of returning the entire XML in a string format.
I cannot understand why.
The SoapServer class isn 't well documented in the PHP documentation. The SoapServer class does everything that you have in mind completely automatically. You have to use a decorator class. What a decorator is and what it does I 'll explain in the next lines. I 'm trying to give you a push in the right direction.
A while ago I had to implement the WSSE authentication standard. I 'll take some parts from the WSSE standard for this example.
The incoming request had a header that looked like this ...
<soapenv:Header>
<wsse:Security xmlns:wsc="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsc:SecurityContextToken>
<wsc:Identifier>identifier</wsc:Identifier>
</wsc:SecurityContextToken>
</wsse:Security>
</soapenv:Header>
The key (identifier) identifies an authorized user to perform a function of the web service. In this sense, we must check that the key is valid before executing any function. For this purpose we need a decorator class, that is executed before the actual function is executed.
class AuthDecorator
{
/**
* Name of the class, which contains the webservice methods
* #var string
*/
protected $class;
/**
* Flag, if the recieved identifier is valid
* #var boolean
*/
protected $isValid = false;
public function getClass() : string
{
return $this->class;
}
public function setClass($class) : AuthDecorator
{
$this->class = $class;
return $this;
}
public function getIsValid() : bool
{
return $this->isValid;
}
public function setIsValid(bool $isValid) : AuthDecorator
{
$this->isValid = $isValid;
return $this;
}
public function __call(string $method, array $arguments)
{
if (!method_exists($this->class, $method)) {
throw new \SoapFault(
'Server',
sprintf(
'The method %s does not exist.',
$method
)
);
}
if (!$this->getIsValid()) {
// return a status object here, wenn identifier is invalid
}
return call_user_func_array(
[ $this->class, $method ],
$arguments
);
}
/**
* Here 's the magic! Method is called automatically with every recieved request
*
* #param object $security Security node form xml request header
*/
public function Security($security) : void
{
// auth against session or database or whatever here
$identifier = $this->getIdentifierFromSomewhereFunc();
if ($security->SecurityContextToken->Identifier == $identfier) {
$this->setIsValid(true);
}
}
}
That 's the decorator class. Looks easy, hm? The decorator contains a class named like the first child of the xml header of the recieved request. This method will be executed automatically every time we recieve a request with the soap server. Beside that the decorator checks, if the called soap server function is available. If not a soap fault is thrown that the soap client on the consumer side recieves. If a method exists is quite easy, too. Every webservice method we put in a class.
class SimpleWebservice
{
public function doSomeCoolStuff($withCoolParams) : \SoapVar
{
// do some fancy stuff here and return a SoapVar object as response
}
}
For illustrative purposes, our web service just has this one function.
But how the hell we bring the decorator to work with the soap server?
Easy, mate. The SoapServer class has some pretty tricky functionality, that is not documented. The class has a method called setObject. This method will do the trick.
$server = new \SoapServer(
$path_to_wsdl_file,
[
'encoding' => 'UTF-8',
'send_errors' => true,
'soap_version' => SOAP_1_2,
]
);
$decorator = new AuthDecorator();
$decorator->setClass(SimpleWebservice::class);
$server->setObject($decorator);
$server->handle();
That 's awesome, right? Just initializing the SoapServer class, add the decorator with the setObject method and run it with the handle method. The soap server recieves all requests and before calling the webservice method the decorator will check, if the identifier is valid. Only if the identifier is valid, the called webservice method will be executed.
How 's the soap client request looking?
On the other side the soap client can look like this ...
$client = new SoapClient(
$path_to_wsdl_file,
[
'cache_wsdl' => WSDL_CACHE_NONE,
'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
'exceptions' => true,
'trace' => true,
]
);
$securityContextToken = new \stdClass();
$securityContextToken->Identifier = 'identifier';
$securityContextToken = new \SoapVar(
$securityContextToken,
SOAP_ENC_OBJ,
null,
null,
'SecurityContextToken',
'http://schemas.xmlsoap.org/ws/2005/02/sc'
);
$security = new stdClass();
$security->SecurityContextToken = $securityContextToken;
$security = new \SoapVar(
$security,
SOAP_ENC_OBJ,
null,
null,
'Security',
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
);
$header = new \SoapHeader(
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd',
'Security',
$security
);
$client->__setSoapHeaders($header);
$result = $client->doSomeCoolStuff(new \SoapParam(...));
Conclusion
When working in an object orientated context the SoapServer and SoapClient classes are pretty cool. Because the documentation doesn 't really give much about both classes, you have to test and learn. You can easily create a SOAP webservice when you know how. Without writing any xml as a string.
Before you productively use the code examples seen here, please make sure that they are only examples and not intended for productive use. The shown examples should push you in the right direction. ;)
Questions?
I need to send the following SOAP request
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:dat="http://touricoholidays.com/WSDestinations/2008/08/DataContracts">
<soapenv:Header>
<dat:LoginHeader>
<dat:username>myUserName</dat:username>
<dat:password>myPassword</dat:password>
<dat:culture>en_US</dat:culture>
<dat:version>8</dat:version>
</dat:LoginHeader>
</soapenv:Header>
<soapenv:Body>
<dat:GetDestination>
<dat:Destination>
<dat:Continent>Europe</dat:Continent>
<dat:Country>Spain</dat:Country>
<dat:State></dat:State>
<dat:City>Madrid</dat:City>
<dat:Providers>
<dat:ProviderType>Default</dat:ProviderType>
</dat:Providers>
</dat:Destination>
</dat:GetDestination>
</soapenv:Body>
</soapenv:Envelope>
I am trying to achieve this using PHP's built in SoapClient Class. When I run the following code it says "Login failure please check user name and password." But I am very much sure that both the username and password are correct as the same values are being used in other applications.
I think the problem is in the code below. Could you please tell me what is the mistake ?
try{
$client = new SoapClient($soap_url, array("trace" => 1));
$ns = 'http://touricoholidays.com/WSDestinations/2008/08/DataContracts';
$auth = array(
'username' => 'myUserName',
'password' => 'myPassword',
'culture' => 'en_US',
'version' => '8',
);
$header = new SoapHeader($ns, 'LoginHeader', $auth);
$client->__setSoapHeaders($header);
$res = $client->__soapCall("GetDestination", array());
var_dump($res);
}
catch(Exception $e)
{
echo $e->getMessage();
}
you should definitively use a WSDL to php generator that still uses the native SoapClient class such as the PackageGenerator project. It simply generates the PHP SDK according to the WSDL. Then you only have to use the generated classes to construct and send your request. The response is then an object using the generated classes.
I'm generating a WSDL and I need to change the function name in the response to match the name from the client (which I have no control over).
Here's the WSDL response I'm getting:
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost:8000/soap/index.php?wsdl" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:fooFunctionResponse>
<return xsi:type="xsd:boolean">true</return>
</ns1:fooFunctionResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I need the line <ns1:fooFunctionResponse> to read <ns1:fooFunctionAcknowledgement>.
Here is my Soap server:
if (isset($_GET['wsdl'])) {
ini_set('soap.wsdl_cache_enabled', 0);
$soapAutoDiscover = new \Zend\Soap\AutoDiscover(new \Zend\Soap\Wsdl\ComplexTypeStrategy\ArrayOfTypeSequence());
$soapAutoDiscover->setBindingStyle(array('style' => 'document'));
$soapAutoDiscover->setOperationBodyStyle(array('use' => 'literal'));
$soapAutoDiscover->setClass('SoapFunction');
$soapAutoDiscover->setUri(http://localhost:8000/soap/index.php);
$soapAutoDiscover->handle();
} else {
$soap = new \Zend\Soap\Server(null, array("soap_version" => SOAP_1_2, 'uri' => 'http://localhost:8000/soap/index.php?wsdl', 'classmap' => array('Identification', 'RemoteIdentification')));
$soap->setClass('SoapFunction');
$soap->handle();
}
I've found this line of code in the Zend framework (Autodiscover.php line 514) which looks like it controls the naming of the function:
$element = [
'name' => $functionName . 'Response',
'sequence' => $sequence
];
But changing it does nothing at all, the parent method is never called. I've no idea how to solve this problem, please help.
I've discovered that this line does change the function name, however I'm using SoapUI to test my API, and from SoapUI I always see Response instead of whatever I change the string to. In Chrome, I see Acknowledgement.
Why does SoapUI show a different function name?
I don't know much about Zend Framework and/or it's structure. But I think you can simply extend the class containing the response() method and overwrite the method.
// Example Zend Class
class Zend_Class {
public function response()
{
// ...
}
}
// Your own Class
class Own_Class extends Zend_Class
{
public function ResponseBoohoo()
{
return parent::response();
}
}
If I totally and utterly misunderstood you, I apologize. :)