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...
Related
I suppose my question is twofold: doGet() in the following context will just fail after 0.1~0.2 seconds without posting logs, so I have no idea how to troubleshoot it by myself. Additionally, if I'm having the script execute on my behalf, do I have to push a request with my authorization token to a more "pertinent" area than just the sheet name, such as within the iteration itself? Read further for more details:
I have a source spreadsheet where I am cross-referencing user inputted data to validate the information we have "on file". Most of our clients are over the age of 55, so I am trying to reduce end-user complexity by having the script run on my behalf whenever they need to use it (to bypass the Authorization screen, with the big scary "This application could be unsafe!" message). The way I've read to accomplish this seems to be with doGet(), so I set up a low-level HTTP Get request that just pushes a doGet() with my OAuth token, returning the sheet name. I also set up a masking function specifically to do this, and linked it to the button originally used for the iteration logic. The doGet() looks like this:
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(iterator(e));
Logger.log(content);
return content;
}
and the button that uses UrlFetchApp looks like:
const runMask = () => {
const active = SpreadsheetApp.getActiveSheet().getSheetName();
const v4 = 'https://script.google.com/macros/s/<scriptid>/dev' // ScriptApp.getService().getUrl() posts 404
UrlFetchApp.fetch(`${v4}?sheetName='${active}'`, {
headers: { Authorization: `Bearer ${ScriptApp.getOAuthToken()}` },
});
I have some logs set up within the real runMask() that proceed all the way to the end of the program, giving me real URLs and OAuth tokens, so I know it's making it through runMask() without an issue. However, the doGet() log doesn't post anything, even at the top of the function. I can see that it's executing the trigger in my execution log, but the log itself remains empty.
I've tried:
using ScriptApp.getService().getUrl() in place of v4: posts 404 in the log w/ truncated server response
replacing ${active} with the name of the sheet: same issue; logging ${active} also returns the correct name of the sheet.
Beyond this, I'm not even sure what to do. I have everything scoped correctly (auth/spreadsheets.currentonly, auth/script.external_request, and auth/userinfo.email), and I have no issues about operational security (as both the spreadsheet and script are written by me, the clients have no need to grant access to their entire drive). Before trying to implement doGet() and bypass the authorization screen, the iterator itself worked just fine. As such, I have chosen not to include it here, as it's hardly relevant (the function that executes the iteration function never makes it to that point).
I understand this has been quite the deluge of information; I'd be happy to provide more information or context as needed.
Getting ReferenceError: iterator is not defined (line 12, file "ag2")
With this:
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(iterator(e));
Logger.log(content);
return content;
}
Issued with url/exec?option=A
It runs with
const doGet = e => {
Logger.log(`Recieved HTTP request.`);
const content = ContentService.createTextOutput(JSON.stringify(e));
Logger.log(content);
return content;
}
and returns the appropriate stringified object
Only use the test URL (/dev) for testing the web app from a web browser.
Before doGet from a web browser using a versioned deployment (/exec) remember to publish a new version.
Assign a Google Cloud Project to your Google Apps Script project. For details see https://developers.google.com/apps-script/guides/cloud-platform-projects.
To make it easier to debug your avoid calling functions from a Google Apps Script method like createTextOutput, instead, assign the function result to a variable and use it as the method parameter, i.e. replace
const content = ContentService.createTextOutput(iterator(e));
by
const something = iterator(e);
const content = ContentService.createTextOutput(something);
For debugging purposes, create a function to call your doGet function, and check that it hasn't any problem to run, i.e.
function __test__doGet(){
const e = {
parameter: {}
}
doGet(e);
}
Related
Exception handling in google apps script web apps
Issue:
When I saw your question, I'm worried about I have everything scoped correctly (auth/spreadsheets.currentonly, auth/script.external_request, and auth/userinfo.email).
If you are using only the following scopes at oauthScopes of appsscript.json,
https://www.googleapis.com/auth/spreadsheets.currentonly
https://www.googleapis.com/auth/script.external_request
https://www.googleapis.com/auth/userinfo.email
Unfortunately, these scopes cannot be used for access to Web Apps. Although I'm not sure about the method for running your function of runMask, I thought that this might be the reason for your issue.
Solution:
If you want to access Web Apps of https://script.google.com/macros/s/<scriptid>/dev using the access token retrieved by ScriptApp.getOAuthToken(), please include the following scope.
https://www.googleapis.com/auth/drive.readonly
or
https://www.googleapis.com/auth/drive
After you include the above scope, please reauthorize the scopes, and test it again. When your function of iterator has already been declared and the script worked, by running runMask, you can see the log of Logger.log(Recieved HTTP request.) and Logger.log(content) at the log.
Reference:
Taking advantage of Web Apps with Google Apps Script
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}`);
});
}
);
Im using SendBird platform API to create chat in my website,
when i try to retrieve the previously send messages in a channel using below API:
https://api.sendbird.com/v3/{channel_type}/{channel_url}/messages
METHOD : GET
Request: ?message_ts=long(Required)&prev_limit=int(Optional, default: 15, 0~200)&next_limit=int(Optional, default: 15, 0~200)&include=boolean(Optional, default: true)&reverse=boolean(Optional, default: false)&custom_type=string(Optional)&message_type=string(Optional)&sender_id=string(Optional)
after passing value to above request,im getting an error saying
{
"message": "Not authorized. Get messages function can be called only from Park or Enterprise plan.",
"code": 400108,
"error": true
}
can some one please help me with this
Thanks
Nithin
The Message Retrieval API which you are calling is a premium feature as you can see in Senbird's Pricing Page.
You will have to contact Sendbird and upgrade to their premium plan in order to retrieve message and successfully call the endpoint.
You can retrieve messages in SDKs (available for JavaScript too).
In Android call this method,
mChannel.getPreviousMessagesByTimestamp()
where mChannel is an object of type GroupChannel(OpenChannel also allowed).
And as far as I know, it is ok to retrieve messages like that.
For Javascript
refer to this link
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.
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