Upload to Google Cloud storage from App Script - google-apps-script

I've been bashing my head at this for over a day, but I can't figure out how to upload data to Google Cloud Storage via an app script attached to a google sheet. I've been running into issues with authorisation. I've copied the getService method from here (pasted below) but the service keeps failing to receive authorisation. service.hasAccess() always returns false.
function uploadFileToGCS(dataJSON) {
var service = getService();
if (!service.hasAccess()) {
Browser.msgBox("Failed to grant service access")
return;
}
var url = 'https://www.googleapis.com/upload/storage/v1/b/BUCKET/o?uploadType=media&name=FILE'
.replace("BUCKET", params.BUCKET_NAME)
.replace("FILE", encodeURIComponent(params.FILE_PATH));
var response = UrlFetchApp.fetch(url, {
method: "POST",
payload: dataJSON,
contentType: "application/json",
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
}
});
var result = JSON.parse(response.getContentText());
Logger.log(JSON.stringify(result, null, 2));
}
function getService() {
return OAuth2.createService('ctrlq')
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setClientId(params.CLIENT_ID)
.setClientSecret(params.CLIENT_SECRET)
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setScope('https://www.googleapis.com/auth/devstorage.read_write')
.setParam('access_type', 'offline')
.setParam('approval_prompt', 'force')
.setParam('login_hint', Session.getActiveUser().getEmail());
}
function authCallback(request) {
var service = getService();
var authorized = service.handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('Connected to Google Cloud Storage');
} else {
return HtmlService.createHtmlOutput('Access Denied');
}
}
I've created OAUTH credentials for a web-app on the Google Cloud Console. I've also enabled the Cloud Storage API and Google Cloud Storage JSON API. I'm unsure however on the redirect URL. (Ideally, I'd like to use a service account because I just want to take the values from the spreadsheet and upload them as a JSON file.)
Anyway, appreciate the help!

I think google app script project and google cloud project should be linked. You need to create a google cloud project to use this API.

I encountered a similar problem when I wanted to duplicate an object in Cloud Storage. Not sure if this is the solution for you, but I'm putting it here in case someone need it.
Just use the XML-formmatted REST API with OAuth2 token, and the code looks like this:
var objectSource = "SOURCE_BUCKET_NAME/SOURCE_OBJECT_NAME";
var url = "https://storage.googleapis.com/DESTINATION_BUCKET_NAME/NAME_OF_COPY";
var resp = UrlFetchApp.fetch(url, {
method: "PUT",
headers: {
Authorization: 'Bearer '+ OAUTH2_TOKEN,
"x-goog-copy-source": objectSource,
},
'muteHttpExceptions': true,
});
Check out the Cloud Storage's document for differences between copy and upload.

Related

how to call Google Local Services API from Google Sheets App script

I am attempting to call Google Local Services API inside Google AppScript, I have followed the documentationDOCUMENTATION and a Guide but couldn't generate a token with .getAccessToken() how can i get it work?
function makeRequest() {
var adsService = getAdsService();
console.log(adsService.getAccessToken()) //I am Not getting Token here
var response = UrlFetchApp.fetch('https://localservices.googleapis.com/v1/accountReports:search?query=manager_customer_id:1GRTlAnR5J0YQmBJs2UixjAhpN34xHfwwkgv9S0XGSRZP-V_LMv5lU_7S', {
headers: {
Authorization: 'Bearer ' + adsService.getAccessToken()
}
});}

Using the BigQuery API on APP Scritps with a Service Account

I'm trying execute a job on BigQuery on a VPC project using App Scripts.
My goal is store the result in an array to create a dynamic prompt for DataStudio using community connectors
Using the following code:
function runQuery() {
var sql = "SELECT Distinct ss_cd FROM `vf-pt-ngbi-dev-gen-03.AEAD_DataSet_test.d_customer` WHERE end_dttm IS NOT NULL";
var queryResults;
var projectNumber = 'projectNumber'
// Inserts a Query Job
try {
var queryRequest = BigQuery.newQueryRequest();
queryRequest.setQuery(sql).setTimeoutMs(100000);
queryResults = BigQuery.Jobs.query(queryRequest, projectNumber);
}
catch (err) {
Logger.log(err);
return;
}
Since this is a VPC project I need to use a Service Account to perform this request?
However, I would like to know how to add this authorization?
Or exists another approach to execute a BigQuery job on a VPC project and store the results in an array?
You can get the service account token in apps script (see reference) then use that token for the REST API via UrlFetchApp.
Sample:
function runQuery() {
// ...
var service = getService();
if (service.hasAccess()) {
sendQuery(service);
}
// ...
}
function sendQuery(service){
var projectId = 'projectID';
var url = 'https://bigquery.googleapis.com/bigquery/v2/projects/' + projectId + '/queries';
// see request body for reference
// https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/query#QueryRequest
var body = {
// ...
}
var options = {
"method": "post",
"headers": {
"Authorization": "Bearer " + service.getAccessToken()
},
"contentType": "application/json",
"payload": JSON.stringify(body)
};
var response = UrlFetchApp.fetch(url, options);
}
// direclty copied from https://github.com/googleworkspace/apps-script-oauth2/blob/master/samples/GoogleServiceAccount.gs
function getService() {
return OAuth2.createService('BigQuery:' + USER_EMAIL)
// Set the endpoint URL.
.setTokenUrl('https://oauth2.googleapis.com/token')
// Set the private key and issuer.
.setPrivateKey(PRIVATE_KEY)
.setIssuer(CLIENT_EMAIL)
// Set the name of the user to impersonate. This will only work for
// Google Apps for Work/EDU accounts whose admin has setup domain-wide
// delegation:
// https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority
.setSubject(USER_EMAIL)
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scope. This must match one of the scopes configured during the
// setup of domain-wide delegation.
.setScope('https://www.googleapis.com/auth/bigquery');
}
References:
BigQuery REST API
Creation of Service Account
Get Service Account Token
Note:
See a question containing a code what yours should look like.

Google Sheets AddOn - AppScript & BigQuery Integration via Service Account

I have a Google Sheets workspace addon and recently did some work to integrate BigQuery. Essentially BigQuery hold a record of books each of which has an author, title etc and my Addon allows people to pull the books that they have read into their sheet. The first column in the sheet allows people to choose from all the authors in the DB, based on that selection the second column is populated with data from BigQuery with all books by that author etc etc. There is no need for my AddOn to access a user's BigQuery, they only access 'my' BgQuery.
This all works fine, but when I submitted my addon for approval I was told
Unfortunately, we cannot approve your request for the use of the following scopes
https://www.googleapis.com/auth/bigquery
We recommend using service accounts for this type of information exchange.
This seems fair and reading up on Service Accounts it seems a much better fit for my use case. I've gone through the process of creating the service accounts and downloaded my security details json file, however I just can't figure out how to actually query BigQuery from AppScript.
In my non-service account method I have the BigQuery Library installed in AppScript and basically run
var queryResults = BigQuery.Jobs.query(request, projectId);
I've been trying to work from an example at https://developers.google.com/datastudio/solution/blocks/using-service-accounts
function getOauthService() {
var serviceAccountKey = getServiceAccountCreds('SERVICE_ACCOUNT_KEY');// from private_key not private_key_id of JSON file
var serviceAccountEmail = getServiceAccountCreds('SERVICE_ACCOUNT_EMAIL');
return OAuth2.createService('RowLevelSecurity')
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(serviceAccountKey)
.setIssuer(serviceAccountEmail)
.setPropertyStore(scriptProperties)
.setCache(CacheService.getScriptCache())
.setScope(['https://www.googleapis.com/auth/bigquery.readonly']);
}
function getData(request) {
var accessToken = getOauthService().getAccessToken();
var billingProjectId = getServiceAccountCreds('BILLING_PROJECT_ID');
// var email = Session.getEffectiveUser().getEmail();
// return cc
// .newBigQueryConfig()
// .setAccessToken(accessToken)
// .setBillingProjectId(billingProjectId)
// .setUseStandardSql(true)
// .setQuery(BASE_SQL)
// .addQueryParameter('email', bqTypes.STRING, email)
// .build();
}
I've commented out the code in the above which relates to
var cc = DataStudioApp.createCommunityConnector();
in the above tutorial since I'm not using DataStudio but I'm really not sure what to replace it with so I can query BigQuery with AppScript via a Service Account. Can anyone offer any advice?
Based on the advice from #TheAddonDepot in the comments above my revised code now looks like:
function getBigQueryService() {
return (
OAuth2.createService('BigQuery')
// Set the endpoint URL.
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the private key and issuer.
.setPrivateKey(JSON_CREDS.private_key) // from the json file downloaded when you create service account
.setIssuer(JSON_CREDS.client_email). // from the json file downloaded when you create service account
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Caching
.setCache(CacheService.getUserCache())
// Locking
.setLock(LockService.getUserLock())
// Set the scopes.
.setScope(['https://www.googleapis.com/auth/bigquery.readonly'])
// .setScope('https://www.googleapis.com/auth/bigquery')
)
}
function queryData(){
const bigQueryService = getBigQueryService()
if (!bigQueryService.hasAccess()) {
Logger.log("BQ ERROR IS "+ bigQueryService.getLastError())
}
//const projectId = bigqueryCredentials.project_id
var projectId = "<yourprojectid>"
let url = 'https://bigquery.googleapis.com/bigquery/v2/projects/<yourprojectid>/queries'; //projectID is taken from the security json file for the service account, although it doesn't seem to matter if you use the project code
const headers = {
Authorization: `Bearer ${bigQueryService.getAccessToken()}`,
'Content-Type': 'application/json',
}
var data = {query:"<your query>",useLegacySql:false};
const options = {
method: 'post',
headers,
//contentType: 'application/json',
payload: JSON.stringify(data),
muteHttpExceptions: true // on for debugging
}
try {
const response = UrlFetchApp.fetch(url, options)
const result = JSON.parse(response.getContentText())
Logger.log("here is result "+ JSON.stringify(result))
} catch (err) {
console.error(err)
}
}

GDrive Disable Copying and downloading

How I can manage the sharing-feature: "Disable Copying and downloading" without the advanced Drive Service?
Currently I solve it about:
function mySolveAboutAdvancedService(id) {
var file = Drive.Files.get(id);
file.labels.restricted = true;
Drive.Files.update(file, id);
}
Why I can change all settings but not this one without the advanced Drive Service?
Thanks
You want to achieve the following script without using Advanced Google services.
var file = Drive.Files.get(id);
file.labels.restricted = true;
Drive.Files.update(file, id);
You want to know the reason that "Disable Copying and downloading" cannot be achieved without using above script.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Issue and workaround:
Drive API of Advanced Google services uses Drive API v2. In this case, labels.restricted is for Drive API v2, and also, the official document says as follows.
labels.restricted: Warning: This item is deprecated. Deprecated - use copyRequiresWriterPermission instead.
By this, when {labels: {restricted: true}} is used for Drive API v3, it cannot be used while no error occurs. But, when Drive API v2 is used with UrlFetchApp, {labels: {restricted: true}} can be still used. By this, your script using Drive API of Advanced Google service works.
In order to achieve your script without using Advanced Google services, please directly request to the endpoint of Drive API v3 (in this case, v3 is used.) with the request body of {copyRequiresWriterPermission: true} using UrlFetchApp. The sample script is as follows.
Sample script:
function mySolveAboutAdvancedService() {
var id = "###"; // Please set the file ID.
var url = "https://www.googleapis.com/drive/v3/files/" + id;
var params = {
method: "patch",
contentType: "application/json",
payload: JSON.stringify({copyRequiresWriterPermission: true}),
headers: {Authorization: "Bearer " + ScriptApp.getOAuthToken()}
};
var res = UrlFetchApp.fetch(url, params);
Logger.log(res.getContentText())
}
Note:
If you want to use Drive API v2 with UrlFetchApp, how about the following script? At Drive API v2, both {labels: {restricted: true}} and {copyRequiresWriterPermission: true} can be used.
function mySolveAboutAdvancedService() {
var id = "###"; // Please set the file ID.
var url = "https://www.googleapis.com/drive/v2/files/" + id;
var params = {
method: "put",
contentType: "application/json",
payload: JSON.stringify({copyRequiresWriterPermission: true}), // or payload: JSON.stringify({labels: {restricted: true}})
headers: {Authorization: "Bearer " + ScriptApp.getOAuthToken()}
};
var res = UrlFetchApp.fetch(url, params);
Logger.log(res.getContentText())
}
References:
Files of Drive API v2
Files of Drive API v3
Files: update of Drive API v3
If I misunderstood your question and this was not the direction you want, I apologize.

How to call a Web App only available to users inside domain from another google script?

I have a Google script deployed as a web app, unfortunately, due to the company policy, I have to deploy it and make it available to anyone inside the domain, not anyone even anonymous.
Here's the web app code
function doPost(e) {
var functionName = JSON.parse(e.postData.contents).functionName;
return ContentService.createTextOutput(JSON.stringify({result:functionName}))
.setMimeType(ContentService.MimeType.JSON);
}
Here's the Google Script code that I'm trying to call the web app through it, I tried to pass an access token in the request header.
function callWebApp(functionName) {
var response = UrlFetchApp.fetch(url, {
headers: {
"Authorization": "Bearer " + ScriptApp.getOAuthToken(),
},
contentType: 'application/json',
muteHttpExceptions:true,
method : 'post',
payload:JSON.stringify({functionName: functionName}),
});
Logger.log(response)
}
You can use something like the Session service and filter out users who are not in your company domain.
For example:
var user = Session.getActiveUser().getEmail();
if(user.split('#')[1] === 'your.company.domain') {
//do something
}