I am using Phil Sturgeon's REST Controller to build an API. API authentication is performed using API keys. Presently there is only one key defined in the api_keys table on the database and I have set-up my client to access the API using this key via the following cURL request:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uri);
//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
//curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
curl_setopt($ch, CURLOPT_MAXREDIRS, 1);
curl_setopt($ch, CURLOPT_HEADER, TRUE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 45);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/html; charset=utf-8',
'Accept: application/html',
'X_API_PREFIX: ' . $this->apiPrefix(),
'X_API_KEY: ' . $this->apiKey(),
"User-Agent: ShowHouse/" . ShowhouseClient::API_CLIENT_VERSION . '; PHP ' . phpversion() . ' [' . php_uname('s') . ']';
'Accept-Language: ' . $this->_acceptLanguage
));
curl_setopt($ch, CURLOPT_USERPWD, $this->apiKey());
if ('POST' == $method)
{
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
else if ('PUT' == $method)
{
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
else if('GET' != $method)
{
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
}
$response = curl_exec($ch);
However I keep getting an invalid API key response back from the API server. The issue appears to be that on the following line in the REST_Controller.php:
if (($key = isset($this->_args[$api_key_variable]) ? $this->_args[$api_key_variable] : $this->input->server($key_name)))
both
$this->_args[$api_key_variable]
and
$this->input->server($key_name)
are not actually set. 3 of us in the office have exactly the same code based checked out from the source control repository, the only difference being two of us are running Apache 2.4.4 and the other is running Apache 2.2.24. Both of us running 2.4.4 keep getting an invalid API key error but it all works fine for the guy running 2.2.24 which would suggest it's an Apache issue but we just can't get to the bottom of it.
Anyone any ideas why this would be happening like this?
Thanks in advance.
Found the issue for anyone else who may have the same problem. Apache 2.4.x now enforces stricter translation of header to environment variables to mitigate against some cross-site scripting attacks via header injection. See:
http://httpd.apache.org/docs/trunk/new_features_2_4.html
"Headers containing invalid characters (including underscores) are now silently dropped."
Related
I've been stuck with this problem for a little while now. I am trying to use a REST API to change certain settings of a user like clear a user and set their device to inactive.
The REST calls are made in php which I am pretty new to. Most calls (get and post) are working just fine so I think I understood the basic concept of php and curl but I just can't get put requests working. The problem is that when making the REST call I get a status code 200 in return indicating that everything went fine but when I check the database nothing changed and the device is still active.
I've spent several hours researching this problem here on stackexchange (cURL PUT Request Not Working with PHP, Php Curl return 200 but not posting, PHP CURL PUT function not working) and additionally reading various tutorials.
To me my code looks fine and makes perfect sense because it is similar to many examples I found online. So please help me find my mistake.
$sn = "123456789";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com/api/sn/".$sn);
$data = array("cmd" => "clearUser");
$headers = array(
'Accept: application/json',
'Content-Type: application/json'
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$username = 'XXX';
$password = 'XXX';
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($data));
$output = curl_exec($ch);
curl_close($ch);
So far as I can see, you have two problems in your code.
Content-Type: application/json is incorrect, I would remove it entirely.
You don't have a Content-Length header.
I suggest trying
$sn = "123456789";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com/api/sn/".$sn);
$data = array("cmd" => "clearUser");
$httpQuery = http_build_query($data);
$headers = array(
'Accept: application/json',
'Content-Length: ' . strlen($httpQuery)
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$username = 'XXX';
$password = 'XXX';
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS,$httpQuery);
$output = curl_exec($ch);
curl_close($ch);
You define in the Header 'Content-Type: application/json'. Try to encode your $data to json and then transfer the jsonEncodeteData:
$dataJson = json_encode($data);
curl_setopt($ch, CURLOPT_POSTFIELDS, $dataJson);
perhaps this helps already.
status 200 may not be a success in the case of a PUT request. In a correct semantics (correct implementation of the server) successful PUT returns "201 Created" and, if the client sends empty or somehow wrong content, the server returns "204 No Content".
Lazy programmers may just return "200 Ok" instead of 204 with the meaning "your request is fine, but there is nothing to do with the data".
Try to verify your data and make sure that you send something that is not empty and is conformal to the API specs.
I am trying to send a list of users to the Xerox for network accounting purposes.
I successfully do this with 2 Xerox models (XEROX WorkCentre 7556 and XEROX WorkCentre 7830), following sudo code in PHP:
function SendUsersToXerox($ip,$users)
{
$url="{$ip}/acct/set_auth";
$auth='account:jbaserve';
$post="+aaav1.0\n+purge\n";
foreach ($users as $u)
{
$post.="+u\"{$u->login}\"\"{$u->code}\"\n";
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_USERAGENT, 'EQ Device Control Engine');
curl_setopt($ch,CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_POSTFIELDS,$post);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_USERPWD, "$auth");
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/xrx-acct-data']);
$data = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// echo "HTTP CODE: {$httpcode}<br/>";
// echo $data;
}
This code works perfectly on the above named Xerox's but fails on the C70 with a 401 error and the following message:
Invalid accounting authentication version
Network accounting is enabled on the device, and I can collect all jobs from the printer at the URL acct/get_acct
If I pass in bad post data I get a different error message:
HTTP Return 400
The Request had invalid syntax
I can't find any references to JBA or Network Accounting anywhere and am not sure how to find out if the format has changed on set_auth or if it's version difference that needs to be set.
I know there are differences in the get_acct output that does point to a different version of the accounting.
The answer is very simple.
Everything is correct except the following:
$post="+aaav1.0+\n+purge\n";
The plus(+) after the v1.0 allows the users to be uploaded for accounting.
I made a script that uses the Mobile.de API.
It worked fine on their test environment, only difference with the live environment is the proxy. So in my cURL I removed the proxy.
For any POST call this works just fine, but for my PUT call it doesn't work at all.
So let's say I sent the following with for example Postman:
PUT /seller-api/sellers/123/ads/456
Host: services.mobile.de
Content-Type: application/vnd.de.mobile.api+json
Authorization: Basic abcdef12345=
And I put in the right JSON, it works just fine.
And this is the script I use in PHP:
function updateVehicle($seller, $voertuig_id, $json) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://services.mobile.de/seller-api/sellers/' . $seller . '/ads/' . $voertuig_id);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_USERPWD, USERNAME . ':' . PASSWORD);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Host: services.mobile.de',
'Content-type: application/vnd.de.mobile.api+json'
));
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
In return I get the 403 forbidden. 'You don't have permission to access /seller-api/sellers/123/ads/456 on this server.'
I already printed out al curl info and saw that all the headers are there and everything seems just fine, but why don't I have permission.
Contacted Mobile.de already but they say it's something in my code.
So after much debugging I found out it was a space after the $voertuig_id that got me an 403 forbidden error. :S
Maybe I just need a pair of fresh eyes....
I need to POST to a page behind .htaccess Basic Authentication. I successfully log in and get past the .htBA, then POST to the target page. I know that the script is getting to that page as I'm logging the access. However $_POST is empty -- evident from both checking the var as well as the target script not working the way it should. (I control all pages).
I've tried many combos of the various curl opts below to no avail. I'm not getting any errors from the second hit.
Thanks.
$post_array = array(
'username'=>$u,
'password'=>$p
);
// Login here
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://example.com/admin/login.php');
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:43.0) Gecko/20100101 Firefox/43.0');
curl_setopt($ch, CURLOPT_COOKIEJAR, realpath('temp/cookies.txt') );
curl_setopt($ch, CURLOPT_COOKIEFILE, realpath('temp/cookies.txt'));
curl_setopt($ch, CURLOPT_COOKIESESSION, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 10);
curl_setopt($ch, CURLOPT_REFERER, 'http://example.com/index.php');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_array));
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'method' => 'POST',
"Authorization: Basic ".base64_encode("$username:$password"),
));
$logInFirst = curl_exec ($ch);
/* Don't close handle as need the auth for next page
* load up a new page */
$post_array_2 = array(
'localfile'=>'my_data.csv',
'theater_mode'=>'normal'
);
//curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, realpath('temp/cookies.txt') );
curl_setopt($ch, CURLOPT_COOKIEFILE, realpath('temp/cookies.txt'));
curl_setopt($ch, CURLOPT_COOKIESESSION, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_REFERER, 'http://example.com/admin/post_here.php');
curl_setopt($ch, CURLOPT_URL, 'http://example.com/admin/post_here.php');
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_array_2));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: multipart/form-data;',
"Authorization: Basic ".base64_encode("$username:$password"),
));
$runAi = curl_exec($ch);
$run_error = curl_error($ch); echo '<hr>'.$run_error.'<hr>';
curl_close($ch);
Here's the code on the target page (post_here.php), which results in a zero count. So I know that the target script is being hit, and based on the output, there are no POSTs.
$pa = ' There are this many keys in POST: '.count($_POST);
foreach ($_POST as $key => $value) {
$pa .= ' '.$key.':'.$value.' ---- ';
}
The error is on the second request:
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_array_2));
// ...
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: multipart/form-data;',
// ...
You send the header Content-Type: multipart/form-data but the data is encoded as application/x-www-form-urlencoded (by http_build_query()).
The data you want to post on the second request contains 'localfile'=>'my_data.csv'. If you want to upload a file on the second request then the content type is correct (but you don't need to set it manually). Don't use http_build_query() but pass an array to CURLOPT_POSTFIELDS, as is explained in the documentation.
Also, for file uploads you have to put a # in front of the file name and make sure curl is able to find the file. The best way to do this is to use the complete file path:
$post_array_2 = array(
'localfile' => '#'.__DIR__'/my_data.csv',
'theater_mode' => 'normal'
);
The code above assumes my_data.csv is located in the same directory as the PHP script (which is not recommended). You should use dirname() to navigate from the script's directory to the directory where the CSV file is stored, to compose the correct path.
As the documentation also states, since PHP 5.5 the # prefix is deprecated and you should use the CURLFile class for file uploads:
$post_array_2 = array(
'localfile' => new CURLFile(__DIR__'/my_data.csv'),
'theater_mode' => 'normal'
);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_array_2);
As a side note, when you call curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); it means curl is allowed to negotiate the authentication method with the server. But you also send the header "Authorization: Basic ".base64_encode("$username:$password") and this removes any negotiation because it forces Authorization: Basic.
Also, in order to negociate, curl needs to know the (user, password) combination. You should always use curl_setopt(CURLOPT_USERPWD, "$username:$password") to tell it the user and password. Manual crafting the Authorization header is not recommended.
If you are sure Authorization: Basic is the method you need then you can
use curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC).
You do not see anything inside post because you are using 'Content-Type: multipart/form-data;',. Just remove that and you should be fine.
If you want to upload a file (i.e. my_data.csv) that case you need to follow this way:
## change your file name as following in your param
'localfile'=> '#'.'./my_data.csv',
## after that remove http_build_query() from post
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_array_2);
This will automatically add the header multipart with your post.
You may look your uploaded file using $_FILES variable.
Finally, You can observe what curl is enabling verbose mode.
curl_setopt($ch, CURLOPT_VERBOSE, true);
Tips: While using cookie, always close curl after each and every curl_exec() you do. Otherwise it will not probably write things into cookie file after every requests you make!
I know nothing about implementing an API. I do know PHP a bit. I have a situation where I need to call a REST API method to purge cache on a CDN server. Can somebody help me with some sample code?
The following is the sample request:
PUT <<url>>
Authorization: TOK:12345-12345
Accept: application/json
Content-Type: application/json
Host: api.edgecast.com
Content-Length: 87
{
"MediaPath":"<<urlhere>>"
"MediaType":"3"
}
Can somebody help me with code to implement this rest api request?
Thanks in advance.
I had to find the hard way too. This has been tested (with slight modifications from my original code)
//## GoGrid PHP REST API Call
define('TOKEN','XXXXX-XXXXX-XXXXX-XXXXXX'); // found on the cdn admin My Settings
define('ACCOUNT_NUMBER','XXXX'); // found on the cdn admin Top Right corner
function purgeCacheFileFromCDN($urlToPurge) {
//## Build the request
$request_params = (object) array('MediaPath' => $urlToPurge, 'MediaType' => 8); // MediaType 8=small 3=large
$data = json_encode($request_params);
//## setup the connection and call.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.edgecast.com/v2/mcc/customers/'.ACCOUNT_NUMBER.'/edge/purge');
curl_setopt($ch, CURLOPT_PORT , 443);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1); // For debugging
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); // no caching
curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1); // no caching
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS,$data);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: tok:'.TOKEN, 'Content-Type: application/json','Accept: application/json', 'Content-length: '.strlen($data)));
$head = curl_exec($ch);
$httpCode = curl_getinfo($ch);
curl_close($ch);
//## check if error
if ($httpCode['http_code'] != 200) {
echo 'Error reported: '.print_r(array($head,$httpCode),1); // output it to stdout this will be emailed to me via cron capture.
}
}
Was too lazy to write from the scratch so copied from amazingly pink site that Google advises in the first page of results.
$data = array("a" => $a);
$ch = curl_init($this->_serviceUrl . $id);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($data));
$response = curl_exec($ch);
if(!$response) {
return false;
}
PS: The source search request: http://www.google.ru/search?q=php+sample+put+request+curl
Here is my source gist for my fully implemented Grunt task for anyone else thinking about working with the EdgeCast API. You'll find in my example that I use a node module to execute the curl command which purges the CDN.
This was that I ended up with after spending hours trying to get an HTTP request to work within Node. I was able to get one working in Ruby and Python, but did not meet the requirements of this project.