How to authenticate a cloud functions with the Api https://www.googleapis.com/drive/v3/changes/watch? - google-cloud-functions

I am developing an application that interacts with Google Drive and will work as follows: When a user adds/modifies a file in Drive Share, my application will receive a notification and I will handle it. I did the development locally using Auth2 authentication and everything works perfectly, but this application will be hosted on a Cloud Functions and because of that I am not able to use Auth2 authentication, as user consent is required.
Due to this problem, I went to the perspective of using a Service Account, where I added it as the manager of my share drive, used it to create the function, and gave it all the necessary permissions, but when I modify a file, the my endpoint that was supposed to receive the message, just doesn't.
I did a search and saw that it's due to the service account not having access to user data, so it makes sense that no notification would be created.
Below I am attaching the code I am using to create the watcher on the drive and the authentication process by SA:
Code responsible for get credentials to authentication
SCOPES = [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.metadata.readonly"
]
credentials, project_id = google.auth.default(scopes=SCOPES)
credentials.refresh(req.Request())
Code responsible for creating the watch
drive = discovery.build("drive", "v3", credentials=credentials)
params = {
"kind": "api#channel",
"id": "id_watcher",
"type": "webhook",
"address": "address cloud functions"
}
# r = drive.changes().watch(fileId=file_id, body=params, supportsAllDrives=True, supportsTeamDrives=True).execute()
r = drive.changes().watch(pageToken=1,
body=params,
driveId=driverId,
includeCorpusRemovals=True,
includeItemsFromAllDrives=True,
includePermissionsForView=None,
includeRemoved=True,
includeTeamDriveItems=True,
pageSize=None,
restrictToMyDrive=None,
spaces=None,
supportsAllDrives=True,
# supportsTeamDrives=True,
# teamDriveId=driverId
).execute()
My question would be if there is a way to use Auth2 without the need for user consent, that is, without the step of opening the browser and allowing the generation of the token. If not, can you help me with a method that might work?
Remembering that this code will be in a cloud functions.
Thank you very much!

Two suggestions, one for the user consent scenario and one alternative for the notifications:
Domain Wide Delegation and Impersonation
If you are using Google Workspace and building the application for the organization. You can use Domain Wide Delegation if you are utilizing Service accounts. This would allow you to start the process of impersonation and avoid the consent of the users.
As suggested by the official documentation you can also apply and review the ways to grant the service account with the option to impersonate users.
Generating Notifications
Another suggestion could be utilizing Pub/Sub or push notifications to have your alerts. You would be able to utilize the Service account and your code and get the notifications and have them similar to an Audit log:
The image is a sample of a Gmail APi for watch and list.
References:
https://cloud.google.com/pubsub/docs/overview
Google Drive API - file watch does not notify (webhooks sample)

Related

Cloud Schedule + Cloud Functions -> Gmail API watch() - WORKING NOW

This is my first post here. I am sorry if it's a repost, but I've been searching for more than one month for the answer to solve my problem in all websites and forums and until now... no answers!
My goal is to make a Gmail pub/sub watch() to make an action whenever I receive a new email.
To do so, according to the developer's website, I need to subscribe to Gmail watch() on a daily basis with the code:
request = {
'labelIds': ['INBOX'],
'topicName': 'projects/myproject/topics/mytopic'
}
gmail.users().watch(userId='me', body=request).execute()
Until now i have this a working scheduled task with a service account, with INVOKER Permissions. This part just works fine.
In my "initial autorization function" i have:
const {google} = require('googleapis');
// Retrieve OAuth2 config
const oauth2Client = new google.auth.OAuth2(
process.env.CLIENT_ID,
process.env.CLIENT_SECRET,
process.env.CALLBACK_URL
);
exports.oauth2init = (req, res) => {
// Define OAuth2 scopes
const scopes = [
'https://www.googleapis.com/auth/gmail.modify'
];
// Generate + redirect to OAuth2 consent form URL
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
//prompt: 'none'// Required in order to receive a refresh token every time
});
return res.redirect(authUrl);
};
My issue now is that the access token is generated via (prompt) the first time and never updates to a new one ( the token expires after 1hour...) it means this code stops working after that period and a "manual" intervention is required. According with the documentation, i need to use "offline" method and on "prompt" i can omit (only requests permissions on the 1st time) or none (never asks), like is said here.
I managed how to make it work! tomorow i will continue with the process.
Should i post here my working code for reference?
Thanks!
I will rephrase the process you illustrated so that there is no ambiguity.
According the documentation you pushed:
You do not suscribed to watch(), you call watch()
watch() is an API call to the Gmail API that will enable automatic events publication on a pub/sub topic you define given conditions you specified. Who are you watching? On what events?
You suscribe to a Pub/Sub topic that is targeted by your previous watch() call
A process (e.g: Google cloud function) suscribes to the topic and will consume messages sent by the Gmail API
The call is to be renewed at least every seven days
Because Google needs to be sure you still need to monitor the targeted inbox, it needs a renewal from you. Another watch() call will act so.
Cloud scheduler will enable this periodic renewal
this service will trigger your renewal script you put in your question. To do so it needs to be authenticated to the platform that host the script. It is easier if your script is hosted in a google service (cloud function, cloud run,...) and the authent type depends on the target URL form. In all cases YOU DO NEED an authent token in your request header. The token is generated from a service account you created with the right permission to call your script (e.g: cloud run invoker). By default the scheduler has the right to generate a token from it
So far so good. Now comes the tricky part and you don't mention it in your question. How is authenticated your gmail api client? You cannot monitor someone inbox, unless this person gave you the permission to i.e you call the API with the right Oauth2 token. Indeed in the video you point they authenticat the user using this principe which is implemented in their code with Express-oauth2-handler.
So you will have a cloud function to init end user authent and watch to his/her inbox. The renewal should do so but problem is user will not be there for accepting the end user consent. Here comes the offline access but it is beyond the scope of your question. Finally a second functions will suscribe to the pubsub topic and consume the message as you need. See their implementation code which populate a spreadsheet.
The documentation you shared in the comments does not say that you can remove the token from the headers of the service account, also the gmail API documentation you also shared says that you only:
need to grant publish privileges to gmail-api-push#system.gserviceaccount.com. You can do this using the Cloud Pub/Sub Developer Console permissions interface following the resource-level access control instructions.
In order to achieve this basically what you will need is a setup of two cloud functions, the first scheduled function is responsible for setting up the watch(), and you can check this documentation for how to deploy a scheduled function, and the second function being triggered by the pubsub of gmail notifications, you can check this documentation for how to build an event triggered function. Both processes are similar.
NOTE: I have never user the Gmail API, so I am not sure if any extra steps are necessary but then again, the documentation implies that setting up the permissions of that service account is enough to make it work.
EDIT:
As per the information you have shared. The issue is likely that you are not properly setting the Service Account to authenticate with the Cloud Function. As per described in the documentation, you have to grant to the Service Account the role Cloud Functions Invoker in IAM.
Let me know if this fixed the issue.

Google Drive API Share document for offline writing/updating

I have created a web app which is making use of Google Drive API/ REST v2 (https://developers.google.com/drive/v2/web/about-sdk) to perform actions such as create/update/rename/delete of documents etc.
I am authorizing requests with OAuth 2.0 (client side - that means every access token is valid for ~1h and then silently I am getting a new token) and then perform previous actions using that token.
I have a new requirement for the authorized user to share his/her documents for writing/updating them (I found out that API has option for inserting permissions (https://developers.google.com/drive/v2/reference/permissions/insert : role: writer, type: anyone).
Is it possible for a non-authenticated user to be able to write/update documents (programmatically - via Google Drive API v2 or another API?) that have been created from the authenticated user that shared these? (something that is similar to google docs/ sharing when a user is sharing his document and offline users are able to edit it?
Thanks.
Is it possible for a non-authenticated user to be able to write/update documents (programmatically - via Google Drive API v2 or another API?) that have been created from the authenticated user that shared these? (something that is similar to google docs/ sharing when a user is sharing his document and offline users are able to edit it?
What you are describing here is something called a service account. Service accounts are like dummy users. You can share a file on your Google drive account with the service accounts email address and the service account will then have access to that file. Assuming that you gave them edit permissions it will be able to read and write to it without authenticating.
Note: service accounts do not work client sided you will need to use a server sided language to use service accounts.

Document List API to Drive SDK and Client Login

We used to have an application connector implementing the Document List Service v3 to upload documents to users account. Now that the service will be discontinued starting as of next Monday and we need to migrate to the Drive API/SDK we have the problem to migrate our current login schema .. we are unable to use the OAuth 2 protocol and we need to authenticate users with their username/password credentials.
DocumentsService myService = new DocumentsService("xxx");
myService.setUserCredentials(username, password);
The reason is that our application scans and processes documents asynchronously from MFD devices (printers) and all processing/storage job is done in a different moment on processing servers, thus the limitation that the processing service cannot ask any consens to the user.
We do the same for other online cloud storage application (e.g. Dropbox) where they allow special 'OAuth 1' schema on request for such 'enterprise' situations.
How can we do this with the new Drive API/SDK? I couldn't find anything about that in the documentation rather than the service account, also looks like not suitable.
What you need to do is request authentication from you user once. The server gives you back a refresh token. Your automated application can then use this refresh token to get a new access token. You only need to ask the user one time for authentication. Then everything can run automated.
A service account wont really work in this instance because its meant for use with an account that you the developer own not a users account

How to prevent suspended Google account to signup

I am integrating ASP.NET application using Google Drive API. For this after authentication we re uploading Files to Google drive. I am using Google client library to Call the APIs.
Everything is working as expected I am able to authenticate user successfully and able to upload the file successfully.
In one scenario when the user Google account is suspended then I am getting refresh token from Google but my upload method is failing and it is not uploading the file to Google drive.
I want to restrict the user on Signup screen itself, when account is suspended.
What parameter do I have to pass to achieve this please suggest?
Unfortunately this info is not easily available. You have two options :
Use the Directory API to see if the user is suspended. This requires additional OAuth permissions to be provided by an admin of the domain.
At login, try and perform a Drive API call to see if you get an error or not. If you get an error (with a couple of retried) and the error message matches the one you had for suspended users, then you can deny access to the user.

Impersonating users of Drive of one domain as a domain administrator

I'm looking for a way to impersonate users of a google app domain using a admin user. I could do it easily with google data document list api but I cant find a way to do it with the new Drive API.
Precisely, what I want to do is authenticate my admin user using Oauth2 (i've already done this), retrieve a list of the users of my domain and then impersonate my users, or at least be able to access files and docs from the Drive of those users.
In the administrative panel of google apps, there are Oauth consumer key and Oauth consumer secret, but these are used in Oauth1 2LO, not Oauth2.
Is there a proper way/workaround/hack to implement what I want ?
Best regards,
Jérôme
I've only been looking at the google-api-ruby-client as an example but you should be able to do this with a service account that is permitted access through the admin panel -> Advanced tools -> manage third party oauath clients. Once permitted you can follow the example for a service account here http://code.google.com/p/google-api-ruby-client/source/browse/service_account/analytics.rb?repo=samples but instead of authorizing with
client.authorization = asserter.authorize()
you can use
client.authorization = asserter.authorize("id#domain.com")
I haven't done a lot with this yet but after authenticating in this method I've been able to list all documents owned by a user on my domain.
Thanks to James Woodward, i've been able to impersonate user. I post an answer to provide Java specific details.
Create a service account in the API console. 3 important resources are created :
Client ID : used to authorize the app on the Google Apps domain
Email address : used to authorize the requests of the app
.p12 key file : used to authorize the requests of the app
Authorize the app on the Google Apps Administrative panel, providing it with Service account client ID, and all the scopes the app will need.
Create GoogleCredential this way :
GoogleCredential serviceCred = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(SERVICE_ID)
.setServiceAccountScopes(Arrays.asList(SCOPES))
.setServiceAccountUser("impersonated.user#domain.com")
.setServiceAccountPrivateKeyFromP12File("key.p12")
.build();
Those credentials can now be used to authenticate the requests made by the app on any scope authorized.