I am trying to update Xero contacts, which is part of the invoice in Laravel. I am using Xero-php-oauth2. I have tried
$xero = resolve(AccountingApi::class);
$invoice = new Invoice();
$invoice->setLineItems($lineItems);
$result = $xero->updateInvoice($tenantId, $invoice_id, $invoice);
$contactId = $result->getInvoices()[0]['contact']['contact_id'];
$contact = $this->newContact();
$contact->setContactId($contactId);
//$tenantId, $contactid, and $contact are correct
$xero->updateContact($tenantId, $contactId, $contact);
The request I am trying to send is like following, but the response returns 401 AuthenticationUnsuccessful
Request {#1160 ▼
-method: "POST"
-requestTarget: null
-uri: Uri {#1212 ▼
-scheme: "https"
-userInfo: ""
-host: "api.xero.com"
-port: null
-path: "/api.xro/2.0/Contacts/134ab308-7d07-4c2d-a770-c01325947ede"
-query: ""
-fragment: ""
}
-headers: array:6 [▼
"Host" => array:1 [▼
0 => "api.xero.com"
]
"User-Agent" => array:1 [▼
0 => "[xero-php-oauth2 (2.11.0)]"
]
"xero-tenant-id" => array:1 [▼
0 => "TENANT_ID_HERE"
]
"Accept" => array:1 [▼
0 => "application/json"
]
"Content-Type" => array:1 [▼
0 => "application/json"
]
"Authorization" => array:1 [▼
0 => "Bearer BEARER_TOKEN_HERE"
]
]
-headerNames: array:6 [▶]
-protocol: "1.1"
-stream: Stream {#1217 ▼
-stream: stream resource #755 ▼
wrapper_type: "PHP"
stream_type: "TEMP"
mode: "w+b"
unread_bytes: 0
seekable: true
uri: "php://temp"
options: []
}
-size: null
-seekable: true
-readable: true
-writable: true
-uri: "php://temp"
-customMetadata: []
}
}
xero-php-oauth2 sends request along with following options to GuzzleHttp\Client, and It seems to throw error at AccountingAPI#updateContactWithHttpInfo() in following line
$response = $this->client->send($request, $options);`
I also tried to add accounting.contacts scope before calling but it does not work.
$options = ['scope' => ['openid email profile offline_access accounting.settings accounting.transactions accounting.contacts accounting.journals.read accounting.reports.read accounting.attachments']];
Error message
[401] Client error: `POST https://api.xero.com/api.xro/2.0/Contacts/134ab308-7d07-4c2d-a770-c01325947ede` resulted in a `401 Unauthorized` response: {"Type":null,"Title":"Unauthorized","Status":401,"Detail":"AuthenticationUnsuccessful","Instance":"94b725d8-435b-4a98-be (truncated...)
It is working when I create or update invoices with almost identical request. Even identical bearer token. Also creating new contact if I pass that with new invoice. But not updating already existing contact. How can I fix this?
The answer is in the 401 response: "Title":"Unauthorized". You're not unauthenticated, you're unauthorised.
From the docs:
When your app is requesting authorisation from a user it will need to ask for a set of scopes. These scopes will be displayed to the user and describe what data your app will be able to access.
You supply the relevant scopes when requesting authentication with OAuth.
Your OAuth request (i.e. when you request authentication from the user) will be missing the accounts.contacts scope. Add it, and you should be able to successfully call those API endpoints.
Related
https://maps.googleapis.com/maps/api/distancematrix/json?units=metric&origins=Neuh%C3%B6fer+Damm+110,Germany&destinations=Vogelweide+8,Hamburg&key=YOUR_API_KEY
The API Key is having the distance matrix API linked to it. If you use your own key, you will see the request in the browser working properly. However, the moment you make an HTTP::get(<>) in the code it returns you 404. Currently, I'm using PHP Laravel and follow the documentation.
Here is the request code:
$response = Http::get('https://maps.googleapis.com/maps/api/distancematrix/json
?units=metric
&origins=Neuh%C3%B6fer+Damm+110,Germany
&destinations=Vogelweide+8,Hamburg
&key=MyKey`enter code here`'
);
and here is the given response:
Illuminate\Http\Client\Response {#576 ▼
#response: GuzzleHttp\Psr7\Response {#618 ▼
-reasonPhrase: "Not Found"
-statusCode: 404
-headers: array:8 [▼
"Date" => array:1 [▶]
"Content-Type" => array:1 [▶]
"Server" => array:1 [▶]
"Content-Length" => array:1 [▶]
"X-XSS-Protection" => array:1 [▶]
"X-Frame-Options" => array:1 [▶]
"Server-Timing" => array:1 [▶]
"Alt-Svc" => array:1 [▶]
]
-headerNames: array:8 [▶]
-protocol: "1.1"
-stream: GuzzleHttp\Psr7\Stream {#617 ▶}
Are you experiencing the same issue? The main question then is: What do we miss so we get NotFound when we use the same valid link? Maybe headers? Maybe something else? I'll keep searching for an more proper answer, but meanwhile, I hope that helps
Do not use new-lines as the empty space might be added to the request.
Instead of tossing in the code one huge unreadable line of URL with tons of parameters, try using the GET Request Query Parameters. I think most HTTP clients can do that. In the case of using Laravel PHP Framework, the whole thing should look like that:
$response = Http::get('https://maps.googleapis.com/maps/api/distancematrix/json', [
'units' => 'metric',
'origins' => 'Neuhöfer Damm 110,Germany',
'destinations' => 'Vogelweide 8,Hamburg',
'key' => YOUR_API_KEY'
]);
Enjoy your coding :)
I am fighting with GuzzleHTTP inside my API. I am using Lumen to develop API for communication with HashiCorp Vault API. To do this I installed GuzzleHTTP to make API calls.
For example I am trying to generate credentials for database user based on specific role. In the commandline cURL request responses to me with credentials for new user. While using Guzzle doesn't give me anything related to new created user.
I am new into Guzzle so I will be glad for any advice.
cURL request looks like this:
curl -X GET \
--header "X-Vault-Token: $VAULT_TOKEN" \
http://192.168.1.3:8200/v1/database/creds/my-role | json_pp
And the response gives me credentials I want:
{
"request_id" : "3eacc89b57b-2e2e-af6f-849f-868bdbfd2dc5e1",
"wrap_info" : null,
"renewable" : true,
"data" : {
"username" : "randomgeneratedusername",
"password" : "randomgeneratedpassword"
},
"auth" : null,
"lease_duration" : 3600,
"warnings" : null,
"lease_id" : "database/creds/my-role/randomsecretname"
}
When my Guzzle code looks like this:
class MySQLVaultAPI extends VaultAPI
{
public function createUser() {
$response = $this->getClient()->request('GET', $this->url.'/database/creds/my-role',
['headers' => $this->headers]);
}
}
And:
class VaultAPI
{
protected string $url;
protected Client $http;
protected array $headers;
public function __construct(Client $client)
{
$this->url = 'http://192.168.1.3:8200/v1';
$this->http = $client;
$this->headers = [
'X-Vault-Token' => 'secrettoken',
'Accept' => 'application/json',
];
}
}
And the response from GuzzleHTTP(I have used symfony dumper on $response object):
^ GuzzleHttp\Psr7\Response {#43 ▼
-reasonPhrase: "OK"
-statusCode: 200
-headers: array:4 [▼
"Cache-Control" => array:1 [▶]
"Content-Type" => array:1 [▶]
"Date" => array:1 [▶]
"Content-Length" => array:1 [▶]
]
-headerNames: array:4 [▼
"cache-control" => "Cache-Control"
"content-type" => "Content-Type"
"date" => "Date"
"content-length" => "Content-Length"
]
-protocol: "1.1"
-stream: GuzzleHttp\Psr7\Stream {#21 ▼
-stream: stream resource #137 ▶}
-size: null
-seekable: true
-readable: true
-writable: true
-uri: "php://temp"
-customMetadata: []
}
}
Okey,
I have found out the answer. All of the content is in GuzzleHttp\Psr7\Stream
So I had to make additional method in my VaultAPI service
public function getFullBody(Stream $responseBody)
{
$responseBody->seek(0);
$output = json_decode(trim($responseBody->getContents()), true);
if ($output === null) {
throw new \Exception('Error parsing response JSON ('.json_last_error().')');
}
return $output;
}
And then inside my MySQLVaultAPI I am calling it
public function createUser() {
$response = $this->getClient()->request('GET', $this->url.'/database/creds/my-role',
['headers' => $this->headers]);
return $this->getFullBody($response->getBody());
}
I am trying to send vacancies to the Google Indexing API whenever we receive them from our vacancy provider. However, despite having set all the appropriate permissions, I keep receiving 403 status codes.
I have followed the "Prerequisites for the Indexing API" guide and created a Service account. In my case, I have 4 entries for the domain in Google Search Console, so I followed MarcQuay's answer and added the service account to all 4 entries.
I use the following code to implement the Google API Client and make my calls. The method sendGoogleRequest() is called for every vacancy that we receive.
// Google API setup
function sendGoogleRequest(array $aData, int $sStatus = 0)
{
global $DOMAIN, $GOOGLE_AUTH_CONFIG, $GOOGLE_AUTH_SCOPE, $GOOGLE_API_ENDPOINT;
$googleClient = new Google_Client();
$googleClient->useApplicationDefaultCredentials();
$googleClient->addScope([$GOOGLE_AUTH_SCOPE]);
// Update Google Indexing API. This will notify Google that the URL should be crawled again.
$httpClient = $googleClient->authorize();
$endpoint = $GOOGLE_API_ENDPOINT;
$sJobUrl = $DOMAIN . '/vacancies/' . $aData['url'];
$sType = "";
if (!empty($sStatus)) {
switch ($sStatus) {
case 1:
$sType = "URL_UPDATED";
break;
case 2:
$sType = "URL_DELETED";
break;
}
}
$content = "{
\"url\": \"$sJobUrl\",
\"type\": \"$sType\"
}";
$response = $httpClient->post($endpoint, ['body' => $content]);
$status_code = $response->getStatusCode();
return $status_code;
}
I have tried debugging it and it seems that '$credentials' is empty in $googleClient->authorize()
$authHandler = $this->getAuthHandler();
if ($credentials) {
$callback = $this->config['token_callback'];
$http = $authHandler->attachCredentials($http, $credentials, $callback);
} elseif ($token) {
$http = $authHandler->attachToken($http, $token, (array) $scopes);
} elseif ($key = $this->config['developer_key']) {
$http = $authHandler->attachKey($http, $key);
}
return $http;
However I have no idea what could be the cause of this.
Using this code returns a '403' for every call. After having browsed the internet for quite some time now, I can seem to find a definite answer, except for 'make sure the service account is an owner' but as previously stated, this is already the case.
I hope someone can help me out, since I have been stuck on this for longer than I'd like to admit.
If any more information is required I'll gladly update my answer.
EDIT: As per request, here is the full error message return by the $httpClient->post() call.
Response {#268 ▼
-reasonPhrase: "Forbidden"
-statusCode: 403
-headers: array:11 [▼
"Vary" => array:3 [▼
0 => "X-Origin"
1 => "Referer"
2 => "Origin,Accept-Encoding"
]
"Content-Type" => array:1 [▼
0 => "application/json; charset=UTF-8"
]
"Date" => array:1 [▼
0 => "Tue, 24 Sep 2019 11:25:29 GMT"
]
"Server" => array:1 [▼
0 => "ESF"
]
"Cache-Control" => array:1 [▼
0 => "private"
]
"X-XSS-Protection" => array:1 [▼
0 => "0"
]
"X-Frame-Options" => array:1 [▼
0 => "SAMEORIGIN"
]
"X-Content-Type-Options" => array:1 [▼
0 => "nosniff"
]
"Alt-Svc" => array:1 [▼
0 => "quic=":443"; ma=2592000; v="46,43,39""
]
"Accept-Ranges" => array:1 [▼
0 => "none"
]
"Transfer-Encoding" => array:1 [▼
0 => "chunked"
]
]
-headerNames: array:11 [▼
"vary" => "Vary"
"content-type" => "Content-Type"
"date" => "Date"
"server" => "Server"
"cache-control" => "Cache-Control"
"x-xss-protection" => "X-XSS-Protection"
"x-frame-options" => "X-Frame-Options"
"x-content-type-options" => "X-Content-Type-Options"
"alt-svc" => "Alt-Svc"
"accept-ranges" => "Accept-Ranges"
"transfer-encoding" => "Transfer-Encoding"
]
-protocol: "1.1"
-stream: Stream {#256 ▼
-stream: stream resource #123 ▼
wrapper_type: "PHP"
stream_type: "TEMP"
mode: "w+b"
unread_bytes: 0
seekable: true
uri: "php://temp"
options: []
}
-size: null
-seekable: true
-readable: true
-writable: true
-uri: "php://temp"
-customMetadata: []
}
}
I ended up fixing this issue by using a Service account as AuthConfig
// Initialize Google_Client to submit vacancies to Indexing API
$googleClient = new Google_Client();
$googleClient->setAuthConfig($GOOGLE_AUTH_CONFIG);
$googleClient->addScope([$GOOGLE_AUTH_SCOPE]);
$googleClient->setAccessType("offline");
$googleClient is used for every call
function sendGoogleRequest(array $aData, int $sStatus = 0, Google_Client $google_Client) {
global $DOMAIN;
$sJobUrl = 'https://MY-DOMAIN.com/' . $aData['url'];
$sType = "";
if (!empty($sStatus)) {
switch ($sStatus) {
case 1:
$sType = "URL_UPDATED";
break;
case 2:
$sType = "URL_DELETED";
break;
}
}
$urlNotification = new Google_Service_Indexing_UrlNotification();
$urlNotification->setType($sType);
$urlNotification->setUrl($sJobUrl);
$indexingService = new Google_Service_Indexing($google_Client);
$response = $indexingService->urlNotifications->publish($urlNotification);
return $response;
}
This script is then called once per day and posts every URL to the Indexing API. To not exceed the limit of 250 requests per day, make sure you exclude vacancies that should no longer be indexed.
When I use Postman to make an API call I receive a JSON object..which is what I expected.
However When I make same call with Guzzle like so:
$client = new \GuzzleHttp\Client(['base_uri' => 'https://api.dev/']);
$response = $client->request('GET', 'search', [
'verify' => false,
]);
var_dump($response->getBody()); //null
var_dump($response); //returns below
I get dump below from Guzzle
Response {#304 ▼
-reasonPhrase: "OK"
-statusCode: 200
-headers: array:8 [▼
"Server" => array:1 [▶]
"Content-Type" => array:1 [▼
0 => "application/json"
]
"Transfer-Encoding" => array:1 [▶]
"Connection" => array:1 [▶]
"Cache-Control" => array:1 [▶]
"Date" => array:1 [▶]
"X-RateLimit-Limit" => array:1 [▶]
"X-RateLimit-Remaining" => array:1 [▶]
]
-headerNames: array:8 [▼
"server" => "Server"
"content-type" => "Content-Type"
"transfer-encoding" => "Transfer-Encoding"
"connection" => "Connection"
"cache-control" => "Cache-Control"
"date" => "Date"
"x-ratelimit-limit" => "X-RateLimit-Limit"
"x-ratelimit-remaining" => "X-RateLimit-Remaining"
]
-protocol: "1.1"
-stream: Stream {#302 ▼
-stream: stream resource #15 ▼
wrapper_type: "PHP"
stream_type: "TEMP"
mode: "w+b"
unread_bytes: 0
seekable: true
uri: "php://temp"
options: []
}
-size: null
-seekable: true
-readable: true
-writable: true
-uri: "php://temp"
-customMetadata: []
}
}
getBody() returns a stream. If you want to get all the content at once you can use getContents() method and decode json while at it (if you need to)
$payload = json_decode($response->getBody()->getContents());
Further reading - Guzzle Responses
If $response->getBody()->getContents() doesn't work for you, try:
$response->getBody()->__toString();
As mentioned here, sometimes getContents's stream pointer is already at the end of stream and then it returns empty response, but __toString resets it by default.
Milan's solution worked for me but the response coming from API was malformed.
I had to use stripslashes to convert it to a PHP array:
$response = $response->getBody()->__toString();
$response = stripslashes($response);
$data = json_decode($response, true);
We are required to get the live data from API URL. First we need to connect with the server, in this case $uri1, then we have to use second URI (e.g: $uri2) which will the give the output in JSON format.
What I've tried to do so is:
$un = 'abc'; //username
$pa = '123'; //password
$base_uri = 'http://example.com:82/';
$uri1 = 'api/instant/connectopc';
$uri2 = 'api/instant/displaydata?site=SITE';
$cookieFile = 'jar.txt';
$cookieJar = new FileCookieJar($cookieFile, true);
$client = new Client([
'base_uri' => $base_uri,
'auth'=>[$un, $pa],
'cookie'=>$cookieJar,
'curl' => [
CURLOPT_COOKIEJAR => 'jar.txt',
CURLOPT_COOKIEFILE => 'jar.txt'
]
]);
$promises = [
'connect' => $client->getAsync($uri1),
'live' => $client->getAsync($uri2)
];
$results = Promise\unwrap($promises);
dd($results);
//$body = $results['live'];
OUTPUT
array:2 [▼
"connect" => Response {#345 ▼
-reasonPhrase: "OK"
-statusCode: 200
-headers: array:10 [▶]
-headerLines: array:10 [▶]
-protocol: "1.1"
-stream: Stream {#336 ▶}
}
"live" => Response {#357 ▼
-reasonPhrase: "OK"
-statusCode: 200
-headers: array:10 [▶]
-headerLines: array:10 [▶]
-protocol: "1.1"
-stream: Stream {#348 ▶}
}]
Data: dd($body->getBody()->getContents()); //-----> output: null
i) First we need to connect with uri 1 after that.
ii) uri 2 will give live data in realtime.
Both URIs are connecting but the content of second URI ($uri2) is NULL.
Any help will be appreciated.
Regards,
NKR
I found the solution:
....
$connect = $client->get($uri1); //<-----this will connect to server.
//Now for second URI, use JAR file to utilize the old cookie
//to ensure the URI is using previous authentication/session.
$live = $client->get($uri2, ['cookie'=>$cookieJar]);
dd($live);