Downloading file from Google Drive by using Google Picker - google-drive-api

I'm trying to build a webpage on which user can select an image from Google Drive by using Google Picker and download selected files to my server by using PHP script.
I have managed to setup the Picker, and i get fileIDs but when i pass this IDs to my backend and try GET method i get an authentication error.
I have spent 2 days working on this and researching, but more I read google official documentation the more confused I am.
Can someone tell me, or link me the example how implement this? Is it possible to somehow pass the oAuthv2 token from the GooglePicker to my PHP backend and then use that token with the GET request?
Thank you very much in advance !
edit:
here is the error that im geting when i try GET https://www.googleapis.com/drive/v2/files/SOME_FILE_ID
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.",
"extendedHelp": "https://code.google.com/apis/console"
}
],
"code": 403,
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup."
}

Before you call the GET, you must set an Authorisation-Header containing an up to date access token.
If you want to do this manually, the steps are:-
Request permission using your app-id/client-id and scopes. This will return to you an authorization code.
Use the authorization code to request a refresh token and an access token
Store the refresh token for future use
Set the access token in an http header Authorization (something like Authorization: Bearer ya29.AHES6ZR3HQa9trJM_IQcgNlM0SI4FvLQFiQfcAZCWLobfpjqtGlT6A)
Issue your GET
You can see the whole process in action by clicking around at https://developers.google.com/oauthplayground/
Alternatively, if your client app already has an access token, you could send that to your server alongside the file ID and your server can simply set that directly into the Authorization header.
There are PHP libraries you can use instead, eg. go to https://developers.google.com/drive/v2/reference/files/insert and scroll down to see the PHP samples. It's entirely your choice whether you build the URLs by hand or use the libraries. The big disadvantage to the libraries is that if something goes wrong, you really need to understand and trace the http anyway to see what's going on, so might as well get to learn and love them from day one.
The message "Daily Limit for Unauthenticated Use Exceeded" seems to confuse first timers (me included). It's the "Unauthenticated Use" which is the important part, meaning you haven't set the Authorization header. Google APIs have a daily quota for unauthorized use (stuff like URL shortener). In the case of Drive, that quota is ZERO, hence the error.

#pinoyyid said everything as it is, and inspired by him, here is the actual solution I came up with:
If you want to download a file, you need two variables - oAuthToken and fileId
oAuthToken you get from JS client side when user authenticates.
If you use the example from google docs (https://developers.google.com/picker/docs/), the function looks like this:
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
oauthToken; // <-- THIS IS THE Bearer token
createPicker();
}
}
fileId you get from when user picks a file. Again, a modified example from google docs:
function pickerCallback(data) {
if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
alert('You picked fileId: ' + doc[google.picker.Document.ID]);
}
}
Probably you will pass these data as a form request, or through ajax. Simple cURL call from backend to download the file:
$oAuthToken = 'ya29.XXXXXXXXXXXXXXXXXXXXXXXXX-XXXXXXXXX-XXXXXXX-XXXXXX-X-XXXXXXXXXXXX-XXXX';
$fileId = '0B4zzcXXXXXXXXXXXXXXXXXXXXXX';
$getUrl = 'https://www.googleapis.com/drive/v2/files/' . $fileId . '?alt=media';
$authHeader = 'Authorization: Bearer ' . $oAuthToken ;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $getUrl);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
$authHeader ,
]);
$data = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
file_put_contents("destination-file.jpg", $data);
Docs about file download: https://developers.google.com/drive/web/manage-downloads

You store the refresh token in the database. When an API call is checked whether the current token is still valid. If not, a new access-token is retrieved by using the refresh-token

Related

Chrome.identity works perfectly without asking for credentials

I have implemented the chrome.identity launchWebAuthFlow to authenticate users of a web extension against an oauth2 provider and the entire flow works perfectly, I receive the access token back in the redirect URL, I extract the token using a regex and then it is valid and accepted to query the APIs.
However, I do not understand why it does not prompt anymore for credentials when I launch again the launchWebAuthFlow. Instead, it retrieves another (valid !) token in the background. Don't get me wrong, I like this, and I prefer it works in the background, but I just don't understand how. Even after clearing all cookies and local data, when I launch the launchWebAuthFlow again it just works in the background without asking for credentials...where are they stored?
Also, not sure if that helps, but my flow is the following:
extension ->oauth2 server->azure ad SSO->enter credentials->redirect to extension
So the real authentication is managed by Azure AD. However, even when I'm signed out from Microsoft, the extension keeps getting a valid auth token when the below code is triggered and without asking for credentials...so the credentials must be stored somewhere...
chrome.identity.launchWebAuthFlow(
{
url: dev.identity_url(),
interactive: true
},
function (responseWithToken) {
// the access token needs to be extracted from the response.
console.log(responseWithToken);
let token = responseWithToken.match(/(?<=access_token=).*(?=&token_type)/);
token = token[0];
chrome.storage.local.set({ "auth-token": token }, function () {
console.log(`Access Token has been saved: ${token}`);
});
}
);

How can I group pushbullet notifications

I am sending push notifications to my Android phone. I want these notifications grouped so my notification list does not get flooded.
According to the documentation messages from the same 'source' get grouped but on my phone the messages always show up ungrouped.
I call the push API from a Google Apps script and have tried setting source_device_iden, source_user_iden and notification_tag when I call the push API. None of these seem to make any difference.
How can I get the pushmessages to be grouped on my phone?
Google Apps script code
function pushNoteToPhone(title, body) {
var digest = "Basic "+Utilities.base64Encode(PUSH_BULLET_TOKEN+":");
var options = {
"method" : "post",
"payload" : {
"device_iden" : MYPHONE_ID,
"type" : "note",
"title" : title,
"body" : body,
"source_device_iden" : <device id>,
"notification_tag": "tag1",
},
"headers" : {
"Authorization": digest
}
};
var push_bullet_url = "https://api.pushbullet.com/v2/pushes";
UrlFetchApp.fetch(push_bullet_url, options);
}
The easiest way to do this (admittedly it should be easier) is to create an OAuth Client and then send using an access token for that oauth client. That way the pushes will all appear to come from that client instead of you. This is how IFTTT and Zapier work on Pushbullet.
Here's how to setup an oauth client: https://docs.pushbullet.com/#oauth
To get an access token you can use the "oauth test url" on the create client page, you will end up with an access token in the URL once you approve access. Use that access token instead of your normal one and the pushes will appear to come from the client instead of you.
Don't know how you are trying to update the notification but without the code, my guess is that you are trying to pass a new Notification ID to each notification being sent to the device. However, please take a look here and look under, "Updating Notifications". As explained in the documentation, by passing the same ID to each notification it will either group these notifications on the device or create a new one in case the old one has been dismissed.

Access token is refreshed but not updated in my DB

I am using Box API 2.0, and I have a service running to sync the documents between Box and my application. Sometimes something happens like a network error or someone updating the application or restarting the server while the service is requesting a new access token. In this case my access token in my local DB is not the same as the issued one, and because of the error of the restart the new issued token is not saved in my local DB for future use.
How can I prevent something like this from happening? The refresh token is also renewed with each request, so it can't be used to issue a new access and refresh token.
You can use your old refresh token again, as long as you have not made calls with the new access token.
So lets say you have Access Token and Refresh token AT1/RT1. At some point you call the refresh URL to get a new set of tokens:
curl https://www.box.com/api/oauth2/token \ -d 'grant_type=refresh_token&refresh_token={RT1}&client_id={your_client_id}&client_secret={your_client_secret}' \ -X POST
And then your network goes dark, and you never get the response.
Box sent you back a new set of tokens AT2/RT2 like this:
{ "access_token": "AT2", "expires_in": 3696, "restricted_to": [], "token_type": "bearer", "refresh_token": "RT2" }
But you never got it. Boo :(... But never fear!
You can send the exact same request again, and Box's server will give you back EXACTLY the same response.
The same AT2/RT2
Then when you use AT2 (or RT2), RT1 becomes invalid.

Examine raw HTTP request from UrlFetchApp.fetch

I am working on something to interact with Amazon's REST API, but I keep getting an error in my response that points to a mal-formed request. I don't see any errors in the code (the parameter that it says is missing is clearly there), so I want to see the raw request that is being sent.
I don't see any available method that will let me do this. Maybe a server that will just include my request as its response?
Create your own endpoint that will echo to the screen your request. For example, to echo a GET request, send it to a script like this (that's been Publish > Deploy as web app):
function doGet(e) {
var test = 'Echo at ' + new Date() + '\n' + e.queryString;
return ContentService.createTextOutput(test);
}
A little late to the game (10 years) but you can now do this:
const requestResult = UrlFetchApp.getRequest(url, options);
Run it in debug mode and break after this executes to examine requestResult.
See: https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#getrequesturl,-params for an official description and a full explanation of the properties for the options parameter.

Calendar API Response with '403: Daily Limit Exceeded' - Google Chrome Extension

I started with an little application for showing some relevant information to our developer team, which is collected from different sources. Like google calendar, our project backoffice, an openarena server-logs etc.
I started with an web application, but then decided to switch the project to an chrome extension. Now i already had the google calendar integration up and running using the V3 Javascript(alpha) Client-Lib. This wasn't working anymore because i had to change the OAUTH dance, from the one for web-apps, to the one for packaged/installed apps.
For this i followed the tutorial (It's my first extension.) http://code.google.com/chrome/extensions/tut_oauth.html and got the OAUTH dance working again. Now i'am trying to request my calendar-data from the google api using the signedRequest-Methode from the tutorial, but alway receive the response "Daily Limit Exceeded. Please sign up" (Api Console shows i haven't performed any request).
Maybe someone has an idea what i am doing wrong here, because i tried everthing i could think about. Thanks in advance, Florian
Code -
manifest.json:
{
"name": "MIS",
"version": "0.1",
"description": "Monitor Information System",
"background_page": "background.html",
"browser_action": {
"default_icon": "img/mis/icon.png",
"default_title": "Mis"
},
"permissions": [
"tabs",
"https://www.googleapis.com/",
"https://www.google.com/"]
}
background.html:
...
oauth = ChromeExOAuth.initBackgroundPage
({
'request_url':'https://www.google.com/accounts/OAuthGetRequestToken',
'authorize_url':'https://www.google.com/accounts/OAuthAuthorizeToken',
'access_url':'https://www.google.com/accounts/OAuthGetAccessToken',
'consumer_key': 'anonymous',
'consumer_secret': 'anonymous',
'scope': 'https://www.googleapis.com/auth/calendar',
'app_name': 'Mis'
});
...
main.html Methode call:
function performCalendarEventsRequest(calendarId)
{
var requestUrl = 'https://www.googleapis.com/calendar/v3/calendars/'+calendarId+'/events';
var request = {
'method': 'GET',
'headers': {
'GData-Version': '3.0',
'Content-Type': 'application/atom+xml'
},
'parameters': {
'alt': 'json'
},
'body': 'Data to send'
};
oauth.sendSignedRequest(requestUrl, calendarEventsRequestCallback, request);
}
Since your consumer key and secret are both set as anonymous you are not identifying your application in any way.
You can either replace these with the Client ID and Client secret values respectively, which can be found on the API Access tab on your projects page in the API Console, or you can pass the API key (found just under the auth tokens on the same page) using an additional key parameter.
This is the same for most Google APIs.
Source: http://code.google.com/apis/calendar/v3/using.html#APIKey
I know this is an old question but I was stuck in the same error using the PHP implementation (Beta) of the API (2016-01-06), because I initially thought the setAuthConfigFile was all it was needed:
$client = new Google_Client();
$client->setApplicationName('MyCalendarAppName');
$client->setAuthConfigFile(APPPATH.'client_secret.json'); //file downloaded from GDC:
// https://console.developers.google.com/apis/credentials?project=YOUR-PROJECT-ID
$client->addScope(Google_Service_Calendar::CALENDAR_READONLY);
$service = new Google_Service_Calendar($client);
The error was not really due to a "daily limit" of requests but to the fact that I (owner of the Google account) hadn't explicitly given access permissions to the calendar. This is how to do it:
access the URL returned by the createAuthUrl() method (which can be invoked before calling the service);
a "Deny" and "Allow" form for accessing the calendars shows up – press Allow;
a code is returned – copy&paste this code to the authenticate($code) method and voilá, no more 403: Daily Limit Exceeded errors.
For doing this, just use the following lines before invoking the service:
//$client->createAuthUrl();
// - invoke the method above one time only: returns a URL with the "Allow" form
// which will give the code for authentication
$client->authenticate('YOUR_CODE_GOES_HERE');
$service = new Google_Service_Calendar($client); //invokes the Calendar service
However, this will allow a one time access to the calendar. If you try this twice, you get:
Google_Auth_Exception: Error fetching OAuth2 access token, message: 'invalid_grant: Code was already redeemed.'
Meaning that a token is needed to reaccess the calendar with the reedemed code. This token is returned by the authenticate method and can be assigned to the client through the setAccessToken method:
//get the access token you previously stored or get a new one to be stored:
$accessToken = $client->authenticate('YOUR_CODE_GOES_HERE');
//after the if-else blocks...
$client->setAccessToken($accessToken);
//refresh the token if it's expired
if ($client->isAccessTokenExpired())
$client->refreshToken($client->getRefreshToken());
The goal is to reuse that access token repeatedly (implicitly also reusing the redeemed code) and only invoke a refreshToken when the token expires. Google's quickstart creates a specific file for storing this token (calendar-php-quickstart.json) and only uses the authenticate method when the token is not found in that file. I.e. the setAccessToken is the only Client authentication method (besides the setAuthConfigFile) that needs to be used in subsequent requests.
I only fully understood this OAuth 2.0 logic by going through these errors and since this question attracted so many people already, perhaps this may help others...