Create apps script service accounts to access Drive, Sheet, and Docs - google-apps-script

I am in a similar situation to the OP of this post:
User access request when GAS run as the user
I need to run a web app as an 'active user', allow this user to access Drive, Docs, and Sheets resources, but not having the user direct access to them.
However my knowledge is much less on the subject.
As I understand it, I need to create a service account so that the script running as the 'active user' can access Drive, Sheet, and Docs resources that the active user does not have access to.
I am also looking at other resources as well as Google's documentation, but it's a bit overwhelming.
Can anyone explain the basics for this? Maybe a tutorial (or a link to such) that really inexperienced users can understand? I just need to get started on the right direction.
Thank you in advance!

Impersonation of users using App Script
It should be possible to generate a key and start the process of impersonation and call off the scopes and API.
function getJWT(sub) {
var header = { "alg": "RS256", "typ": "JWT" }
var encodedheader = Utilities.base64EncodeWebSafe(JSON.stringify(header))
var key = "-----BEGIN PRIVATE KEY----- fjsklfjl;sdjfasd -----END PRIVATE KEY-----\n"
var time = Math.floor(new Date().getTime() / 1000)
var claim = {
"iss": "yourserviceaccount#mail-p-any.iam.gserviceaccount.com",
"scope": "https://mail.google.com/",
"aud": "https://oauth2.googleapis.com/token",
"iat": time,
"exp": time + 3600,
"sub": sub[0]
}
var encodedclaim = Utilities.base64EncodeWebSafe(JSON.stringify(claim))
var input = encodedheader + "." + encodedclaim
var signed = Utilities.computeRsaSha256Signature(input, key)
var base64signed = Utilities.base64Encode(signed)
var jwt = encodedheader + "." + encodedclaim + "." + base64signed
return jwt
}
function getAccessToken(user) {
var payload = {
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": getJWT(user)
}
var params = {
"method": "POST",
"contentType": "application/x-www-form-urlencoded",
"payload": payload,
"muteHttpExceptions": true
}
var response = UrlFetchApp.fetch("https://oauth2.googleapis.com/token", params)
var output = JSON.parse(response.getContentText())
console.log(output.access_token)
return output.access_token
}
You can also review the library and step by step process on how you can implement it in another way from here:
https://github.com/googleworkspace/apps-script-oauth2
My code sample was based on the sample script from:
https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority
You can also review the other sample code from the references below.
This way you are able to impersonate the user and run or make calls on behalf of the user from your organization without having access to it. This might be where you can start your idea on how to start.
References
https://cloud.google.com/iam/docs/impersonating-service-accounts
https://github.com/googleworkspace/apps-script-oauth2/blob/main/samples/GoogleServiceAccount.gs

I got this to work, for the benefit of those who are the same level in this subject as I am, and in the similar situation. Anyone please expound or correct me if I'm wrong, thanks.
You cannot use the methods to access Drive, Docs, and Sheets in the
same code that runs as the 'active user'.
You have to access these Google services using the equivalent HTTP
API calls of the methods.
The HTTP API calls need a user that would interact with the resources
(because it's being called from publicly from the internet and not
from the script).
You create a service account for this. This acts as the user for the
calls.
I started with Ricardo Jose Velasquez Cruz's response, and found other resources, as I was calling the API from Apps Script.
https://medium.com/geekculture/how-to-use-service-accounts-and-oauth2-in-google-apps-script-99c4bc91dc31
Note that Apps Script requires an OAUTH2 library to connect, not sure why this was not built-in to GAS itself:
https://github.com/googleworkspace/apps-script-oauth2
How to create a service account and use it to access Google Drive (you use the same code to access Docs and Sheet as well, you just need to use the corresponding URL and parameters for the services):
https://www.labnol.org/code/20375-service-accounts-google-apps-script
it's basically the same code as another post I found here:
Google Service Accounts / API - I keep getting the Error: Access not granted or expired. (line 454, file "Service")
Hope this helps :)

Related

How to Properly Configure GAS Web App (as another user) to Execute GAS API Executable (as me) using OAuth2?

Problem
After days of reading and attempting trial-and-error, I am trying to make a call from a GAS Web App (executed as any Google User) to a GAS API Executable (executed as Me), but consistently receive an error message after Reviewing/Granting permissions:
*"Error: Access not granted or expired."*
That is not a response from the server, but simply a notification from the OAuth2 library: "This method will throw an error if the user's access was not granted or has expired."
So it seems there may be some otherwise obvious step that is missing from instructions and Q&As. Somehow, after doing all of the setup, the web app cannot access the API Executable.
I also spent a few hours writing this question and formulating a minimal test/example. Here are the files in Google Drive, for viewing directly.
Desired Outcome
The desired outcome is to be able to have other users use the Web App as themselves and, from that app, execute the API Executable as Me.
Question
What is wrong with my configuration and/or code, and how can I receive data back from the API Executable?
What I've tried
I've combined various tutorials and Q&As and attempted to make a minimal example. The three most closely related are:
Google Groups - "Webapp execute as user BUT save data to spreadsheet"
...Service accounts, while applicable, are not the best fit for this use-case. They are better suited to situations where the service account acts as a proxy for multiple users...
...Basically, you'll need to generate OAuth2 credentials specific to
your account and use the OAuth2 library to generate access tokens.
That access token can then be used to make direct calls against the
Spreadsheet REST API OR alternatively, the Apps Script API (formerly
the Execution API) to invoke a function in the script under your own
authority...
SO - "Can I have part of Google Apps Script code execute as me while the rest executes as the accessing user?"
SO - "Get user info when someone runs GAS web app as me"
The first link seems directly applicable to my scenario. However, the instructions are sparse, though I have done my best to follow them. The second is also applicable, but sparse. The third is related, but is actually the inverse of what I want to do, and so of limited help.
Summary of Steps in GCP
Here is what I did within console.cloud.google.com:
Created a new project named "apiExecTest".
Within "APIs & Services", enabled two APIs:
Apps Script API (unsure if necessary)
Google Sheets API (unsure if necessary)
Within "APIs & Services", configured the Oauth Consent Screen
Internal
Set App name, User support email, and Developer contact email. Did nothing else. Did not set "App domain" nor "Authorized domains".
Added all 61 scopes for Apps Script and Sheets (unsure if necessary)
Within "APIs & Services", created credentials
OAuth client ID
Web Application
Added Client name.
Added Authorized Redirect URI:
https://script.google.com/macros/d/1zj4ovqMWoCUgBxJJ8u518TOEKlckeIazVBL4ASdYFiVmjoZz9BLXbJ7y/usercallback
Obtained Client ID & Client Secret to insert into webApp code.
Summary of Steps in GAS
Here is what I did in Google Drive / Apps Script. The files can be viewed here:
Created a new folder in Google Drive containing three things:
GAS file: "webApp"
Deployed as Web App
Execute as: User accessing the web app
Who has access: Anyone with Google account
GAS file: "apiExec"
Deployed as API Executable
Who has access: Anyone with Google account
Google Sheet: sSheet
Not shared with anyone, owned by Me.
Added a basic function to apiExec that obtains the first cell of the first sheet in sSheet, and confirmed it works by executing it within the GAS editor and observing the console output.
Added the OAuth2 library to webApp as oauth2.gs, copy/pasted from GitHub. Setup and configured setClientId(), setClientSecret(), API URL and other settings per the readme and examples cited above. For setScope(), I used:.setScope('https://www.googleapis.com/auth/script.external_request https://www.googleapis.com/auth/spreadsheets')
Added a basic functionality to webApp that makes a call to apiExec to obtain data from sSheet.
Added the following to the webApp appsscript.json (unsure if correct, have tried variations):"oauthScopes": ["https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/spreadsheets"]
I changed the GCP Project Number for both apiExec and webApp to that of the GCP project created in the steps above.
I then executed the doGet() function of webApp within the GAS editor. It does ask for authorization, which I granted. After authorization, as the execution continues, the error mentioned above is thrown. I also ran the function via webApp's URL, which of course also results in the error.
After attempting this multiple times, and spending days reading and with trial-and-error, I've made no progress. Any help is greatly appreciated.
To be thorough, here are the contents of the GAS files:
apiExec
appsscript.json
{
"timeZone": "America/New_York",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"executionApi": {
"access": "ANYONE"
}
}
Code.gs
function doPost() {
var spreadsheet = SpreadsheetApp.openById("1aIMv1iH6rxDwXLx-i0uYi3D783dCtlMZo6pXJGztKTY");
var sheet = spreadsheet.getSheetByName("test sheet");
var data = sheet.getRange("A1").getValues()
console.log(data)
return data;
}
webApp
appsscript.json
{
"timeZone": "America/New_York",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/spreadsheets"
],
"webapp": {
"executeAs": "USER_ACCESSING",
"access": "ANYONE"
}
}
Code.gs
function doGet(e) {
var myParam = "someParam";
console.log(myParam);
var apiExecResponse = makeRequest('doPost', [myParam]);
console.log(apiExecResponse);
var appsScriptService = getService();
if (!appsScriptService.hasAccess()) {
// This block should only run once, when I authenticate as myself to create the refresh token.
var authorizationUrl = appsScriptService.getAuthorizationUrl();
var htmlOutput = HtmlService.createHtmlOutput('Authorize.');
htmlOutput.setTitle('GAS Authentication');
return htmlOutput;
}
else {
console.log("It worked: " + myParam + " " + apiExecResponse);
htmlOutput.setTitle("The Results");
return HtmlService.createHtmlOutput("<p>It worked: " + myParam + " " + apiExecResponse + "</p>");
}
}
function getService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService('apiExecService')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId('390208108732-s7geeikfvnqd52a0fhf6e015ucam0vqk.apps.googleusercontent.com')
.setClientSecret('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')
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scopes to request (space-separated for Google services).
.setScope('https://www.googleapis.com/auth/script.external_request https://www.googleapis.com/auth/spreadsheets')
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
//.setParam('login_hint', Session.getEffectiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
// Consent prompt is required to ensure a refresh token is always
// returned when requesting offline access.
.setParam('prompt', 'consent');
}
function authCallback(request) {
var apiExecService = getService();
var isAuthorized = apiExecService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
}
else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
function makeRequest(functionName, paramsArray) {
console.log("Running " + functionName + " via 'makeRequest'.");
var apiExecUrl = 'https://script.googleapis.com/v1/scripts/AKfycbzHV5_Jl2gJVv0wDVp93wE0BYfxNrOXXKjIAmOoRu3D8W6CeqSQM9JKe8pOYUK4fqM_:run';
var payload = JSON.stringify({
"function": functionName,
"parameters": paramsArray,
"devMode": false
});
var params = {
method:"POST",
headers: {
Authorization: 'Bearer ' + getService().getAccessToken()
},
payload: payload,
contentType: "application/json",
muteHttpExceptions: true
};
var result = UrlFetchApp.fetch(apiExecUrl, params);
return result;
}
OAuth2.gs
See: https://github.com/googleworkspace/apps-script-oauth2/blob/master/dist/OAuth2.gs
If I understand correctly, your current flow is as follows:
Use the OAuth2 library to do a one-time capture of the auth token for your own Google account.
Use that stored token to authenticate the request to the API Executable (when running the web app as another user).
Apps Script has a built-in method for accomplishing step 1: ScriptApp.getOAuthToken(), so I'm not sure you even need the OAuth2 library for this. (You would need that library for authorizing services other than Google.)
Possibly you can avoid using the OAuth2 library completely by doing the following:
Add this function to your web app project and run it once from the editor, i.e. under your own authorization:
function storeOauthToken() {
PropertiesService.getScriptProperties().setProperty(
'myToken',
ScriptApp.getOAuthToken()
)
}
Change the headers in the makeRequest function of your webApp project from this
headers: {
Authorization: 'Bearer ' + getService().getAccessToken()
},
to this:
headers: {
Authorization: 'Bearer ' + PropertiesService.getScriptProperties().getProperty('myToken')
},
I created a copy of your projects and was able to confirm that this technique works.
Token refresh
I assume that the token may expire like any other OAuth2 token, so you may need to set a timed trigger (again, under your own authorization) to run storeOAuthToken() periodically.

"Authorisation is required to perform that action" message, even after clicking "Allow"

I've recently run into an issue authorising a new Google App Script project, specifically one using the Cloud SQL admin API.
The same code exists in previously authorised GAS projects and works fine, but if I take a copy of the GAS project and try to run a function for the first time I'm unable to complete the authorisation process. The screens I'm going through are listed below:
Authorisation Required. - clicked "Review Permissions"
Choose an account to authorise the Google project. - clicked my account
This app isn't verified! - clicked "Go to project
(unsafe)"
Google project wants access to this list of scopes.- clicked "Allow"
Authorisation is required to perform that action.
The warning screen (3) is a recent addition to the process. I don't rememeber encountering it when I've created and run new projects earlier this year. I'm wondering if Google has made any changes to their security implementation of OAuth2.0 recently.
Also, this issue only seems to affect REST calls to the Cloud SQL admin API. In the same project mentioned above I am able to run functions which write data to BigQuery tables in the same Google project which is also hosting the Cloud SQL instances. Clearly some scopes and code can be made to work.
The "https://www.googleapis.com/auth/sqlservice.admin" scope is included in the list of those I requested and approve. I even tried manually editing the URL to add more scopes being requested and it still doesn't get me passed the "Authorisation is required to perform that action" screen.
Does anyone have any idea's?
EDIT:
The code in question which is triggering the authentication.
// Function to get the ip address of a given CloudSQL instance
function _getInstanceIpAddress_(projectId, sqlInstance) {
var token = _getAuthenticationToken_();
// Create the header authorisation
var headers = {
"Authorization": "Bearer " + token
};
// Create the Cloud SQL instances get parameters
var parameters = {
"method": "get",
"headers": headers,
"instance": sqlInstance,
"project": projectId,
"muteHttpExceptions": true
};
// Create the url of the sql instances get API
var api = "https://www.googleapis.com/sql/v1beta4/projects/" + projectId + "/instances/" + sqlInstance + "?fields=ipAddresses";
try {
// Use the url fetch service to issue the https request and capture the response
var response = UrlFetchApp.fetch(api, parameters);
// Extract the ip address of the instance from the response
var content = JSON.parse(response.getContentText());
return content.ipAddresses[0].ipAddress;
} catch(err) {
_log_('ERROR', 'Getting ' + sqlInstance + ' instance ip address failed: ' + err);
return null;
}
}
function _getAuthenticationToken_() {
// Check we have access to the service
var service = getService();
if (!service.hasAccess()) {
var authorizationUrl = service.getAuthorizationUrl();
_log_('INFO', 'Open the following URL and re-run the script: ' + authorizationUrl);
return;
}
Logger.log('Passed Authentication');
//Get the Access Token
return service.getAccessToken();
function getService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService('companyName-dev-service')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Set the scopes to request (space-separated for Google services).
// this is admin access for the sqlservice and access to the cloud-platform:
.setScope(
'https://www.googleapis.com/auth/sqlservice.admin ' +
'https://www.googleapis.com/auth/cloud-platform')
//Removed because this Should be covered by cloud-platform
//'https://www.googleapis.com/auth/devstorage.read_write '
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
.setParam('login_hint', Session.getActiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
// Forces the approval prompt every time. This is useful for testing,
// but not desirable in a production application.
.setParam('approval_prompt', 'force');
}
function authCallback(request) {
var cloudSQLService = getService();
var isAuthorized = cloudSQLService.handleCallback(request);
if (isAuthorized) {
_log_('INFO', 'Access Approved');
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
_log_('INFO', 'Access Denied');
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
}
If you think back about a year ago you may remember the Massive Phishing Attack Targets Gmail Users What you are seeing is googles response to that.
Web credentials that use specific scopes require that Google approve them before anyone but the developer who created the credentials in question can use it. It normally takes about a week to get approved or so Google says.
You didnt seen it before because this only recently hit App script OAuth client verification
Starting July 18, 2017, Google OAuth clients that request certain sensitive OAuth scopes will be subject to review by Google.
We had a similar issue with the Google Compute Engine API. Setting the scopes explicitly in the appsscript.json file as per this article solved it for us:
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets.readonly",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/script.container.ui",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/cloud-platform"
],

Calling a Google Apps Script web app with access token

I need to execute a GAS service on behalf of a user that is logged to my system. So I have her/his access token. I would like somehow to transfer the token to the web app and without having to authorize again the user to use it for some activities. Can this be accomplished? Thank you.
EDIT: I think I didn't explain right what I try to accomplish. Here is the work flow I try to achieve:
We authorize a user visiting our website using OAuth2 and Google;
We get hold of her/his access token that Google returns;
There is a Google Apps Script web app that is executed as the user running the web app;
We want to call this app (3) by providing the access token (2) so Google not to ask again for authorization;
Actually, we want to call this app (3) not by redirecting the user to it but by calling it as a web service.
Thanks
Martin's answer worked for me in the end, but when I was making a prototype there was a major hurdle.
I needed to add the following scope manually, as the "automatic scope detection system" of google apps script did not ask for it: "https://www.googleapis.com/auth/drive.readonly". This resulted in UrlFetchApp.fetch always giving 401 with additional information I did not understand. Logging this additional information would show html, including the following string
Sorry, unable to open the file at this time.</p><p> Please check the address and try again.
I still don't really understand why "https://www.googleapis.com/auth/drive.readonly" would be necessary. It may have to do with the fact that we can use the /dev url, but who may use the /dev url is managed is checked using the drive permissions of the script file.
That said, the following setup then works for me (it also works with doGet etc, but I chose doPost). I chose to list the minimally needed scopes explicitly in the manifest file, but you can also make sure the calling script will ask for permissions to access drive in different ways. We have two google apps script projects, Caller and WebApp.
In the manifest file of Caller, i.e. appsscript.json
{
...
"oauthScopes":
[
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/script.external_request"]
}
In Code.gs of Caller
function controlCallSimpleService(){
var webAppUrl ='https://script.google.com/a/DOMAIN/macros/s/id123123123/exec';
// var webAppUrl =
// 'https://script.google.com/a/DOMAIN/macros/s/id1212121212/dev'
var token = ScriptApp.getOAuthToken();
var options = {
'method' : 'post'
, 'headers': {'Authorization': 'Bearer '+ token}
, muteHttpExceptions: true
};
var response = UrlFetchApp.fetch(webAppUrl, options);
Logger.log(response.getContentText());
}
In Code.gs of WebApp (the web app being called)
function doPost(event){
return ContentService.createTextOutput("Hello World");
}
The hard answer is NO you can't use the built-in services of Apps Script with a service token. But if you already have the token for a user generated by a service account, access to the users data is pretty similar to any other language. All calls would be to the REST interface of the service your token is scoped for.
Take this small script for example. It will build a list of all the user's folders and return them as JSON:
function doGet(e){
var token = e.parameter.token;
var folderArray = [];
var pageToken = "";
var query = encodeURIComponent("mimeType = 'application/vnd.google-apps.folder'");
var params = {method:"GET",
contentType:'application/json',
headers:{Authorization:"Bearer "+token},
muteHttpExceptions:true
};
var url = "https://www.googleapis.com/drive/v2/files?q="+query;
do{
var results = UrlFetchApp.fetch(url,params);
if(results.getResponseCode() != 200){
Logger.log(results);
break;
}
var folders = JSON.parse(results.getContentText());
url = "https://www.googleapis.com/drive/v2/files?q="+query;
for(var i in folders.items){
folderArray.push({"name":folders.items[i].title, "id":folders.items[i].id})
}
pageToken = folders.nextPageToken;
url += "&pageToken="+encodeURIComponent(pageToken);
}while(pageToken != undefined)
var folderObj = {};
folderObj["folders"] = folderArray;
return ContentService.createTextOutput(JSON.stringify(folderObj)).setMimeType(ContentService.MimeType.JSON);
}
You do miss out on a lot of the convenience that makes Apps Script so powerful, mainly the built in services, but all functionality is available through the Google REST APIs.
I found a way! Just include the following header in the request:
Authorization: Bearer <user's_access_token>

How to authorize and post/update Trello card from a Google docs script

I have a Google Docs Spreadsheet that I'd like to use to update referenced cards in Trello. I've had some success with oauth and pulling data via their HTTP API, but am stuck with the following:
1) it seems Trello's code.js requires a window object, which the Google Doc script doesn't provide. So, I am stuck using their HTTP API.
2) authenticating via OAuth works, but only gives me read access. I cannot update cards with the token I am able to get.
function test() {
var oauthConfig = UrlFetchApp.addOAuthService("trello");
oauthConfig.setAccessTokenUrl("https://trello.com/1/OAuthGetAccessToken");
oauthConfig.setRequestTokenUrl("https://trello.com/1/OAuthGetRequestToken");
oauthConfig.setAuthorizationUrl("https://trello.com/1/authorize?key=" + consumerKey + "&name=trello&expiration=never&response_type=token&scope=read,write");
//oauthConfig.setAuthorizationUrl("https://trello.com/1/OAuthAuthorizeToken"); <-- this only gives read access. Cannot POST
oauthConfig.setConsumerKey(consumerKey);
oauthConfig.setConsumerSecret(consumerSecret);
var url = 'https://trello.com/1/cards/yOqEgvzb/actions/comments&text=Testing...';
var postOptions = {"method" : "post",
"oAuthServiceName": "trello",
"oAuthUseToken": "always"};
var response = UrlFetchApp.fetch(url, postOptions); // "Request failed for returned code 404. Truncated server response: Cannot POST"
Logger.log(response.getContentText());
}
I've found a number of related questions but no direct answers:
How to get a permanent user token for writes using the Trello API?
Trello API: vote on a card
Trello API: How to POST a Card from Google Apps Script (GAS)
Google apps script oauth connect doesn't work with trello
Many thanks ahead of time for any advice.
In order to get write access, you need to change the authorization url.
This example works for me
var oauthConfig = UrlFetchApp.addOAuthService("trello");
oauthConfig.setAccessTokenUrl("https://trello.com/1/OAuthGetAccessToken");
oauthConfig.setRequestTokenUrl("https://trello.com/1/OAuthGetRequestToken");
oauthConfig.setAuthorizationUrl("https://trello.com/1/OAuthAuthorizeToken?scope=read,write");
on 1) yes you cant use the library from server gas, its meant to be run from a browser.
on 2), Ive done it from GAS with write access without problems. You need to use the format:
https://api.trello.com/1/.../xxxx?key=yyyyyy&token=zzzzzzz&...
and when you get the token, you need to request permanent access (no expiration) and write access, as in:
https://trello.com/1/authorize?key="+key+"&name=xxxxxxx&expiration=never&response_type=token&scope=read,write"
As in:
function postNewCardCommentWorker(cardId, comment, key, token) {
var commentEncoded=encodeURIComponent(comment);
var url = "https://api.trello.com/1/cards/"+cardId+"/actions/comments?text="+commentEncoded+"&key="+key+"&token="+token;
var options =
{
"method" : "POST"
};
UrlFetchApp.fetch(url, options);
}

Is it possible to impersonate domain's users with Google Drive API using Google Apps Script?

I am a head of studies and school administrator of our Google Apps for Education.
I used Google Apps Script for a lot of applications (control of absences, sending emails, automatic reporting, ScriptDb databases and more) using gas services. It's fantastic.
Basically I need to create a folder structure (years, courses, teachers, ...) within the Google Drive of students.
With Google Apps Script services I can do it easily but then the folders belong to the creator (administrator) and I think then users spend the administrator storage quota. This does not interest me.
(Yes, I can make an application to be executed by the users and create the structure in its Google Drive, but I'd rather do it in an automated manner and without intervention)
To create this documents (and folders) in Google Drive users (teachers, students, ...) have adapted the code provided by Waqar Ahmad in this response [ Add an writer to a spreadsheet ... ]
That allows me to take possession of documents of other users to make updates using the Google Document List API (Google Apps administrative access to impersonate a user of the domain) and also have adapted to create folders and files on other Google Drive users. It works perfectly. I mention here:
How to add a Google Drive folder ...
But now, the version 3 of the Google Documents List AP, has been officially deprecated and encourage us to work with the Google API Drive.
I tried to do the same with this new Google API. Has anyone managed to do this? Is it possible? I have no idea where to start!
Thank you.
Sergi
Updated:
This is the code i'm working but I get an "invalid request" error:
(...)
var user = e.parameter.TB_email // I get user from a TextBox
//https://developers.google.com/accounts/docs/OAuth2ServiceAccount
//{Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature}
//{Base64url encoded header}
var header = '{"alg":"RS256","typ":"JWT"}'
var header_b64e = Utilities.base64Encode(header)
//{Base64url encoded claim set}
var t_ara = Math.round((new Date().getTime())/1000) // now
var t_fins = t_ara + 3600 // t + 3600 sec
var claim_set = '{"iss":"1111111111-xxxxxxxxxxxxxxxxxxxxxx#developer.gserviceaccount.com",'+
'"prn":"' + user + '",' +
'"scope":"https://www.googleapis.com/auth/prediction",'+
'"aud":"https://accounts.google.com/o/oauth2/token",'+
'"exp":'+t_fins+','+
'"iat":'+t_ara+'}'
// where '1111111111-xxxxxxxxxxx... is my CLIENT-ID (API Access -> Service Account)
var claim_set_b64e = Utilities.base64Encode(claim_set)
claim_set_b64e = claim_set_b64e.replace(/=/g,'')
var to_sign = header_b64e + '.' + claim_set_b64e
// [signature bytes] ??? // password 'isnotasecret???'
var key_secret = DocsList.getFileById('0Biiiiiiiiii-XXXXXXXXXXX').getBlob().getBytes()
// where '0Biiiiiiiiii-XXXXXXXXXXX'... is my p12 file (key_secret) uploaded to GDRive
// I don't know if this is correct !!!
var sign = Utilities.base64Encode(Utilities.computeHmacSha256Signature(to_sign, key_secret))
var JWT_signed = to_sign + '.' + sign
JWT_signed = JWT_signed.replace(/=/g,'')
// Token Request /////////////////////////////////////////////////////////////
var url = 'https://accounts.google.com/o/oauth2/token'
//var url = 'https%3A%2F%2Faccounts.google.com%2Fo%2Foauth2%2Ftoken' ???
//var url = 'https:' + '%2F%2Faccounts.google.com%2Fo%2Foauth2%2Ftoken' ???
var parameters = {
"method" : "POST",
"payload" : '"' + 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=' + JWT_signed + '"',
"contentType" : "application/x-www-form-urlencoded"
}
var content = UrlFetchApp.fetch(url,parameters) //.getContentText()
// Token Request end ////////////////////////////////////////////////////////
And I get an "Invalid Request" and not a JSON with the token
The 2 first parts ( header & claim set ) are OK. The result are equal to the result of Google OAuth page.
I don't know if the signature part are correct or if the error is in the token request.
The issue with your example above is that it it's computing the signature with hmacsha256. You need to use rsasha256. There are two service account libraries for apps script right now. One that I put together is:
https://gist.github.com/Spencer-Easton/f8dad65932bff4c9efc1
The issue with both libraries is they are derived from jsrsa which runs very slow on the server side.