How to use UrlFetchApp.fetchAll with Calendar API in google workspace addon - google-apps-script

Our Google Workspace Addon synchronises calendar events from Google Calendar to an external database.
As part of this process we update multiple Calendar Events using the patch command
Calendar.Events.patch({extendedProperties:pp},_e.calendar.calendarId,_e.calendar.id);
The problem we have is that we need to perform this operation multiple times within a limited amount of time (app script will time-out)
We could achieve this with the UrlFetchApp.fetchAll function but unfortunately to call this we would need to call the calendar api directly. Although this is easily done - we do not have the AccessToken as this is not available and is handled by the API.
Does anybody know how to get the accessToken (without pushing the user through a separate OAuth approval process) that is being used by the Calendar API so that we can utilise it to call UrlFetchApp.fetchAll

As long as you have the correct Calendar scope in your add-on, I think you can just pass ScriptApp.getOauthToken() as the token in the UrlFetchApp request:
getOAuthToken()
Gets the OAuth 2.0 access token for the effective user. If the script's OAuth scopes are sufficient to authorize another Google API that normally requires its own OAuth flow (like Google Picker), scripts can bypass the second authorization prompt by passing this token instead. The token expires after a time (a few minutes at minimum); scripts should handle authorization failures and call this method to obtain a fresh token when needed.
The token returned by this method only includes scopes that the script currently needs. Scopes that were previously authorized but are no longer used by the script are not included in the returned token. If additional OAuth scopes are needed beyond what the script itself requires, they can be specified in the script's manifest file.
Return
String — A string representation of the OAuth 2.0 token.
Example Request:
function patchCalendarEvents() {
const calendarId = "calendarId"
const eventIds = [] // your event ID list
const baseUrl = `https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events/{{eventId}}`
const requests = []
eventIds.forEach(function (event) {
requests.push({
"url": baseUrl.replace("{{eventId}}", event),
"method": "PATCH",
"headers": {
"Authorization": "Bearer " + ScriptApp.getOAuthToken()
},
"payload": '{"extendedProperties": {"shared": {"testKey": "Test Value"}}}',
"contentType": "application/json",
})
})
console.log(requests)
const responses = UrlFetchApp.fetchAll(requests)
responses.forEach(function(response) {
console.log(response.getContentText())
})
}

Related

How to use Jobs resource in google apps script?

How do I use the 'Job' resource within the Google Apps Script?
I understand that YouTube Reporting API has to be enabled, but what object can it be called from?
I am trying to access YouTube Reporting API. is this the right way to call it?
If so, my Apps Script can't identify youtubeReporting, why would that be?
youtubeReporting.jobs()
To sum up, my goal is to extract 'Content Owner Reports' bulk/query, pretty much read all the steps leading to that, except the very starting point, which is an object name.
Note: Although this answer deals with YouTube reporting api, the flow should be the same for all Google apis, where advanced Google service wrapper isn't provided.
AFAICT, YouTube reporting API isn't directly available as a advanced Google service. You may be able to use YouTubeanalytics api, which is provided as a advanced Google service. To access reporting api, You need to directly connect to the api through HTTP calls and urlfetch.
Required reading:
Advanced or HTTP
ScriptApp#getOauthToken
YouTube reporting rest
UrlFetchApp#fetch
Editing manifest#Setting explicit scopes
Switch to standard GCP
API Library
Solution:
It is possible to access YouTube reporting api from Google apps script using UrlFetchApp
Full OAuth flow can be bypassed using oauth token provided by ScriptApp
Include scopes in the appsscript.json manifest file.
Switch to a standard GCP and enable the YouTube reporting api for this project. Otherwise 403 will be returned.
Snippet:
/**
* #description A wrapper for accessing Google apis from apps script
* #param {string} url Google URL endpoint
* #param {string} method HTTP method
* #param {object} requestBody Requestbody to send
* #param {object} pathParameters Parameters to modify in the url
* #param {object} queryParameters Queryparameters to append to the url
* #return {object} response
*/
function accessGoogleApiHTTP_(
url,
method,
requestBody = {},
pathParameters = {},
queryParameters = {}
) {
const modifiedUrl = Object.entries(pathParameters).reduce(
(acc, [key, value]) => acc.replace(key, value),
url
);
const queryUrl = Object.entries(queryParameters).reduce(
(acc, param) => acc + param.map(e => encodeURIComponent(e)).join('='),
'?'
);
const options = {
method,
contentType: 'application/json',
headers: {
Authorization: `Bearer ${ScriptApp.getOAuthToken()}` /*Need to set explicit scopes*/,
},
muteHttpExceptions: true,
payload: JSON.stringify(requestBody),
};
const res = UrlFetchApp.fetch(
modifiedUrl + queryUrl,
options
).getContentText();
console.log(res);
return JSON.parse(res);
}
/**
* #see https://developers.google.com/youtube/reporting/v1/reference/rest/v1/jobs/create
* #description POST https://youtubereporting.googleapis.com/v1/jobs
*/
function createYtReportingJob() {
const reportTypeId = 'id',
name = 'name';
const jsonRes = accessGoogleApiHTTP_(
'https://youtubereporting.googleapis.com/v1/jobs',
'POST',
{ reportTypeId, name },
undefined,
{ onBehalfOfContentOwner: 'contentOwnerId' }
);
}
Manifest scopes:
"oauthScopes": [
"https://www.googleapis.com/auth/yt-analytics",
"https://www.googleapis.com/auth/script.external_request"
]
Answer
In order to access the YouTube Reporting API you have to add YouTube Analytics API and optionally add the YouTube Data API as well. From this point you can create a report as in the Apps Script: YouTube Analytics example.
Bear in mind:
When importing the services like YouTube Analytics API be aware with the version.
You can specify the amount of metrics desired.
Reference
Apps Script: YouTube Analytics
YouTube Data API: Channel list
Analytics and Reporting APIs: Content Owner Reports

how to make a correct HTTP request to BigQuery from google script

I am working in google script API trying to get a schema of a table from BiqQuery... not sure why it is so troublesome.
I am sending a request like this :
let url = 'https://bigquery.googleapis.com/bigquery/v2/projects/'+ projectId +'/datasets/'+ datasetId +'/tables/' +tableId;
var response = UrlFetchApp.fetch(url)
I am getting this response:
Exception: Request failed for https://bigquery.googleapis.com returned code 401. Truncated server response: { "error": { "code": 401, "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie ... (use muteHttpExceptions option to examine full response) (line 68, file "bigQuery")
I have been able to load data to bigQuery alright... not sure why this does not work. I have looked at the OAuth fields in manifest and the script does have access to bigQuery...
no success also when adding this to the options field of the UrlFetch request
var authHeader = 'Basic ' + Utilities.base64Encode(USERNAME + ':' + PASSWORD);
var options = {
headers: {Authorization: authHeader}
}
Use bearer tokens
The reason why the BigQuery API rejects your requests is that the endpoint requires one of the following scopes to be provided with the access token to work, and it is missing from the request:
https://www.googleapis.com/auth/bigquery
https://www.googleapis.com/auth/cloud-platform
https://www.googleapis.com/auth/bigquery.readonly
https://www.googleapis.com/auth/cloud-platform.read-only
The actual issue here is that the basic authorization scheme lacks info about any claims, only sending over correct credentials. Since you are requesting the endpoint directly with UrlFetch service, despite correctly specifying the scopes in the manifest, they will not be sent over.
ScriptApp service now provides an easy method to get a valid bearer token without using an OAuth 2.0 library or building the flow from scratch: getOAuthToken. Pass it to an Authorization header as bearer token, and you should be all set:
const token = ScriptApp.getOAuthToken();
const options = {
headers : {
Authorization : `Bearer ${token}`
}
};
Use Advanced Service
As an alternative, there is an official advanced service as a wrapper around BigQuery REST API that will manage authentication and response parsing for you.
You must enable the BigQuery advanced service before using it
Also, note that the advanced service identifier is configurable, so you have to reference the identifier you chose.
In your case, the service can be used as follows (assuming you used the default BigQuery identifier). There is also the 4th argument of type object that contains optional arguments (not shown here):
Bigquery.Tables.get("projectId","datasetId", "tableId");
The method chain above corresponds to tables.get method of the BigQuery API.

Obtain an id token in the Gmail add-on for a backend service authentication

The background
I'm using the Google Apps Script to create a Gmail Add-on.
Via this plugin, I would like to connect to my backend server (a non-Google service) using a REST service request. The request has to be authorised. When authorised, I could then make requests to that server to receive data associated with that user in the database. I'm already using Google sign-in in my webapp to sign in to the backend service - at the front end, I receive the id_token inside of the GoogleUser object in the authorisation response.
The problem
I need this id_token to log in to my backend service when connecting to it via the Gmail plugin. However, I couldn't find a way how to access the token.
The research
I would assume the token must be available through the API in the Apps Script.
In the webapp, I receive the id_token using the Google Auth API like this:
Promise.resolve(this.auth2.signIn())
.then((googleUser) => {
let user_token = googleUser.getAuthResponse().id_token; // this is the id_token I need in the Gmail plugin, too
// send the id_token to the backend service
...
};
In the Google Apps Script API I could only find the OAuth token:
ScriptApp.getOAuthToken();
I assumed the token could also be stored in the session. The Google Apps Script API contains the Session class and that itself contains the getActiveUser method, which returns the User object. The User object, however, only contains the user's email address, no id token (or anything else for that matter):
Session.getActiveUser().getEmail();
The question(s)
Is there a way to obtain the id token?
Am I choosing the right approach to logging in to the backend server using the data of the signed-in user in the Gmail?
Method 1: use getIdentityToken()
Gets an OpenID Connect identity token for the effective user:
var idToken = ScriptApp.getIdentityToken();
var body = idToken.split('.')[1];
var decoded = Utilities.newBlob(Utilities.base64Decode(body)).getDataAsString();
var payload = JSON.parse(decoded);
var profileId = payload.sub;
Logger.log('Profile ID: ' + profileId);
Method 2: use Firebase and getOAuthToken()
Steps to get Google ID Token from Apps Script's OAuth token:
Enable Identity Toolkit API for your Apps Script project.
Add new Firebase project to your existing Google Cloud Platform project at https://console.firebase.google.com/
Create Firebase app for platform: Web.
You will get your config data: var firebaseConfig = {apiKey: YOUR_KEY, ...}.
Enable Google sign-in method for your Firebase project at https://console.firebase.google.com/project/PROJECT_ID/authentication/providers.
Use Apps Script function to get ID Token for current user:
function getGoogleIDToken()
{
// get your configuration from Firebase web app's settings
var firebaseConfig = {
apiKey: "***",
authDomain: "*.firebaseapp.com",
databaseURL: "https://*.firebaseio.com",
projectId: "***",
storageBucket: "***.appspot.com",
messagingSenderId: "*****",
appId: "***:web:***"
};
var res = UrlFetchApp.fetch('https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp?key='+firebaseConfig.apiKey, {
method: 'POST',
payload: JSON.stringify({
requestUri: 'https://'+firebaseConfig.authDomain,
postBody: 'access_token='+ScriptApp.getOAuthToken()+'&providerId=google.com',
returnSecureToken: true,
returnIdpCredential: true
}),
contentType: 'application/json',
muteHttpExceptions: true
});
var responseData = JSON.parse(res);
idToken = responseData.idToken;
Logger.log('Google ID Token: ');
Logger.log(idToken);
return idToken;
}
Kudos to Riël Notermans
You should enable oAuth scopes,
https://developers.google.com/apps-script/concepts/scopes

"Insufficient Permission" when trying to authenticate to cloud-storage via apps-script

I am about to give up on this as I can't find out what I am doing wrong.
I have a cloud-storage bucket with our companies billing data (json file objects written by google) that I am supposed to process into spreadsheets.
As there is no apps script API for oauth2, I am using the custom OAuth2 library provided by google with the key "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF", and have setup the auth request as shown in this example for service accounts:https://github.com/googlesamples/apps-script-oauth2/blob/master/samples/GoogleServiceAccount.gs
The token is being created and put into the scripts property store, where I can view it. So far so good.
I have this code for requesting the token and then I am trying to list the contents of the bucket in the function "getFilesList()":
function getService() {
return OAuth2.createService('CloudStoreGrab-Service')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(creds_private_key)
.setIssuer(creds_client_email)
.setSubject(creds_user_email)
.setPropertyStore(PropertiesService.getScriptProperties())
.setScope(['https://www.googleapis.com/auth/drive','https://www.googleapis.com/auth/script.external_request','https://www.googleapis.com/auth/script.storage','https://www.googleapis.com/auth/spreadsheets']);
}
function getFilesList() {
var service = getService();
service.reset();
if (service.hasAccess()) {
var url = 'https://www.googleapis.com/storage/v1/b/'+bucket+'/o';
var response = UrlFetchApp.fetch(url, {
method: "GET",
muteHttpExceptions: true,
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
}
});
}
Logger.log("Response:", response.getContentText())
}
No matter what I seem to try, the request always returns "403 Insufficient Permission". The service account has all necessary roles and permissions activated though (DwD, Storage-Administrator, Project-Owner). When I authenticate with the same credentials from gcloud and then browse the bucket with gsutils I can see the listing. This leads me to believe, that I am still requesting the auth token incorrectly. I tried using the generated token with curl and am getting the same Insufficient Permission response.
What am I doing wrong, while requesting the token?
Are the requested scopes too narrow?
Are the requested scopes too narrow?
That they are. You can find the OAuth scopes for Google's Cloud Storage API listed below (you won't need to use all of them, pick the ones best suited to your use-case, the 1st and 5th scopes in the list should be sufficient):
https://www.googleapis.com/auth/cloud-platform
https://www.googleapis.com/auth/cloud-platform.read-only
https://www.googleapis.com/auth/devstorage.full_control
https://www.googleapis.com/auth/devstorage.read_only
https://www.googleapis.com/auth/devstorage.read_write
In future, you can find the required OAuth scopes for any Google API you need at the following link:
https://developers.google.com/identity/protocols/googlescopes

How to turn on email forwarding with Google Script

A brief description of the project: I am looking to toggle the email forwarding option in the settings of one of my gmail accounts through a google script. This will be a function I would like to call every night between certain hours forwarding my mail from main_email#gmail to secondary_email#gmail.
I am having a difficult time finding the easiest way to toggle this through a google script. The simplest solution seems to be described here where they use an HTTP request. However in all honesty I don't completely understand how it all works, much less if it is the simplest way.
https://developers.google.com/gmail/api/v1/reference/users/settings/updateAutoForwarding
The code that I try and run on the gmail account to enable/disable email forwarding is the following:
function updateForwarding() {
var userID = "main_email#gmail.com"
var response = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/" + userID + "/settings/autoForwarding", {
method: 'put',
enabled: true,
emailAddress: "secondary_email#gmail.com",
disposition: "leaveInInbox"
});
Logger.log(response.getContentText());
}
However I get the following error:
Request failed for
https://www.googleapis.com/gmail/v1/users/main_email#gmail.com/settings/autoForwarding
returned code 401. Truncated server response: { "error": { "errors": [
{ "domain": "global", "reason": "required", "message": "Login
Required", "locationType": "header", ... (use muteHttpExceptions
option to examine full response) (line 4, file "Code")
I recognize this is shows I need to provide credentials for making the request, but I don't understand how I would do that. I read on the tutorial (https://developers.google.com/gmail/api/auth/about-auth) I need to authorize my app with gmail and get an API key, so I have gone to the google developers console to create that. However, I have no idea how to authenticate or make the call through a Google script after a few hours of google.
Here are the key and secret I was given:
Is this the easiest solution to toggle gmail forwarding? If so, how do I authenticate my call? If not, what is the easiest solution to being able to toggle my gmail forwarding off/on?
You need to pass oAuth token in header information
function updateForwarding() {
var userID = "main_email#gmail.com";
var header = {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken(),
}
var response = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/" + userID + "/settings/autoForwarding", {
method: 'put',
enabled: true,
headers: header,
emailAddress: "secondary_email#gmail.com",
disposition: "leaveInInbox"
});
Logger.log(response.getContentText());
}
As noted in the authorization section of https://developers.google.com/gmail/api/v1/reference/users/settings/updateAutoForwarding, you need to use OAuth with the given scopes to make that call, not just an API key. You appear to have a client id, but you need to plug this into a library to handle the OAuth process for you. The OAuth process will then give you a Bearer token to add to your request (although most OAuth libraries will handle this for you).
It looks like https://github.com/googlesamples/apps-script-oauth2 is the current recommened way to do this if you're using UrlFetchApp (based on https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app).