Based on the Google's documentation, I've implemented the following code to append a row to the Google Sheet (and I'm owner of this sheet).
function addSalaryLog(updateArray) {
const resource = {
"majorDimension": "ROWS",
"values": [updateArray]
};
const spreadsheetId = 'here_goes_the_spreadsheet_id';
const range = 'A:A';
const optionalArgs = {valueInputOption: "USER_ENTERED"};
Sheets.Spreadsheets.Values.append(resource, spreadsheetId, range, optionalArgs);
}
When I call it, in return I get the following error:
GoogleJsonResponseException: API call to
sheets.spreadsheets.values.append failed with error: Request is
missing required authentication credential. Expected OAuth 2 access
token, login cookie or other valid authentication credential. See
https://developers.google.com/identity/sign-in/web/devconsole-project.
No need to ask, I clicked the link :-) It explains how to create an OAuth 2.0 in the developer console. Created ✅. But later it does not explain how to actually authorize the script.
I expect that before calling the Sheets API, I should be able to trigger launching the permission dialog.
I see the following, possible options:
Instead of Sheets.... use URLFetch.
Trigger the authentication (preferred).
Any tips here?
UPDATE
I've implemented testAddSalaryLog() which is exactly the same as the original function but has hardcoded values. And it works! It makes me think that the solution is to force the permission screen to trigger.
I have 3 questions/suggestions:
Question 1.
Since you are using the Sheets API. Have you enabled the Google Sheets API advanced service in your script? From Editor > Service > Google Sheet API?
Question 2.
Have you reviewed if the "Sheets API scope" were added to the script by reviewing the project Overview? If they were not added automatically, you need to add them manually by following these steps:
Access the 'Project Settings' to enable the manifest file in the editor.
Return to the editor and select 'appsscript.json'
Add the scopes:
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/spreadsheets"
Question 3.
Have you authorized the code already?
In the Apps Script editor, click Run. The first time you run the sample, it will prompt you to authorize access:
Select Review permissions > Allow.
Reference:
Method: spreadsheets.values.append scopes
Google Apps Script Quickstart for Sheets API
Advanced Sheets Service
Setting explicit scopes for Apps script
Related
I have multiple Google Apps Scripts deployed as libraries and added into Google Sheets.
The problem starts when I share these sheets with other users.
To even let them see the user menu load, I needed to give them edit access to my library scripts. View only access wasn't enough. Then I got the following error message from them "Exception: Service Admin SDK API has not been enabled for your Apps Script-managed Cloud Platform project"
And I know I'm getting this for using function AdminDirectory.Members.insert(...);
I don't want add API privileges for them in GSuite.
The following oauthScopes are in use:
"oauthScopes": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/documents",
"https://www.googleapis.com/auth/script.send_mail",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/admin.directory.group.member"
]
What I'm looking for is a solution to create these scripts in a way that I would able to add them to my Google Sheets, where user menu could be loaded from the scripts and called by users. All script code from the library/add-on (eg,: GSuite create new user / add user to group / open, edit docs / open, edit sheets) should be run through my credential, not with theirs. Users shouldn't be able to view/edit the libraries/add-on scripts' code.
The only code they should able to see and edit is the tiny onOpen script in the Sheet's script file. They have to have access to the script library / add-on but only to run it, call functions from it. They shouldn't be able to read and edit the library/add-on script code. Their code in the spreadsheet would be really small. Just an onOpen trigger which would load the menu from the script and give access to the main functions which could be called from the menu. Those would be public functions. The rest is private.
Script from Sheet
Library called "Script"
This is how my script is loading the menu in Sheets and calls the functions through it.
Issue:
If I understand your situation correctly:
You have an editor add-on through which you want to execute functions that access Admin SDK (via a custom menu).
You want users in your domain, which do not have privileges to access Admin SDK, to be able to execute these functions.
You are a domain admin and can access the desired Admin SDK services.
You won't accomplish this by placing your code in a library, since there's no way to "delegate" the execution of a library function: the library function will run under the authority of the user executing the menu function.
Solution:
In that case, I'd suggest the following alternative:
Create a service account.
Follow this guide to grant the service account domain-wide delegation, so that it can be used to impersonate any account in your domain: in this case, your account.
Import and use the library OAuth2 for Apps Script in order to use the service account in your Apps Script project.
Use UrlFetchApp to call your desired API, using the access token from the service account.
Code sample:
For example, if you wanted to call Directory API's users.get to retrieve data from the user currently executing this, you would do something like this:
function getService() {
const service = OAuth2.createService("Service account")
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(SERVICE_ACCOUNT_PRIVATE_KEY)
.setIssuer(SERVICE_ACCOUNT_EMAIL)
.setSubject(IMPERSONATED_EMAIL)
.setPropertyStore(PropertiesService.getScriptProperties())
.setParam('access_type', 'offline')
.setScope('https://www.googleapis.com/auth/admin.directory.user')
return service;
}
function getActiveUserData() {
const service = getService();
if (service.hasAccess()) {
const userKey = Session.getActiveUser();
const url = `https://admin.googleapis.com/admin/directory/v1/users/${userKey}`;
const options = {
headers: {
'Authorization': "Bearer " + service.getAccessToken(),
'Content-Type': 'application/json'
},
muteHttpExceptions: true
}
const resp = UrlFetchApp.fetch(url, options);
const userData = JSON.parse(resp.getContentText());
return userData;
}
}
I'm using Apps Script API to run a function with the service account's credential.
I added all scopes required in Rest resource API https://developers.google.com/apps-script/api/reference/rest/v1/scripts/run.
But when i run this script below it failed.
function run(){
var CREDENTIALS = {
"private_key": "Your Private key",
"client_email": "Your Client email",
"client_id": "Your Client ID",
"user_email": "Your Email address",
"api_key": "Your API key"
};
var service = getService(CREDENTIALS.client_email,CREDENTIALS.private_key);
service.reset();
if (service.hasAccess()) {
var url = 'https://script.googleapis.com/v1/projects/[SCRIPT ID]:run';
var body = {
"function": [FUNCTION NAME]
};
var params = {
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
},
method: 'post',
playload : JSON.stringify(body),
contentType: 'application/json',
muteHttpExceptions: true
};
var response = UrlFetchApp.fetch(url, params);
Logger.log(response);
}
else {
Logger.log(service.getLastError());
}
}
function getService(email, privateKey) {
return OAuth2.createService('Service Account')
// Set the endpoint URL.
.setTokenUrl('https://oauth2.googleapis.com/token')
// Set the private key and issuer.
.setPrivateKey(privateKey)
.setIssuer(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/script.external_request');
}
I've got a 404 Error and I think it comes from the scopes list.
So I can't run a script deployed as an API Executable with the OAuth2.0 token.
Which scopes should I choose to run a function via an HTTP request?
In your run function, for the params object you should have payload not playload.
You want to use Apps Script API with the service account.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Issue and workaround:
Unfortunately, in the current stage, the method of scripts.run in Apps Script API cannot be used with the service account. The official document says as follows. About this, when I tested this, I could confirm that the method of scripts.run in Apps Script API cannot be used with the service account.
Warning: The Apps Script API doesn't work with service accounts.
From above situation, as the workaround, how about using the access token retrieved by OAuth2? In order to use the Apps Script API with OAuth2, it is required to link Cloud Platform Project to Google Apps Script Project. About this, you can see the flow for linking them at here.
Note:
I think that when you use OAuth2, Oleg Valter's comment and TheAddonDepot's answer are very useful.
References:
Executing Functions using the Apps Script API
Linking Cloud Platform Project to Google Apps Script Project
If this was not the direction you want, I apologize.
Added:
You want to make several users run the script as the owner who is you.
From your replying, I could understand like above. When Apps Script API is used for above situation, the credential information is required to give each user. When each user uses the access token retrieved by your credential information, your goal can be achieve. But I cannot recommend this. So in your case, I would like to use Web Apps to achieve your goal. The flow is as follows.
1. Prepare script.
Please prepare your script. For example, in the current stage, you want to make users run a function of myFunction(), please put the following sample script.
function doGet(e) {
var values = e; // When you want to give the values by requesting, you can use the event object like "e".
var res = myFunction(values);
return ContentService.createTextOutput(res);
}
In this case, the GET method is used. When you want to only run the function, you can use this script. When you want to run the function by giving the large data, you can use doPost() instead of doGet().
2. Deploy Web Apps.
On the script editor, Open a dialog box by "Publish" -> "Deploy as web app".
Select "Me" for "Execute the app as:".
By this, the script is run as the owner.
Here, when "Anyone" is set, the script is run as each user. In this case, it is required to share the script to each user. And the access token is required to be used. Please be careful this.
Select "Anyone, even anonymous" for "Who has access to the app:".
In this case, no access token is required to be request. I think that as the test case, I recommend this setting.
Of course, you can also use the access token. At that time, please set this to "Anyone".
Click "Deploy" button as new "Project version".
Automatically open a dialog box of "Authorization required".
Click "Review Permissions".
Select own account.
Click "Advanced" at "This app isn't verified".
Click "Go to ### project name ###(unsafe)"
Click "Allow" button.
Click "OK".
Copy the URL of Web Apps. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please redeploy as new version. By this, the modified script is reflected to Web Apps. Please be careful this.
3. Run the function using Web Apps.
This is a sample curl command for executing myFunction with Web Apps. Please set your Web Apps URL. At above settings of Web Apps, each user can access by the following curl command.
curl -GL \
-d "key=value" \
"https://script.google.com/macros/s/###/exec"
When key=value is used as the query parameter like above, at doGet(e), you can retrieve value using e.parameter.key.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
You can deploy the script as a Web App. To do so, go to Publish > Deploy as web app. Set the Execute the app as: field to Me (youremail). This way you can share the script as a browser link, and any user will run the script with your credentials.
You can add a some user interface with a confirmation message so the users know they have successfully executed the script. You can find the documentation in this link.
Let's say I press a button that requires my authorization in google sheets, how can I see all the permissions I have given to scripts to execute so that I can revoke them? I understand these are only local executions.
You want to retrieve all scopes which was permitted for the GAS project.
You want to revoke the permitted scopes.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer?
1. Retrieve all scopes which was permitted for the GAS project:
When the use of scopes were permitted, this is reflected to the access token. Using this, you can retrieve all scopes which was permitted for the GAS project from the access token. The sample script is as follows.
Sample script:
var url = "https://oauth2.googleapis.com/tokeninfo?access_token=" + ScriptApp.getOAuthToken();
var res = JSON.parse(UrlFetchApp.fetch(url).getContentText());
Logger.log(res.scope)
When you run the script, all authorized scopes of the GAS project can be retrieved.
About url, you can also use var url = "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=" + ScriptApp.getOAuthToken().
2. Revoke permitted scopes:
When the scopes are authorized for the GAS project, the project can be seen at "Third-party apps with account access". For example, when the project is manually removed, the authorized scopes are revoked. This means that the access token is revoked. By this, when the script in the GAS project is run, the authorization screen is opened again. Using this, you can revoke the access token using the following script.
Sample script:
var url = "https://accounts.google.com/o/oauth2/revoke?token=" + ScriptApp.getOAuthToken();
var res = UrlFetchApp.fetch(url);
Logger.log(res.getResponseCode());
When you run the script, when res.getResponseCode() is 200, it means that the access token was revoked.
By this, you can see the GAS project was removed at "Third-party apps with account access" page.
When the script in the GAS project is run, you can see the authorization screen is displayed again.
References:
Revoking a token
OpenID Connect
Remove Third-party Apps with Account Access using Google Apps Script
If I misunderstood your question and this was not the result you want, I apologize.
I'm creating a Google Docs add-on in Google Apps Script, and some of the functionality requires that I use the Google Drive advanced service as described in https://developers.google.com/apps-script/guides/services/advanced. After enabling the advanced service, my script is now requesting the https://www.googleapis.com/auth/drive scope, which is way overbroad for what I'm trying to do - I only want to touch the files that the user is actually using this add-on with, not their whole drive! I'd much rather be using https://www.googleapis.com/auth/drive.file, which is restricted to files the user is actively using with the script.
I've tried setting the #OnlyCurrentDoc JSDoc tag as mentioned in https://developers.google.com/gsuite/add-ons/concepts/scopes#editor_add-on_scopes, but that only changes the broad https://www.googleapis.com/auth/documents scope to https://www.googleapis.com/auth/documents.currentonly - it doesn't change the Drive API scope.
Also, I've verified that the script does actually need the auth/drive scope, because when I went into the project manifest and explicitly requested auth/drive.file, I got a 404 response with API call to drive.revisions.list failed with error: File not found: 1Sj_oq93ny5q9348ncyo8934nyc at getAuthors(Code:54) at showSidebar(Code:20). That's exactly what I'd expect for a file that hasn't been "tagged" for use with this script.
Here's a very minimal gdocs addon that shows the issue:
function onOpen(e) {
var menu = DocumentApp.getUi().createAddonMenu();
menu.addItem("Get revisions", "getRevisions");
menu.addToUi();
}
function getRevisions() {
var docId = DocumentApp.getActiveDocument().getId();
Logger.log("Document id: "+docId);
var revs = Drive.Revisions.list(docId);
Logger.log("Found revisions: "+revs.items.length);
}
Again, this works just fine with the default auth/drive scope, but not with auth/drive.file.
The documentation for https://www.googleapis.com/auth/drive.file specifies that it grants "Per-file access to files created or opened by the app. File authorization is granted on a per-user basis and is revoked when the user deauthorizes the app." according to the docs at https://developers.google.com/drive/api/v2/about-auth#OAuth2Authorizing. What isn't clear is: how does Google determine what files have been "created or opened by the app", especially when it comes to editor addons? I would think that any document that has had the add-on enabled would count as "opened by the app", but I guess not. Is there any way to make this scope work?
I'm trying to set the Gmail signature of the user executing the script (Execute the app as: "User accessing the web app"; Who has access to the app: "Anyone within my domain") using the following function:
function setSignature(signature) {
var newSig = Gmail.newSendAs();
newSig.signature = signature;
Gmail.Users.Settings.SendAs.patch(newSig, "me", Session.getActiveUser().getEmail());
}
where signature is some html. This function is called from a client-side script when a form is submitted:
google.script.run.withSuccessHandler(signatureSuccess).setSignature($("#signatureParent").html());
The user is served a web app using the HtmlService containing the form. The Gmail API has been enabled in both the Advanced Google Services window as well as the Google API Console.
My issue is that when the I try and execute the function I receive the following console error message:
The message states that the auth scope gmail.settings.basic is missing. This is despite the user authorizing the web app before any html is served:
How do I fix or work around this issue?? The strange thing is I've had this working previously so I don't know what I'm doing wrong.
EDIT:
I've noticed that if I create a simple Apps Script with just the function:
function testSet() {
var testSig = "signature";
var newSig = Gmail.newSendAs();
newSig.signature = testSig;
Gmail.Users.Settings.SendAs.patch(newSig, "me", Session.getActiveUser().getEmail());
}
And leave out everything else I get presented with these permissions to authorize:
If I click Allow it works! So clearly "Manage your basic mail settings" a.k.a. auth scope gmail.settings.basic is required and isn't being asked for in the more involved script.
So how do I force that permission to be acquired or how do I rewrite my script to get the correct set of permissions needed?
After extensive testing I've determined that this issue is a bug in Google Apps Script in determining what scopes are required.
A basic version of my script requires these scopes (File > Project Properties > Scopes):
Extending the script to interact with Google Drive modifies the scopes to this:
By dropping the required gmail.settings.basic scope a critical function within the script is denied permission to run. Infuriating.
I was also facing the same issue on nodejs application, the solution is to generate referesh token using this required scope which is mentioned in the rest api documentation find below.
rest apis documentation
you can create refresh token using required scopes on this link if you're logged in developer account.
https://developers.google.com/oauthplayground: