Guzzle 6 - getting the effective url - object

I have just upgraded from guzzle 3 to guzzle 6
Now i have some code here..
$request = $this->_client->get($url);
$response = $request->send();
$url = $response->getInfo('url');
return $url;
After updating to guzzle 6 I see that getInfo() & also geteffectiveurl() has been removed.. for some reason. so my new code is...
$res = $this->_client->request('GET', $url, ['on_stats' => function (TransferStats $stats) use (&$url) {
$url = $stats->getEffectiveUri();
}])->getBody()->getContents();
return $url;
Now that $url variable is a GuzzleHttp\Psr7\Uri Object which doesnt really solve my problem as i just need to return the url as a string.
How can i covert the object ->
[24-Mar-2017 19:12:26 UTC] GuzzleHttp\Psr7\Uri Object
(
[scheme:GuzzleHttp\Psr7\Uri:private] => https
[userInfo:GuzzleHttp\Psr7\Uri:private] =>
[host:GuzzleHttp\Psr7\Uri:private] => signup.testapp.com
[port:GuzzleHttp\Psr7\Uri:private] =>
[path:GuzzleHttp\Psr7\Uri:private] => /login
[query:GuzzleHttp\Psr7\Uri:private] => username=jeff&blablablablabla
[fragment:GuzzleHttp\Psr7\Uri:private] =>
)
Into a simple string that i can pass on to make another request to?
Or am i missing somthing? getInfo('url') in Guzzle 3 was the perfect solution to a problem surely another one has taken its place?
Thanks

\GuzzleHttp\Psr7\Uri has a magic __toString() method which will return you the URI as a string.
If you are just wanting to send another request to the exact same URI the docs state
When creating a request, you can provide the URI as a string or an instance of Psr\Http\Message\UriInterface.
That means your second request to the same URI could be executed with the $url object you have.
// Your existing code
// I assume this is within a method since it returns $url
$res = $this->_client->request('GET', $url, [
'on_stats' => function (TransferStats $stats) use (&$url) {
$url = $stats->getEffectiveUri();
}
])->getBody()->getContents();
return $url;
// Make a second request to the same URI
// This would be in another method or something after receiving the $url from the above method
$response2 = $this->_client->request('GET', $url);

Related

How can I listen to webhooks from PayStack?

I created a website and I integrated payment using PayStack and it is fully functional, but something unusual came up sometime when a customer wanted to make a payment. After the successful payment processing, maybe something went wrong with the customer's Network provider but the customer was not redirected to a success page where to give values to database.
So I implemented webhooks to get values from paystack and PUT THE CONTENTS in a .txt (webhookApi.txt) file but it seems something is wrong with the code and I can't figure it out.
`
<?php
// only a post with paystack signature header gets our attention
if ((strtoupper($_SERVER['REQUEST_METHOD']) != 'POST' ) || !array_key_exists('x-paystack-signature', $_SERVER) )
exit();
// Retrieve the request's body
$input = #file_get_contents("php://input");
define('PAYSTACK_SECRET_KEY','sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxx');
// validate event do all at once to avoid timing attack
if($_SERVER['HTTP_X_PAYSTACK_SIGNATURE'] !== hash_hmac('HMAC SHA512', $input, PAYSTACK_SECRET_KEY))
exit();
http_response_code(200);
// parse event (which is json string) as object
// Do something - that will not take long - with $event
$event = json_decode($input);
$reference = $event->data->reference;
$email = $event->data->customer->email;
$eventMessage = $event->event;
file_put_contents("webhookApi.txt", PHP_EOL.$input, FILE_APPEND);
exit();
?>
`

Impossible to get an access_token for Instagram Basic Display API

I'm trying to get an access_token from Instagram to use their Basic Display API for a new app (simply display tweets on a webpage).
I followed these steps: https://developers.facebook.com/docs/instagram-basic-display-api/getting-started
But I'm stuck at Step 5: Exchange the Code for a Token
The cURL request always returns a 400 error with the message: "Matching code was not found or was already used"
However, after many tests, I got an access_token one time only, but it expired about one hour later. This seems to be very random.
The Instagram Basic Display API seems rather new. A while ago, I have used apps created on the https://www.instagram.com/developer/ website and it used to work. Now this site display this message:
UPDATE: Starting October 15, 2019, new client registration and permission review on Instagram API platform are discontinued in favor of the Instagram Basic Display API.
... with a link to the developers.facebook.com.
I tried using the command-line tool as per the original docs(https://developers.facebook.com/docs/instagram-basic-display-api/getting-started), but no luck...
Here's what to do in 3 easy steps:
First thing: Install Postman https://www.postman.com/downloads/
Make a POST request to https://api.instagram.com/oauth/access_token with the parameters in the body, NOT the params. Make sure that the x-www-form-urlencoded option is enable.
You should now get a status of 200 OK and a response with both access_token and user_id.
{
"access_token": "IGQVJYUXlDN...",
"user_id": 17841400...
}
Happy days!!
See the screenshot for the correct settings:
I just succeeded by removing the trailing #_ at the end in the code they give you. Not sure if that was your issue?
https://developers.facebook.com/support/bugs/436837360282557/
I had this problem when i was trying implement a application.
My problem was the code generated when you allow the permissions.
Try to remove #_ from the end of the generated code and try generate the token again
Generated code example:
AQBvrqqBJJTM49U1qTQWRMD96oRyMR3B_04JSfjc-nUIi0iGbSc3x_EceggQi9IyG3B3Rj3ocreMThQoPJbPpeXLUM4exJMy4o01fXcRtT_I9NovaNAqmWSneFt3MYv_k7ifAUUeMlC050n5xnjQP6oAvDBfCFQvTdrFaR95-5i71YsfQlmjYWDG6fcWRvOB9nqr6J9mbGMXMi9Y4tKlSfElaYm0YKRijZQDG2B5PaxQ8A #_
Generated code edited:
AQBvrqqBJJTM49U1qTQWRMD96oRyMR3B_04JSfjc-nUIi0iGbSc3x_EceggQi9IyG3B3Rj3ocreMThQoPJbPpeXLUM4exJMy4o01fXcRtT_I9NovaNAqmWSneFt3MYv_k7ifAUUeMlC050n5xnjQP6oAvDBfCFQvTdrFaR95-5i71YsfQlmjYWDG6fcWRvOB9nqr6J9mbGMXMi9Y4tKlSfElaYm0YKRijZQDG2B5PaxQ8A
I was also having the same problem, I solved clearing the cache, coockie and other browser data.
Then I made a new request.
Try it, it worked with me.
I found the solution.
The direct uri must the same as you use in the beginning.
ex. You use
www.abc.com/auth
to get the code. When you exchange the token the redirect_uri must be the same as
www.abc.com/auth
I was using the old Instagram API as well. I had to change a few things to make my code work on the new API. Not sure what you're using, this is how I did it with PHP.
$url = 'https://api.instagram.com/oauth/access_token';
$fields = array(
'app_id' => 'YOUR_APP_ID',
'app_secret' => 'YOUR_APP_SECRET_ID',
'grant_type' => 'authorization_code',
'redirect_uri' => 'YOUR_REDIRECT_URL',
'code' => $code
);
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_POST, true);
curl_setopt($ch,CURLOPT_POSTFIELDS, $fields);
curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch,CURLOPT_VERIFYPEER, false);
$result = curl_exec($ch);
curl_close($ch);
//get the access token from the string sent from Instagram
$splitString = explode('"access_token":', $result);
$removeRest = explode(',', $splitString[1]);
$withSpace = str_replace('"','', $removeRest[0]);
$access_token = str_replace(' ','', $withSpace);
I am using PHP but without using any lib. Maybe this one help you.
curl.php
class InstagramApi
{
public function GetAccessToken($client_id, $redirect_uri, $client_secret, $code) {
$url = 'https://api.instagram.com/oauth/access_token';
$curlPost = 'app_id='. $client_id . '&redirect_uri=' . $redirect_uri . '&app_secret=' . $client_secret . '&code='. $code . '&grant_type=authorization_code';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $curlPost);
$data = json_decode(curl_exec($ch), true);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if($http_code != '200')
throw new Exception('Error : Failed to receieve access token');
return $data;
}
index.php
include "curl.php";
include "instagram_keys.php"; // holding APP ID, SECRET KEY, REDIRECT URI
$instagram_ob = new InstagramApi();
$insta_data = $instagram_ob->GetAccessToken(INSTAGRAM_CLIENT_ID, INSTAGRAM_REDIRECT_URI, INSTAGRAM_CLIENT_SECRET, $_GET['code']);
echo $insta_data['access_token'];
echo $insta_data['user_id'];
NOTE: $_GET['code'] is required and you should know how to get the code. Read here

HTTPBuilder & Session ID

I have the following code to connect to a REST API service, authenticate, retrieve a session ID then make further requests passing the session ID to authenticate. The initial request works and I get a HTTP 200 OK plus the session ID in the response, however when I try to make a second request passing the session ID in the header, I get
Caught: groovyx.net.http.HttpResponseException: Bad Request
I know the script can be written much better with the use of classes and try / catch etc. I am still learning both java and groovy so I start by just trying to do everything within the same class.
Any help much appreciated.
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.URIBuilder
import static groovyx.net.http.Method.POST
import static groovyx.net.http.ContentType.*
def url = 'https://1.1.1.1/web_api/'
def uri = new URIBuilder(url)
String CHKPsid
uri.path = 'login'
def http = new HTTPBuilder(uri)
http.ignoreSSLIssues()
http.request(POST,JSON ) { req ->
headers.'Content-Type' = 'application/json'
body = [
"user":"username",
"password":"password"
]
response.success = { resp, json ->
println (json)
CHKPsid = (json.sid)
println "POST Success: ${resp.statusLine}"
}
}
uri.path = 'show-changes'
http.request(POST,JSON ) { req ->
headers.'Content-Type' = 'application/json'
headers.'X-chkp-sid' = '${CHKPsid}'
body = [
"from-date" : "2017-02-01T08:20:50",
"to-date" : "2017-10-21"
]
response.success = { resp, json ->
println (json)
println "POST Success: ${resp.statusLine}"
}
}
String interpolation does not work with single (or triple single quotes). When groovy will evaluate '${CHKPsid}' (single quotes), its value will be ${CHKPsid} (this string). In order to use the value of the variable, you should use double quotes: "${CHKPsid}" or simply just the variable: headers.'X-chkp-sid' = CHKPsid.
So the output of this:
String CHKPsid = "abc123"
println '${CHKPsid}'
println "${CHKPsid}"
will be:
${CHKPsid}
abc123
In order to quickly test what the server receives, you can use httpbin.org or requestb.in
So as well as the correct assignment of the value of the session ID, I found that calling the same HTTPbuilder - http.request the second time even with a change of uri, header and body was the problem. The listening server still saw this as part of the same login API call. My workaround / resolution was to define a 2nd HTTPbuilder with a different name and this now works. I'm interested to know if this is normal behaviour and how others approach this. Thanks.

Is it safe to reuse Groovy HTTPBuilder objects for multiple requests?

I have some HTTPBuilder code that behaves differently depending on whether or not I reuse the same HTTPBuilder object to perform two different requests to the same REST service:
def http = new HTTPBuilder( 'https://myBaseURI/' )
http.auth.basic username, password.getPlainText()
http.ignoreSSLIssues()
http.request(GET,JSON) { req ->
uri.path = 'some/api/path/'
headers.'User-Agent' = 'Mozilla/5.0'
} // this request always behaves as expected
http.request(POST, JSON) { req ->
uri.path = 'some/other/api/path'
headers.'User-Agent' = 'Mozilla/5.0'
body = {
// Request body elided for brevity
}
}
The 'correct' behavior is for the POST to return a 201 - Created, but the response comes back as 200 OK unless I create a new HTTPBuilder to handle issuing the second request, in which case, the API call behaves as expected.
Certainly, the cause of the different results could be elsewhere, but I first wanted to make sure I wasn't misusing this object. Are there problems to be aware of when reusing the HTTPBuilder to issue multiple HTTP requests?
Try removing that forward slash at the end when you set uri.path in the GET request.
Looking at the documentation for setPath in URIBuilder you get:
//Set the path component of this URI. The value may be absolute or relative to the current path. e.g.
def uri = new URIBuilder( 'http://localhost/p1/p2?a=1' )
uri.path = '/p3/p2'
assert uri.toString() == 'http://localhost/p3/p2?a=1'
uri.path = 'p2a'
assert uri.toString() == 'http://localhost/p3/p2a?a=1'
uri.path = '../p4'
assert uri.toString() == 'http://localhost/p4?a=1&b=2&c=3#frag'
I understand this to mean that if you set the uri.path of an httpbuilder object with a slash at the end, you have essentially updated the working path so any subsequent relative path updates to uri.path will result in a concatenation of the path. Therefore, your POST in that example ends up pointing at https://myBaseURI/some/api/path/some/other/api/path

Mandrill Webhooks - Security

For security purposes, I try to allow only Mandrill's IP(s) to access these urls.
Does anyone know them?
Mandrill's signature is located in the HTTP response header: Authenticating-webhook-requests
In the request header find: X-Mandrill-Signature. This is a base64 of the hashcode, signed using web-hook key. This key is secret to your webhook only.
We have a range of IPs used for webhooks, but they can (and likely will) change or have new ones added as we scale. An alternative would be to add a query string to the webhook URL you add in Mandrill, and then check for that query string when a POST comes in so you can verify it's coming from Mandrill.
Just replace the constants and use this function:
<?php
function generateSignature($post)
{
$signed_data = WEB_HOOK_URL;
ksort($post);
foreach ($post as $key => $value) {
$signed_data .= $key;
$signed_data .= $value;
}
return base64_encode(hash_hmac('sha1', $signed_data, WEB_HOOK_AUTH_KEY, true));
}
//---
if (generateSignature($_POST) != $_SERVER['HTTP_X_MANDRILL_SIGNATURE']) {
//Invalid
}
?>
As described in mandrill's docs, they provide a signature to check if the request really came from them. to build the request there's a few steps:
start with the exact url of your webhook (mind slashes and params)
sort the post variables by key (in case of mandrill, you'll only have one post parameter: mandrill_events)
add key and value to the url, without any delimiter
hmac the url with your secret key (you can get the key from the web-interface) and base64 it.
compare the result with the X-Mandrill-Signature header
here's a sample implementation in python:
import hmac, hashlib
def check_mailchimp_signature(params, url, key):
signature = hmac.new(key, url, hashlib.sha1)
for key in sorted(params):
signature.update(key)
signature.update(params[key])
return signature.digest().encode("base64").rstrip("\n")
205.201.136.0/16
I have just whitelisted them in my server's firewall.
We don't need to white list the Ip they are using. Instead of that they have provided their own way to authenticate the webhook request.
When you are creating the mandrill webhook it will generate the key. It will come under the response we are getting to our post URL which is provided in the webhook.
public async Task<IHttpActionResult> MandrillEmailWebhookResponse()
{
string mandrillEvents = HttpContext.Current.Request.Form["mandrill_events"].Replace("mandrill_events=", "");
// validate the request is coming from mandrill API
string url = ConfigurationManager.AppSettings["mandrillWebhookUrl"];
string MandrillKey = ConfigurationManager.AppSettings["mandrillWebHookKey"];
url += "mandrill_events";
url += mandrillEvents;
byte[] byteKey = System.Text.Encoding.ASCII.GetBytes(MandrillKey);
byte[] byteValue = System.Text.Encoding.ASCII.GetBytes(url);
HMACSHA1 myhmacsha1 = new HMACSHA1(byteKey);
byte[] hashValue = myhmacsha1.ComputeHash(byteValue);
string generatedSignature = Convert.ToBase64String(hashValue);
string mandrillSignature = HttpContext.Current.Request.Headers["X-Mandrill-Signature"].ToString();
if (generatedSignature == mandrillSignature)
{
// validation = "Validation successful";
// do the updating using the response data
}
}

Resources