How can I create an authenticated doPost API with Google Apps Script? - google-apps-script

What I want to realize
Use doPost API with authentication from curl
Now, I'm using an API that automatically creates a google form from POST data and returns the form URL and other data.
This is made using GoogleAppsScript doPost.
This publishing method is
Who has access to the app: Anyone, even anonymous
Even if you are not logged in to your google account, you can use the API if you know the url.(Rather, I'm using curl to make an API so I can use it anonymously)
Until now, there was no problem because it was operated with a personal google account.
But now I need to run this API with my corporate G-suite Google account.
In this case, the only option for Who has access to the app: is only myself or an account belonging to the company.
Therefore, API cannot be used from anonymous such as cURL command.
How can I prove that I am the owner of this script or a user in my company?
Same with doGet, not just doPost.
When a browser logged in with my account makes an HTTP request to the "Current web app URL", the response is returned as expected.
If I use an anonymous user like curl, it's natural to redirect to the google account login page.
Is there a way to prove that the user of curl is me or a company person by issuing a token and having it in an HTTP header?
Or is there any way to achieve the same thing as my API with some alternatives?
Even if it is not curl, I can use a script written in python or js and so on.
If you are familiar with GAS, have experience, or have some information that may be helpful, please let me know.
Thank you.
And
I'm not good at English.
Sorry for the bad grammar.
What I tried
I heard that an API with authentication can be created with ExecutionAPI (Apps Script API?). I also challenged.
However, the API is not yet available from curl.
Required OAuth scope for this script
https://www.googleapis.com/auth/forms
Source code
function doPost(e) {
var postData = JSON.parse(e.postData.getDataAsString());
if (postData.title) {
const title = postData.title;
const description = postData.description;
} else {
var returnData = ContentService.createTextOutput();
returnData.setMimeType(ContentService.MimeType.JSON);
returnData.setContent(
JSON.stringify({
message: 'please input title!',
error: true,
})
);
return returnData;
}
// create form
var form = FormApp.create(title);
form.setDescription(description);
var items = postData.items;
var itemIdList = []
while (items.length) {
var item = items.shift();
var textItem = form.addTextItem();
textItem
.setTitle(item.question);
itemIdList.push({
question: item.question,
item_id: textItem.getId(),
});
}
var returnData = ContentService.createTextOutput();
returnData.setMimeType(ContentService.MimeType.JSON);
returnData.setContent(
JSON.stringify({
published_url: form.getPublishedUrl(),
edit_url: form.getEditUrl(),
error: false,
form_id: form.getId(),
item_id_list: itemIdList,
})
);
return returnData;
}
POST data example
{
"title": "Question title",
"description": "This is a description.",
"items": [
{
"question": "How old are you?"
},
{
"question": "What's your name"
},
{
"question": "Please tell me the phone number"
}
]
}

Related

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

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 :)

Google Web Apps - Get user email but run scripts as owner

I've recently gravitated to google web apps and I have a bit of a dilemma. I'm trying to build an app that is open to very specific users and the data they are viewing filters based on their access group.
In a google sheet I'm listing the user emails and their respective access groups. Column A - email, Column B - access group
The issue
When the user accesses the web app I'm using this to grab their email:
var email = Session.getActiveUser().getEmail();
And then I run this code to get their access group:
function validate(email){
var sheet = SpreadsheetApp.openById(ID).getSheetByName(ssUserList);
try{
var group = getRowsData(sheet).find(e => e.userEmail === email).securityGroup;
return group;
} catch(e){
return "Not Authorized";
}
}
Because the user doesn't have access to my google sheet, they get an error when the function runs. And I can't deploy the web app to run as me because I need the user's email. I understand this very well.
What I've read:
Tons of other posts and articles about access tokens and credentials and urlFetchApps ... I don't understand any of it and to be honest I don't know which one makes more sense for my situation.
What I've tried:
I can't use the 1st usable option I've found which is to access web app 1 (which runs as user), then call web app 2 using the user email as a parameter because if they share that link from web app 2 then anyone could see the data and I'm working with really sensitive data.
I realize I could just put these parameters in a separate sheet and give them view only access and the scripts will run fine, but I'm extra and I want to do it right.
In reality I'm going to have a few other functions that will need to run as me. If you were in my shoes, where would you start? Or can someone explain it in layman's terms? Should I be looking into something like this? Any help is appreciated!
Summary
One of the possibilities, as suggested here, is to create a separate web application to handle access to SpreadSheets.
The client (the main web app) would make a request through UrlFetchApp to the middleware (web app in charge of consulting the SpreadSheet), the middleware would make the needed queries and would return them to the client. Finally, depending on the response obtained, one content or another would be rendered.
Minimal Example
Configuring the Project
First, we create two GAS projects, one we call Main and the other Middleware. The main point is that the Middleware would run as USER_DEPLOYING and the client as USER_ACCESSING. This allows access to the sheet without requiring additional permissions.
The appscripts.json file would look like this on the client. :
"oauthScopes": [
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/userinfo.email"
],
"webapp": {
"executeAs": "USER_ACCESSING",
"access": "ANYONE"
}
And like this on the middleware:
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets"
],
"webapp": {
"executeAs": "USER_DEPLOYING",
"access": "ANYONE_ANONYMOUS"
}
If you have any questions about editing or viewing appscript.json, check the Manifest and Scopes documentation.
Attention: "access": "ANYONE" and "access": "ANYONE_ANONYMOUS" are only being used for testing purposes. This is dangerous, and should be reviewed for the specific needs of your project.
Code Sample
As for the client, we only need to ask for the email of the user who is accessing through Session.getActiveUser().getEmail() and then send it to the middleware to obtain the response. Depending on the response obtained, we will render one content or another (I assume there are two roles present: USER and ADMIN)
Client
const doGet = () => {
var data = {email: Session.getActiveUser().getEmail()}
var options = {
'method': 'POST',
'contentType': 'application/json',
'payload': JSON.stringify(data)
}
var fetch = UrlFetchApp.fetch(URL_MIDDLEWARE, options)
var userAccess = JSON.parse(fetch).accessLevel
return HtmlService.createHtmlOutput(
userAccess === "ADMIN"
? `<h1>${data.email} - ADMIN USER</h1>`
: userAccess === "USER"
? `<h1>${data.email} - COMMON USER</h1>`
: "<h1>Unauthorized</h1>" )
}
For the middleware we need to obtain that email and compare it with our sheet to check the access level of the user. Then we return the result.
Middleware
const doPost = (request) => {
// destructuring the request
const { parameter, postData: { contents, type } = {} } = request;
const userEmail = JSON.parse(contents).email;
let userAccess = SpreadsheetApp.openById(SPREADSHEET_ID).getRange('A1:B2').getValues()
// This can be replaced by a filter function
let userAccessLevel;
for (let user of userAccess) { if (userEmail == user[0]) userAccessLevel = user[1] }
return ContentService.createTextOutput(Utilities.jsonStringify({
user: userEmail,
accessLevel: userAccessLevel
}))
};
Finally, you access the Main Web App to check that everything is working.
Remember that this is a test implementation, and should not be used in production. If you need more information on these topics, you can visit the following links:
Load data asynchronously, Best Practices
Request Parameters doGet() doPost()
ContentService Class

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.

How do I send the standard invitation email when calling addGuest on a CalendarEvent?

Whenever I add a guest via the Google Calendar UI, the following dialog pops up:
If I choose "Send" it sends a nicely formatted email to the user with an option to respond to the calendar event too:
This works flawlessly when you're manually using the Google Calendar UI. Problem is I'm trying to use Google Apps Scripts to automate adding people to events.
I'm programmatically adding guests to a CalendarEvent by using addGuest():
event.addGuest("user#example.com");
However, there doesn't seem to be an option to send an email.
The closest I could find is that when you programmatically create an event, you can set sendInvites:
var event = CalendarApp.getDefaultCalendar().createEvent(
'Test',
new Date('April 5, 2029 20:00:00 UTC'),
new Date('April 5, 2029 21:00:00 UTC'),
{sendInvites: true, guests:"your.email+1#example.com"});
Logger.log(event.getId());
This sends a nicely formatted email correctly.
However, the sendInvites option only works when the event is newly created. If at a later time I call:
var event = CalendarApp.getEventById("insert_id_here");
event.addGuest("your.email+2#example.com");
... it does not send an email update (even though the event was created with sendInvites=true). This means sendInvites is transient and doesn't persist on the event.
Is there any way to sendInvites when calling addGuest() on an event? E.g. event.addGuest("user#example.com", {sendInvite: true})?
Are there any other workarounds that will produce the same email that is sent when hitting that "Send" button in the UI above?
Note: I do not want to use MailApp to send an email because it won't be nicely formatted like the one Google automatically sends out (e.g. with embedded calendar invite, attendees, description, response links, etc, etc, etc).
Update:
I just saw getAllTagKeys in the API and was hoping I could use that to get the sendInvites key, but that's not the case. It is empty even for events that have sendInvites set to true.
As tehhowch mentioned, the key is you must use the Advanced Google API for this. However, the text on this page that says:
This sends an invitation email to the attendees and places the event on their calendar.
... is very misleading. The only way it'll send email is if you set sendUpdates to all for the request.
First, you can verify that sendUpdates is the key piece to sending email for new events via the advanced API:
Go to this link.
Hit the Try it now button up top.
This should show you a form with the following info populated:
calendarId: primary
sendUpdates: all
Request body:
{
"start": {
"date": "2029-04-04"
},
"end": {
"date": "2029-04-04"
},
"summary": "Test",
"attendees": [
{
"email": "your.email+1#example.com"
}
]
}
Change the your.email+1#example.com to your email address.
Note that this cannot be your exact email address that you are logged in with. Otherwise the script will notice that you're trying to send an email to yourself and not send it. Instead, if you have a Google email account, you should use a plus address instead.
Hit the Execute button.
After giving it permissions, you will receive an email to your inbox with the calendar event fully populated just as it is sent when using Google Calendar's UI.
If you want to definitively prove that sendUpdates is important, set it to blank instead of all. Send the invite. Notice that it didn't arrive in your inbox.
If it's not working, make sure that you follow these caveats:
You must not send an email to the same logged in Google account. E.g. either use a plus address, or send it to a different account.
Ensure that sendUpdates is set to all.
Ensure that the event is some time in the future. Google will never send email for invites sent to events in the past. This is true even if you manually use Google Calendar's interface. It'll ask you if you want to send updates to the user(s), you choose "yes", but because the event is in the past, it won't actually send email.
Since the original question already shows how to use the standard CalendarApp to do what we did above, we didn't accomplish much, yet. Now that we know that sendUpdates is important, though, it's time to look at adding a guest.
As the original question points out, there is no way to specify sendInvite (as we would with the standard Calendar API) or sendUpdates (as we would with the advanced Calendar API) when using CalendarApp. The only method we have is addGuest().
Luckily, we can use the Advanced Calendar API similar to how we did above and send an update (aka PATCH) to the event. Rather than interfacing with raw JSON and the Advanced Calendar API, we can use Google's nice wrapper around the advanced API (aka Calendar).
However, since we're using the advanced Calendar API, we must abide by this note:
Note: This is an advanced service that must be enabled before use.
This means there are some prerequisites before jumping into code:
First, enable Google Calendar API in your Cloud Platform project:
In your script, navigate to Resources > Cloud Platform project...
Do one of the following:
If you see This script has an Apps Script-managed Cloud Platform project., congratulations, you're done with this step!
If you see This script is currently associated with project:, do the following:
Click on the link underneath This script is currently associated with project:
This will take you to the current project on Google Cloud Platform.
APIs & Services > Dashboard
Enable APIs and Services
Add Google Calendar API
Hit the Enable button.
You can safely ignore the warning: To use this API, you may need credentials. Click 'Create credentials' to get started.
If both of the above fail, you can try the nuclear route and recreate your spreadsheet/doc/script whatever.
Next, enable Google Calendar API for the script:
Back in your script, Resources > Advanced Google services
Hit "On" next to Google Calendar API.
You're now ready to use the advanced API in a script.
With those prerequisites out of the way, you can now create a function:
// Note: requires advanced API
function addGuestAndSendEmail(calendarId, eventId, newGuest) {
var event = Calendar.Events.get(calendarId, eventId);
var attendees = event.attendees;
attendees.push({email: newGuest});
var resource = { attendees: attendees };
var args = { sendUpdates: "all" };
Calendar.Events.patch(resource, calendarId, eventId, args);
}
Some caveats:
When you pass an eventId, you will need to remove the #google.com at the end of the eventId.
You must call get first to get the current list of invitees. If you don't, you'll accidentally remove all the people that were on the event originally. See this answer for more info.
Note: patch is smart and will only send email updates to the people that were actually added. E.g. if the current people on an event are [alice, bob], and you send a patch with [alice, bob, charlie], only charlie will receive an email. This is exactly what you want!
All the previous caveats in the list above apply.
Again, the key is knowing how to pass in sendUpdates to the insert() method. I couldn't find any documentation about the patch method1, but the auto-complete revealed that there is an optionalArgs parameter that can be added. I randomly tried the format you see above, and it worked!
If it's not working, make sure you read all caveats above.
Note: I'm glad this worked with Google's API, because if it didn't, it likely meant needing to manually send the email(s), and look into how to add calendar metadata to a message so that Gmail parses it properly.
1 We have the general patch API, but this is only useful when sending the raw JSON. We're using Google's Calendar wrapper, which doesn't map exactly the same.
Just an update to senseful answer. Since the api has been updated in it's latest version.
I modified the function to:
const { google } = require("googleapis");
const calendar = google.calendar({ version: "v3" });
async function addGuestAndSendEmail(calendarId, eventId, newGuest) {
try {
const event = await calendar.events.get({
calendarId,
eventId,
});
// console.log("micheal ~ file: index.js ~ line 316 ~ addGuestAndSendEmail ~ event", event)
const attendees = event.data.attendees;
attendees.push({ email: newGuest });
const resource = { attendees: attendees };
const args = { sendUpdates: "all" };
// resource, calendarId, eventId, args
const res = await calendar.events.patch({
calendarId,
eventId,
sendUpdates: "all",
requestBody: {
attendees,
},
});
return res
} catch (err) {
throw new Error(err.message);
}
}
And i called it this way:
async function inviteAnEmail(id, attendee) {
try {
const res = await addGuestAndSendEmail("primary", id, attendee);
console.log(res.data, "invite Email Data");
} catch (err) {
console.log(err.message);
}
}
refer the following request.
method: POST
endpoint: https://www.googleapis.com/calendar/v3/calendars/primary/events?sendUpdates=all
here, sendUpdates means when you add any guest so he would get an invitation mail used based on scenario.
Input Json Like:
{
"kind": "calendar#event",
"etag": "etag",
"status": "confirmed",
"summary": "JayKara",
"description": "eqwbdjhwhhwhhwrhjehrhejhfj",
"location": "America",
"creator": {
"email": "**#mail.com",
"self": true
},
"organizer": {
"email": "**#mail.com",
"self": true
},
"start": {
"date": "2019-12-23"
},
"end": {
"date": "2019-12-24"
},
"originalStartTime": {
"date": "2019-12-24"
},
"visibility": "public",
"attendees": [
{
"email": "****#mail.com" //this guys are the guest
}
]
}.
After that there is no patch method required your guest guys will receive an invitation whenever event will be updated.
Cheers!

How to get the list of members in a Google group in Google app script (Admin SDK)?

I would like to get a list of members in a Google group using Admin SDK.
But im not getting how to do this. I found below link - https://developers.google.com/admin-sdk/directory/v1/guides/manage-group-members
But I do not know how to use POST method in Google app script.
Can someone please guide me with an example?
UPDATED
I got the output as below, But i would like to access each element (role,email) separately for each member of the group. Is that possible??
{
"role": "OWNER",
"kind": "admin#directory#member",
"type": "USER",
"etag": "\"fdo0/1gUrEe8bli75zvzmqFHyH3cPzlQ\"",
"id": "107108832717913338955",
"email": "useremailid#domain.com",
"status": "ACTIVE"
}
Thanks in advance.
Please follow the steps,
Login your G drive using super admin account.
Create one Google sheet.
Go to sheet Tool menu -- script Editor.. Save Script project project. Enable admin SDK
Apps script editor click on Resources menu select Advance Google Services -- In Pop up on Admin Directory Apis then Click Google Developers Console link.
Developer Console -- click on Library --> search APIs box enter Admin sdk --> click on admin Sdk api link ==> ENABLE and close dev console.
=== At Apps script editor paste following code.
var onSheet = SpreadsheetApp.getActiveSpreadsheet();
var groupKey = "googlegroupid#domainName.com"
function MainGetUserList()
{
var rows = [];
var pageToken, page;
do {
page = AdminDirectory.Members.list(groupKey,
{
domainName: 'YOURDOMAINNAME.#com',
maxResults: 500,
pageToken: pageToken,
});
var members = page.members
if (members)
{
for (var i = 0; i < members.length; i++)
{
var member = members[i];
var row = [groupKey, member.email, member.role, member.status];
rows.push(row);
}
}
pageToken = page.nextPageToken;
} while (pageToken);
if (rows.length > 1)
{
var sheetData = onSheet.getSheetByName("Sheet1")
var header = ['Group Name', 'User Id', 'User role', 'User Status'];
sheetData.clear()
sheetData.appendRow(header).setFrozenRows(1);
sheetData.getRange(2, 1, rows.length, header.length).setValues(rows);
}
}
==> Run MainGetUserList() function allow the permission, open you Google Sheet refresh it.
Done.
I have tested this 20k members.
Thanks
Try to use Members:list to retrive list of all members in a group.
HTTP request
GET https://www.googleapis.com/admin/directory/v1/groups/groupKey/members
Every request your application sends to the Directory API must include an authorization token. The token also identifies your application to Google. Your application must use OAuth 2.0 to authorize requests.
HTTP response:
{
"kind": "admin#directory#members",
"etag": etag,
"members": [
members Resource
],
"nextPageToken": string
}