NTML Authentication with PHP to Navision SOAP webservice - php

I'm currently developing a payment gateway that has to send the order to Navision where it will be managed. In the webservice the NTML authentication is enabled so first it is necessary to extend the native class SoapClient. For it I have found enough documentation in the web https://thomas.rabaix.net/articles/using-soap-php-with-ntlm-authentication that allows to extend this native class.
Now the code exposed in that post does not return me the xslm first.
In this case this would be my code
define("USERPWD", "user:password");
require_once("NTLMStream.php");
require_once("NTLMSoapClient.php");
stream_wrapper_unregister('http');
stream_wrapper_register('http', 'NTLMStream') or die("Failed to register protocol");
// Initialize Soap Client
$url = "http://ipaddress:port/DynamicsNAV1_test/WS/enterprise/Codeunit/SalesEnterprise?WSDL";
$uri = "urn:microsoft-dynamics-schemas/codeunit/SalesEnterprise";
$params = [
'stream_context' => stream_context_create([
'ssl' => [
'verify_peer'=>false,
'verify_peer_name'=>false,
'allow_self_signed'=>true,
]]),
'cache_wsdl'=> WSDL_CACHE_NONE,
'trace' => 1,
];
$client = new NTLMSoapClient($url, $params);
stream_wrapper_restore('http');
As you can see I have dispensed with the classes used by this author to define the credentials.
inally the code returns the following error:
SOAP Fault: (faultcode: WSDL, faultstring: SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://ipaddress:port/DynamicsNAV1_test/WS/Enterprise/Codeunit/SalesEnterprise?WSDL' : Document is empty).
I will be happy to provide more information if needed. Many thanks in advance!

The first 2 responses you get from an NTLM handshake are 401's with no body, the 3rd response if successfully authenticated will contain the 200 and response.
https://techcommunity.microsoft.com/t5/iis-support-blog/windows-authentication-http-request-flow-in-iis/ba-p/324645
Update 30/1/21
Its been a while since ive used Php, but id start with this post that shows how to manually do the NTLM curl from the shell then how to repeat that same command from php curl - curl with ntlm authentication works in command line but not inside php
Also there are some nuggets in the top few answers of this SO search: https://stackoverflow.com/search?q=NTLM+PHP+curl

I had this issue with the same SOAP exception, but the code similar to yours was working fine with NAV2013. The company I'm working for upgraded to NAV2020 and then the SOAP Exception appeared. After countless hours of back and forward of refactoring, and researching the web, the solution is on the Microsoft documentation... The issue is that in the original code for NTLMSoapClient class you have to change the following line:
'Content-Type: text/xml; charset=UTF-8'
Which is under the variable $headers in the method processRequest to:
'Content-Type: text/xml; charset=ISO-8859-1'
At some point Dynamics API is no longer accepting credentials with Unicode characters. I hope this works for you as it did for me.

Related

Set Content-type header to Elasticsearch-php client

I'm trying to use Elasticsearch-php version 5.0 to send search queries to Elasticsearch 6.4.2.
One of the breaking changes from 5.0 to 6.0 is that there is the "strict content type validation"
which means that requests to Elasticsearch must sent with "Content-type: application/json" header.
In order to add this header, I tried to use polyfractal's suggestion from this thread:
$params = [
'index' => $index,
'type' => $mapping,
'body' => $query,
'client' => [
'curl' => [CURLOPT_HTTPHEADER => array('Content-type: text/plain')]
]
];
$res = $this->mESClient->search($params); // this is Elasticsearch/Client
return $res;
but for some reason, I keep getting "Notice: Array to string conversion" when the code tries to do curl_setopt_array(), and the request is net sent.
Please note: that when I remove the 'client' part of the $params array the request is being received in the Elasticsearch.
According to Version Matrix, you should use elasticsearch-php 6.0 when dealing with ES >=6.
elasticsearch-php 5.0 is not compatible with ElasticSearch 6.
The thread you mentioned, relates to ES-PHP 1.x/2.x, which may have different syntax for options. It's not relevant for your situation, except that one of comments says the same that I just did above.
FYI, if you're using Elasticsearch 6.0+, you need to upgrade your ES-PHP client to the 6.0 branch too. ES-PHP 6.0+ sets the content-type headers automatically: fd3b0f1
Found the problem. It seems that there is a buggy functionality when trying to set HTTP headers using curl and specifying authorization details in the URL.
for example:
https://username:password#host:port
For some reason the client copies the curl http headers into other curl options (that requires string and not array) and, therefore, the exception of Array to string conversion thrown from curl_setopt_array.
When I removed the authorization details from the host URL and used the curl authorization header everything worked.

SOAP client timeouts during function execution

I need to use SOAP to retrieve some data from a database. I'm not an experienced PHP programmer, that's why I need some help. The company which provides the webservice (WSDL) gave me login info and links to the svc and wsdl files. They also gave me an example in C# of how to connect:
var proxy = new ChannelFactory<ServiceReferenceWCF.IWebService2>("custom");
proxy.Credentials.UserName.UserName = login;
proxy.Credentials.UserName.Password = pass;
var result = proxy.CreateChannel();
var logged_in = result.loggedIn();
Here's my PHP code:
$wsdl_proto = 'https';
$wsdl_host = 'their_wsdl_host';
$wsdl_host_path = 'their_wsdl_path';
$namespace_proto = 'https';
$namespace_host = 'their_namespace_host';
$namespace_path = 'their_namespace_path';
$location = $namespace_proto.'://'.$namespace_host.$namespace_path;
$wsdl_url = $wsdl_proto.'://'.$wsdl_host.$wsdl_host_path;
$connection = new SoapClient($wsdl_url, array('location' => $location, 'soap_version' => SOAP_1_1, 'connection_timeout'=> 600,
'proxy_login' => "my_login", 'proxy_password' => "my_password"));
$functions = $connection->__getFunctions();
var_dump($functions);
$logged_in = $connection->loggedIn();
It hangs during the loggedIn() function call. This function is listed in the $functions variable, so it is valid. I tried some other functions provided by the service - the result is always the same: the script simply freezes. And by that I mean there is no response from the service and PHP waits for the loggedIn() function to finish. After it exceeds the timeout, I get an error: Error Fetching http headers in...
What am I doing wrong? How can I debug it?
UPDATE:
I tried every single thing you guys suggested. But I still didn't manage to solve the problem. I don't use a proxy. You can find the results below:
1. I installed the SoapUI. After configuring the request for the some_method function (creating a basic Auth with credentials) I received a response: An error occurred when verifying security for the message.
Ticking the Authenticate pre-emptively option didn't help. I searched for a solution to this error, but I didn't find anything.
2. I tried almost every imaginable combination of options for the SoapClient class. Here are some of them:
$connection = new SoapClient($wsdl_url, array(
'login' => "login",
'password' => "pass",
'trace' => 1,
));
The response headers are empty. The request headers:
REQUEST HEADERS:
POST /file.svc HTTP/1.1
Host: host
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.6.16
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://tempuri.org/file/some_method"
Content-Length: 221
Authorization: Basic HASH
REQUEST:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://tempuri.org/"><SOAP-ENV:Body><ns1:some_method/></SOAP-ENV:Body></SOAP-ENV:Envelope>
Next combination:
$connection = new SoapClient($wsdl,array(
'login' => "login",
'password' => "pass",
'trace' => 1,
'connection_timeout' => 500000,
'cache_wsdl' => WSDL_CACHE_BOTH,
'keep_alive' => false,
));
The response headers are empty. The request headers are the same, as before.
3. Using this:
$connection->__setLocation('https://host.org/file.svc');
doesn't help. However when I set the location to the WSDL file instead of the SVC file, I get the following response:
HTTP/1.1 405 Method Not Allowed
Connection: Keep-Alive
Content-Length: 1293
Date: Wed, 23 Dec 2015 14:28:53 GMT
Content-Type: text/html
Server: Microsoft-IIS/7.5
Allow: GET, HEAD, OPTIONS, TRACE
X-Powered-By: ASP.NET
I'm sure that the WSDL service is not slow enough, to exceed the timeout (Ricardo Velhote suggested it).
4. I've got an XML configuration file provided with the C# example I mentioned earlier:
<client>
<endpoint address="https://host/file.svc" binding="customBinding" bindingConfiguration="custom" contract="ServiceReference1.file" name="custom" />
</client>
<bindings>
<customBinding>
<binding name="custom">
<security defaultAlgorithmSuite="Default" authenticationMode="UserNameOverTransport" requireDerivedKeys="true" includeTimestamp="true" messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
<localClientSettings detectReplays="false" />
<localServiceSettings detectReplays="false" />
</security>
<textMessageEncoding messageVersion="Soap11WSAddressing10" />
<httpsTransport maxReceivedMessageSize="2147483647" maxBufferPoolSize="2147483647" />
</binding>
</customBinding>
</bindings>
I tried to extend the SoapClient class as suggested here, but as you can guess it didn't work - the behaviour of the script is still the same.
According to your update and the XML configuration file provided with the example this Webservice appears to be using WS-Addressing so the regular SOAPClient will not work and requires it to be extended in order to support WS-Addressing.
This is the line that gives it away
<textMessageEncoding messageVersion="Soap11WSAddressing10" />
I have yet to use WS-Addressing but I was recently analysing an API for project we will be working on in the future that requires it.
Please take note of this project or this other project which may be useful (it's easy to search for more PHP WS-Addressing).
Again, I have only done research and do not have any hands-on experience to help you with actual code :)
[EDIT: Obsolete answer after the update]
First of all, you might be being mislead by the use of the variable proxy in the example code. They are probably referring to HTTP Basic Authentication and not a proxy.
Try replacing proxy_login and proxy_password with login and password.
However, having said that, if you are getting the WSDL it means that at least it's connecting and obtaining the information about the service (which is good).
In normal situations you do not need to specify location in SoapClient as it should be defined by the WSDL file. By setting the location parameter you are overriding what is set in the WSDL file and you may be pointing it to a location that does not exist.
Try ommiting the location and soap_version from the SoapClient constructor and let the library handle those parameter automatically:
$connection = new SoapClient($wsdl_url, array('connection_timeout'=> 600,
'proxy_login' => "my_login", 'proxy_password' => "my_password"));
On the other hand, perhaps you are dealing with an extremely slow Web Service. There are many parameters in PHP that may be affecting the time it takes to timeout and most likely they are well below your connection_timeout parameter:
default_socket_timeout
max_execution_time
You have missed proxy_host and proxy_port in the options... If you need proxy to work provide theese parameters:
....
$connection = new SoapClient($wsdl_url, array(
'location' => $location,
'soap_version' => SOAP_1_1,
'connection_timeout'=> 600,
'proxy_host' => '....', // Your proxy host
'proxy_port' => 8080, // Your proxy port
'proxy_login' => "my_login",
'proxy_password' => "my_password"
));
Your first action when coding in the language you don't really know should be using manuals and examples.
Here is a link to PHP Manual on SoapClient - http://php.net/manual/en/soapclient.soapclient.php
For debugging SoapClient you can pass "trace" argument. Quote from the manual: Setting the boolean trace option enables use of the methods SoapClient->__getLastRequest, SoapClient->__getLastRequestHeaders, SoapClient->__getLastResponse and SoapClient->__getLastResponseHeaders.
$client = new SoapClient("some.wsdl", array('trace' => true));
So, in your case if you want to see what is going wrong do this:
$client = SoapClient($wsdl_url, array('trace' => 1));
$result = $client->SomeFunction();
echo "REQUEST HEADERS:\n" . $client->__getLastRequestHeaders() . "\n";
echo "REQUEST:\n" . $client->__getLastRequest() . "\n";
echo "Response headers:\n" . $client->__getLastResponseHeaders() . "\n";
echo "Response:\n" . $client->__getLastResponse() . "\n";
Also, as was noted in another answer - you are setting proxy_login and proxy_password parameters in the request, which should only be used if you are using proxy server to connect to that WSDL service.

Invoking WCF service with PHP (with federated security)

I’m trying to invoke a WCF service (.NET) from PHP. It’s a little more complicated than just using a SoapClient since the service uses a WS2007FederationHttpBinding to authenticate.
Here’s the code I’m using at the moment. I haven’t even added credentials as I’m not sure how, but regardless, I’m not even at the point where I’m getting access denied errors.
$wsdl = "https://slc.centershift.com/sandbox40/StoreService.svc?wsdl";
$client = new SoapClient($wsdl,array(
//'soap_version'=>SOAP_1_2 // default 1.1, but this gives 'Uncaught SoapFault exception: [HTTP] Error Fetching http headers'
));
$params = array();
$params['SiteID'] = 123;
$params['GetPromoData'] = false;
$ret = $client->GetSiteUnitData(array('GetSiteUnitData_Request'=>$params));
print_r($ret);
Which WSDL should I be pointing to?
https://slc.centershift.com/Sandbox40/StoreService.svc?wsdl
Seems to be very short, but includes a reference to (note the wsdl0) https://slc.centershift.com/Sandbox40/StoreService.svc?wsdl=wsdl0
https://slc.centershift.com/Sandbox40/StoreService.svc?singleWsdl
Seems to have everything in it.
Do I need to specify SOAP 1.2? When I do, I get a connection timeout ([HTTP] Error Fetching http headers). When I don’t, the default of SOAP 1.1 is used and I get a [HTTP] Cannot process the message because the content type 'text/xml; charset=utf-8' was not the expected type 'application/soap+xml; charset=utf-8'. Is this because I’m not authenticated yet, or because I’m using the wrong SOAP version?
How to authenticate in PHP? Here’s the corresponding .NET/C# code. Do I need to somehow put these as SOAP headers? Or am I thinking about it all wrong, and I need to do some kind of authentication before I even call the method (from what I read, I’m supposed to get a token back and then use it for all future method calls – I think I see an example of this in an answer here on Stack Overflow.
If I call $client->__getFunctions(), using either WSDL and either SOAP version, I’m getting a valid list of all functions, so I assume either of these is fine and my real issue is the authentication.
Other programmers I’ve talked to had spent time trying to get this to work, but gave up and instead implemented a proxy in .NET. They pass their parameters from PHP to their own unsecured .NET service, which in turn calls this secure service. It works, but seems crazily inefficient to me, and counter-productive, as the purpose of WCF is to support all types of clients (even non-HTTP ones!).
I’ve read How to: Create a WSFederationHttpBinding on MSDN, but it didn’t help.
You can use this URL for WSDL https://slc.centershift.com/Sandbox40/StoreService.svc?singleWsdl. This WSDL has all definitions.
You have to use 1.2 because this webservice works with SOAP 1.2 version. I tried it with 1.1 and 1.2 and both of them gived error. 1.1 is version error, 1.2 is timeout error. I think there is an error at this test server. I used it with svcutil to generate code but it gived error too. Normaly it should get information and generate the code example to call service.
Normally you can add authenticate parameters with SoapHeader or directly add to options in SoapClient consruct (if service authentication is basic authentication). I write below code according to your screenshot. But it gives timeout after long wait.
$wsdl = "https://slc.centershift.com/sandbox40/StoreService.svc?wsdl";
$client = new SoapClient($wsdl,array('trace' => 1,'soap_version' => SOAP_1_2));
$security = array(
'UserName' => array(
'UserName'=>'TestUser',
'Password'=>'TestPassword',
'SupportInteractive'=>false
)
);
$header = new SoapHeader('ChannelFactory','Credentials',$security, false);
$client->__setSoapHeaders($header);
$params = array();
$params['SiteID'] = 100000000;
$params['Channel'] = 999;
try {
$ret = $client->GetSiteUnitData($params);
print_r($ret);
}catch(Exception $e){
echo $e->getMessage();
}
__getFunctions works, because it prints functions defined in WSDL. There is no problem with getting WSDL information at first call. But real problem is communication. PHP gets WSDL, generates required SOAP request then sends to server, but server is not responding correctly. SOAP server always gives a response even if parameters or request body are not correct.
You should communicate with service provider, I think they can give clear answer to your questions.
Having worked with consuming .NET WS from PHP before I believe you would need to create objects from classes in PHP that matches the names that .NET is expecting. The WSDL should tell you the types it is expecting. I hope this assist with your path forward!
If the SOAP call works from a C# application, you could use Wireshark (with the filter ip.dst == 204.246.130.80) to view the actual request being made and then construct a similar request from php.
Check this answer to see how you can do a custom SOAP call.
There's also the option of doing raw curl requests, since it might be easier to build your xml body, but then you would have to parse the response yourself with simplexml.

How to make PHP Soap 1.1 call with non-default Content-Type

I need to make call to SOAP 1.1 web service in PHP. However, one of requirements in order to work is that I must send Content-Type=application/soap+xml. Now, I know that these are differences:
SOAP 1.2 -> Content-Type: application/soap+xml
SOAP 1.1 -> Content-Type: text/xml
I need to use SoapClient with WSDL to do this. However, I couldn't find how to set Content-Type after I set version to SoapClient to 1.1
Could someone provide example or code snippet?
Thank you!
There are a number of headers that are ignored when you try to set them in a stream_context, and will never be used.
Check the underlying C source file soap/php_http.c and search for "skip some predefined headers" for a list.
This is the reason some people report problems trying to set Host, Authorization, Content-Type and other headers in certain situations.
You can supply a stream context in the SoapClient options.
$ctx_opts = array(
'http' => array(
'header' => 'Content-Type: application/soap+xml'
)
);
$ctx = stream_context_create($ctx_opts);
$soapClient = new SoapClient('your.wsdl', array('stream_context' => $ctx));

Collecting/Processing headers in PHP Soap Server

I'm creating a web service using PHP5's native SOAP Methods. Everything went fine until I tried to handle authentication using SOAP Headers.
I could easily find how to add the username/password to the SOAP headers, client-side:
$myclient = new SoapClient($wsdl, $options);
$login = new SOAPHeader($wsdl, 'email', 'mylogin');
$password = new SOAPHeader($wsdl, 'password', 'mypassword');
$headers = array($login, $password);
$myclient->__setSOAPHeaders($headers);
But I can't find anywhere the methods for collecting and processing these headers server-side. I'm guessing there has to be an easy way to define a method in my SoapServer that handles the headers...
With a modern PHP version it is NOT necessary to add anything to the WSDL as the headers are part of the SOAP Envelope specification.
The user contributed example cited by Paul Dixon does not work simply because the header is not UserToken as written in the comment, the header is Security, so that's is the name the class method should have. Then you get a nice stdClass object with a UserToken stdClass object property that has Username and Password as properties.
Example code (to be inserted in a PHP class that implements the SOAP service:
public function Security( $header ){
$this->Authenticated = true; // This should be the result of an authenticating method
$this->Username = $header->UsernameToken->Username;
$this->Password = $header->UsernameToken->Password;
}
Works like a charm for Username/Password based WSSE Soap Security
SoapClient uses the username and password to implement HTTP authentication. Basic and Digest authentication are support (see source)
For information on implementing HTTP authentication in PHP on the server side, see this manual page.
If you don't want to use HTTP authentication, see this user-contributed sample on the SoapServer manual page which shows how you could pass some credentials in a UsernameToken header.
You can try reading RAW POST data.
if ( $_SERVER['REQUEST_METHOD'] == 'POST' )
{
$xml = file_get_contents('php://input');
print( htmlspecialchars( $xml ) );
// XML processing
}
In $xml you will have the whole SOAP XML request.
SoapServer does not have methods for reading SOAP headers.
--
edit: contributed example from manual does not seem to work, header handling method never gets called
You have to use a current version of PHP. With PHP 5.2.4 I had the same problem, but with 5.2.17 or 5.3.8 the callback for SOAP header handling (described in the user-contributed samle on php.net) gets called and everything works pretty fine.

Categories