CURL http authentication fails when setting CURL_OPTPOST to true - php

I'm trying to build a payment form that integrates with Firstdata's api. I need to post an XML string to their server. They also require a client side certificate and http authentication. My CURL set up currently looks like this:
function firstdata_send($config_param, $data) {
$config_default = array(
'test' => FALSE,
);
// settings in $config_param will overwrite settings in $config_default
$config = (object)array_merge($config_default, $config_param);
if($config->test) {
$url = 'https://ws.merchanttest.firstdataglobalgateway.com/fdggwsapi/services/order.wsdl';
}
else {
$url = 'https://ws.firstdataglobalgateway.com/fdggwsapi/services/order.wsdl';
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, "{$config->username}:{$config->password}");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSLCERT, $config->pemfile);
curl_setopt($ch, CURLOPT_SSLKEY, $config->keyfile);
curl_setopt($ch, CURLOPT_SSLKEYPASSWD, $config->keypass);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_HEADER, TRUE);
curl_setopt($ch, CURLOPT_VERBOSE, TRUE);
$result = curl_exec($ch);
$result .= curl_error($ch);
return $result;
}
Their server responds with HTTP/1.1 401 Unauthorized. But if I comment out the post options:
//curl_setopt($ch, CURLOPT_POST, TRUE);
//curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
I get HTTP/1.1 200 OK. Unless I'm completely misunderstanding whats going on it seems like using post somehow interferes with the auth headers. I don't know what I'm missing.
Solved:
Turns out the ssl certificates the test account had generated were bad. I had to call their tech support and they had to regenerate the certs 3 times before the system would accept them. Sorry for wasting your time. I should have called them first. If anybody is interested the tech support number I called was (888) 477-3611. I think NomikOS was closest to being correct so I'll mark his as the answer and up vote the rest of you. Thanks again.

Form what i see you need a certificate in pem format to use this service ....
A. Download the API https://www.firstdata.com/downloads/customerservice/30006_api_php.zip
B. You would see so many examples
Example Sales Info
include"lphp.php";
$mylphp=new lphp;
$myorder["host"] = "secure.linkpt.net";
$myorder["port"] = "1129";
$myorder["keyfile"] = "./YOURCERT.pem"; # Change this to the name and location of your certificate file
$myorder["configfile"] = "1234567"; # Change this to your store number
$myorder["ordertype"] = "SALE";
$myorder["result"] = "LIVE"; # For a test, set result to GOOD, DECLINE, or DUPLICATE
$myorder["cardnumber"] = "4111-1111-1111-1111";
$myorder["cardexpmonth"] = "01";
$myorder["cardexpyear"] = "05";
$myorder["chargetotal"] = "9.99";
$myorder["addrnum"] = "123"; # Required for AVS. If not provided, transactions will downgrade.
$myorder["zip"] = "12345"; # Required for AVS. If not provided, transactions will downgrade.
// $myorder["debugging"] = "true"; # for development only - not intended for production use
# Send transaction. Use one of two possible methods #
// $result = $mylphp->process($myorder); # use shared library model
$result = $mylphp->curl_process($myorder); # use curl methods
if ($result["r_approved"] != "APPROVED") // transaction failed, print the reason
{
print "Status: $result[r_approved]\n";
print "Error: $result[r_error]\n";
}
else
{ // success
print "Status: $result[r_approved]\n";
print "Code: $result[r_code]\n";
print "OID: $result[r_ordernum]\n\n";
}

Check you access credentials (username, password)
401 Unauthorized
The request requires user authentication. The response MUST include a
WWW-Authenticate header field (section 14.47) containing a challenge
applicable to the requested resource. The client MAY repeat the
request with a suitable Authorization header field (section 14.8). If
the request already included Authorization credentials, then the 401
response indicates that authorization has been refused for those
credentials. If the 401 response contains the same challenge as the
prior response, and the user agent has already attempted
authentication at least once, then the user SHOULD be presented the
entity that was given in the response, since that entity might include
relevant diagnostic information. HTTP access authentication is
explained in "HTTP Authentication: Basic and Digest Access
Authentication" [43].
source: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
==
OBS: I recommend you this code to check for errors
// check for errors before close
$result = curl_exec($ch);
if ($result === false)
{
echo curl_error($ch);
}
curl_close($ch);

Just checked First Datas API is a SOAP based API...
The url to the wsdl only accepts GET because the wsdl is just the xml instruction set for sending soap calls.
PHP soapclient class

Related

Access Microsoft Graph resources with an App access token (no user sign-in)

I own an institutional e-mail account and I want to access its content (and send emails) using a PHP web application I'm writing, and I want it to access this account without sign-in from the user (they will be logged to my application, that will send predefined messages based upon user's actions).
I registered the application using Microsoft Application Registration Portal using this institutional account credentials, and got my client_id and client_secret (password).
I'm using the code below to perform authentication based on these instructions:
function preformat($string) {
return "<pre>".htmlentities($string)."</pre>";
}
function getGraphAccessToken($domain, $client_id, $password) {
$graph_endpoint="https://graph.microsoft.com/.default";
$token_endpoint = "https://login.microsoftonline.com/".$domain."/oauth2/v2.0/token";
$token_postdata = implode("&",[
"grant_type=client_credentials",
"client_id=".urlencode($client_id),
"client_secret=".urlencode($password),
"scope=".urlencode($graph_endpoint)
]
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_URL, $token_endpoint);
curl_setopt($ch, CURLOPT_POSTFIELDS, $token_postdata);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$token_json = curl_exec($ch);
curl_close($ch);
$token_object = json_decode($token_json);
return "Authorization: ".$token_object->token_type." ".$token_object->access_token;
}
function queryGraph($auth_header, $feed){
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, false);
curl_setopt($ch, CURLOPT_URL, "https://graph.microsoft.com/v1.0/".$feed);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array($auth_header,"Accept:application/json;odata=minimalmetadata","Content-Type:application/json;odata=minimalmetadata","Prefer:return-content"));
$graph_query = curl_exec($ch);
curl_close($ch);
return $graph_query;
}
$auth_header=getGraphAccessToken($domain,$client_id,$client_secret);
echo preformat($auth_header);
$query_me=queryGraph($auth_header,"me");
echo preformat($query_me);
This code succeeds to grab the access token and requests from https://graph.microsoft.com/v1.0/me/ expecting to receive the institutional account's info (as a success test). However, I get the following response:
{
"error": {
"code": "BadRequest",
"message":
"Current authenticated context is not valid for this request.
This occurs when a request is made to an endpoint that requires user sign-in.
For example, /me requires a signed-in user.
Acquire a token on behalf of a user to make requests to these endpoints.
Use the OAuth 2.0 authorization code flow for mobile and native apps
and the OAuth 2.0 implicit flow for single-page web apps.",
"innerError": {
"request-id": "3073f561-43db-49d3-9851-7f50037abb61",
"date": "2018-12-31T17:28:45"
}
}
}
What am I missing or doing wrong here?
You cannot use /me with app-only authentication, you need to reference the user via their userPrincipalName - /users/{upn}. After all, how would Graph know which "me" you are referring too?

php - CURL - Getting HTTP code if/else to test

I want to do 404 testing on an API using curl and php
While valid requests to the token generation endpoint (/api/Utilities/StashData) will receive an HTTP 200 response containing a token for use with Leap, invalid requests will receive an HTTP 400 response (Bad Request). If your system receives an HTTP 400 response from the API, it must not attempt to proceed to Leap. The content of this response will contain a JSON-serialized object describing the validation error(s), as per the following example:
[
{
"Key": "CapeConsumers.TrackerNumber",
"Errors": [
{
"ErrorCode": "CC002",
"Description": "The value is required."
}
]
}
]
As you can see, the response contains an array of objects, where each object has a Key property which refers to the key submitted to the service, and an Errors property describing the reason(s) the value was rejected. The ErrorCode values are predefined and guaranteed never to change, the Description is dynamic for certain errors and exists mainly to aid debugging
The codes
CC001
Invalid value.
The value does not conform to one or more validation rules defined for
the key.
CC002
The value is required.
A required value has been omitted from the request. Sending null or a
blank string for a required value will also result in this error
(unless a blank string has been defined as valid by supporting
documentation).
CC003
The value is not of the correct type. Expected {0}, found {1}.
The system expected the value to be of one type, but a different type
was submitted. (e.g. a number was sent instead of a string)
CC004
The origin is not valid for the specified campaign.
Campaigns are linked to specific integrating parties. If an
integrating party uses a tracker number that isn’t assigned to it,
this error will be returned.
CC005
The campaign is not currently active.
The tracker number references a campaign that either hasn’t started or
has already come to an end.
Here is the code I have tried.
$endpointUser = "myuser";
//
$endpointPassword = "mypass";
//
$url = "https://webservices_test.capeconsumers.co.za/api/Utilities/StashData";
$iframeUrl = "https://onlineapplicationtest.capeconsumers.co.za/Bridge/CapeConsumersSACommercial?token=";
$fields = array(
"User.IdentityNumber"=> $_GET['security_phrase'],
"CapeConsumers.TrackerNumber"=> "6E273247DB4840G3",
"Call.AgentReference"=> $_GET['user'] ,
"Call.RecordingReference"=> $_GET['security_phrase']
);
$process = curl_init($url);
curl_setopt($process, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($process, CURLOPT_HEADER, 0);
curl_setopt($process, CURLOPT_USERPWD, $endpointUser . ":" . $endpointPassword);
curl_setopt($process, CURLOPT_TIMEOUT, 30);
curl_setopt($process, CURLOPT_POST, 1);
curl_setopt($process, CURLOPT_POSTFIELDS, json_encode($fields));
curl_setopt($process, CURLOPT_RETURNTRANSFER, TRUE);
$token = curl_exec($process);
curl_close($process);
$token = str_replace('"','',$token);
$httpCode = curl_getinfo($process, CURLINFO_HTTP_CODE);
if($httpCode == 400) {
echo "there was an error"; exit;
}
?>
<iframe src="<?php echo $iframeUrl . $token; ?>" width="100%" height="800"></iframe>
My problem is that if I deliberately give it a bad response, it will still generate the iframe instead of exiting at the if statement for the 400 error.
I am unsure of my logic as this is my first attempt at curl
You are getting response code too late.
curl_close manual says:
Closes a cURL session and frees all resources. The cURL handle is also deleted.
Order must be:
$httpCode = curl_getinfo($process, CURLINFO_HTTP_CODE);
curl_close($process);
Otherwise there is no CURL handle anymore and therefore you can't retrieve response code.
404 will not be returned by API, it would be thrown if and only if requested url doesn't exist or running. To generate 404 deliberately, pls modify the hostsection of url, so that it refers to wrong location and thus should result 404.
Hope this helps.

Finding a specific GitLab tag from PHP

We have a JIRA instance that our custom PHP app built in Laravel pulls from and for each issue looks to see if a specific branch or tag exists:
chdir($path . $repo);
exec("git rev-parse --verify ".$branch, $branch_dump, $return_var);
if ($return_var == 0) {
return true;
} else {
return false;
}
However, we have migrated all of our git projects to GitLab and that method no longer works, since you need root to get into GitLab's repo data directory.
We looked at GitLab's API and found that we could do this:
http://gitlab/api/v3/projects/10/repository/commits/OUR-TAG-HERE?private_token=XXX
However this requires us to specify an arbitrary GitLab project ID (10 in this case) and therefore isn't predictable, so we can't programmatically execute the search for each JIRA API return like we did before. This method would work if we could simply search for tags using the project name only, but I can't find a way to do that.
Here's an overlook at the way the app works:
JIRA contains all issues we want
Each issue contains several custom fields we use to search our git repos with, generically they are "Repo Name" and "Tag Name"
Our Laravel app connects to JIRA's api and harvests all issues into an array we use to build a table listing information about each issue
The two custom fields "Repo Name" and "Tag Name" are matched against our git repositories to determine which of several options to provide the end user (clone tag, create tag if repo exists but no tag exists, or none if neither)
We briefly considered adding another custom field to our JIRA issues which we would fill with GitLab's project ID, but we have hundreds of issues and it is an inelegant solution that really only acts as another potential point of failure, to say nothing of the extra maintenance.
Any ideas?
The best solution I found to this issue was to use the API to get the list of projects and use that list to pair name and ID.
For example, this code will output the tag names for all your projects:
//Get Projects list via API
$header = array("PRIVATE-TOKEN: <YOUR_TOKEN>");
$ch = curl_init("https://<YOUR_GITLAB_DOMAIN>/api/v3/projects/");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
//Parse returned list to an array
$projectsArray= json_decode($result, true);
//Loop over the array of projects accessing the list of tags via the API
foreach ($projectsArray as $project) {
echo $project["name"] . " Tags:<br>";
$tagURL = "https://<YOUR_GITLAB_DOMAIN>/api/v3/projects/" . $project["id"] . "/repository/tags";
$ch2 = curl_init($tagURL);
curl_setopt($ch2, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch2, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch2, CURLOPT_RETURNTRANSFER, 1);
$result2 = curl_exec($ch2);
curl_close($ch2);
$tagsArray= json_decode($result2, true);
foreach ($tagsArray as $tag) {echo $tag["name"] . "<br>";}
echo "<br>";
}
Since arbitrary project IDs are still required by the GitLab API for this functionality we've scrapped the API altogether. Instead we're now simply cURLing HTTP response codes. Here's one of our methods to see if the issue has a tag:
public function HasTag($projectName, $nameSpace, $tagName)
{
$url=$this->gitLabUrl.'/'.$nameSpace.'/'.$projectName.'/tags/'.$tagName;
$ch = curl_init(); // Initiate curl
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL verification
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Will return the response, if false print the response
curl_setopt($ch, CURLOPT_URL, $url); // Set the url
curl_exec($ch); // Execute
$info = curl_getinfo($ch);
curl_close($ch);
if ($info['http_code'] == 200) {
return true;
} else {
return false;
}
}
And here's our method to check for a branch:
public function HasBranch($projectName, $nameSpace, $branchName)
{
$url=$this->gitLabUrl.'/'.$nameSpace.'/'.$projectName.'/tree/'.$branchName;
$ch = curl_init(); // Initiate curl
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL verification
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Will return the response, if false print the response
curl_setopt($ch, CURLOPT_URL, $url); // Set the url
curl_exec($ch); // Execute
$info = curl_getinfo($ch);
curl_close($ch);
if ($info['http_code'] == 200) {
return true;
} else {
return false;
}
}
As you can see this is pretty simple and hacky, but it works for our implementation because none of the projects being accessed are private (our GitLab instance is purely internal).
Hopefully in the future GitLab will remove the ID requirement from its API.

Laravel OAuth Authorization Header for FitBit API

I have been struggling for days now to find a decent solution for Laravel but to no avail.
There are many libraries out there that at one point may have worked to provide a Laravel - FitBit API OAuth integration however after trying over 15 different ones and none of them working I am stuck.
Reading the FitBit Documentation I see that once you receive a token you must swap the authorization code with an access token. To do this you need to send an authorization header like this:
POST https://api.fitbit.com/oauth2/token
Authorization: Basic Y2xpZW50X2lkOmNsaWVudCBzZWNyZXQ=
Content-Type: application/x-www-form-urlencoded
client_id=22942C&grant_type=authorization_code&redirect_uri=http%3A%2F%2Fexample.com%2Fcallback&code=1234567890
I have tried using guzzle and a few other libraries for sending the requests but none of them support the format that FitBit require.
I've seen sites with FitBit API integrated so there must be a solution for this.
If anyone has managed to integrate the FitBit API please let me know where I am going wrong.
Thanks.
I don't have a fitbit account, so I can't test this and it will probably need some tweaking, but I would start with something like:
class FitbitConnection{
public function getToken($request_url, $client_id, $client_secret, $code, $redirect_uri){
// base64 encode the client_id and client_secret
$auth = base64_encode("{$client_id}:{$client_secret}");
// urlencode the redirect_url
$redirect_uri = urlencode($redirect_uri);
$request_url .= "?client_id={$client_id}&grant_type=authorization_code&redirect_uri={$redirect_uri}&code={$code}";
// Set the headers
$headers = [
"Authorization: Basic {$auth}",
"Content-Type: application/x-www-form-urlencoded",
];
// Initiate curl session
$ch = curl_init();
// Set headers
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// Options (see: http://php.net/manual/en/function.curl-setopt.php)
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_URL, $request_url);
curl_setopt($ch, CURLOPT_POST, 1);
// Execute the curl request and get the response
$response = curl_exec($ch);
// Throw an exception if there was an error with curl
if($response === false){
throw new Exception(curl_error($ch), curl_errno($ch));
}
// Get the body of the response
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$responseBody = substr($response, $header_size);
// Close curl session
curl_close($ch);
// Return response body
return $responseBody;
}
}
You should note that I've commented out
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
You can put this option back in if you get an SSL certificate problem on your localhost, but you shouldn't use it in production .
You can then just do something like:
try{
$fitbitConnection = new FitbitConnection();
$token_response = $fitbitConnection->getToken("https://api.fitbit.com/oauth2/token","22942C","client_secret","1234567890","http://www.example.com");
echo $token_response;
}catch(Exception $e){
// curl error
echo $e->getMessage();
}

Auth lost when doing a curl request to my laravel server

I'm trying to make a curl request to my laravel server, in that request I have to check whether the user of my laravel application is logged in or not. I use this code:
$transferAmount = 200;
//set POST variables
$url = URL::route('post-spend-partner');
$fields = array(
'transferAmount' => urlencode($transferAmount),
'cancelUrl' => urlencode(URL::route('get-return-page-example')),
'returnUrl' => urlencode(URL::route('get-return-page-example')),
);
// New Connection
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0);
curl_setopt($ch, CURLOPT_COOKIESESSION, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
curl_setopt($ch, CURLOPT_URL, $url);
curl_exec($ch);
curl_close($ch);
In the requested url I'm just checking if I'm logged in or not, but it always returns false:
public function postSpendPartner() {
echo "Authenticated? " . (Auth::check() ? 'Yes' : 'No');
}
I know for sure that I'm logged in, if I try the exact same thing with Ajax it completely works!
Does anyone know what I could try, to solve this problem?
Best regards!
Fabrice
Some facts: HTTP is stateless. Session IDs need to be passed to the server in order to continue the session. Session IDs are (most of the time) stored in cookies. Cookies are included in the request.
Using a cookiejar could indeed be one possible solution. The fact that it works using Ajax, and not by re-submitting the request from your server might be because of the session-verification mechanism on the server: Some session implementations lock session IDs to the initial IP address. If the contents of your cookiejar file check out, that might be the culprit.
That aside: re-submitting the request via Curl from your server is a severe codesmell to me. A proper solution would to implement something such as OAuth.
Try sending your cookies as a header with your curl request.
// ...
$cookie_header = "Cookie:";
$headers = [];
foreach($_COOKIE as $key => $val) {
// Do sanitize cookie values
$cookie_header .= " ".$key."=".$value.";";
}
$headers[] = $cookie_header;
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
// ...
You could filter out unnecessary cookie values from $cookie_header.

Categories