Can Google API domain-wide delegation service accounts enable gmail push through impersonating a user? - google-api

I created a service account on my Google Apps domain with domain-wide delegation enabled, and full gmail and pubsub API scopes enabled on the service account's client ID from my domain's control panel.
I can successfully instantiate a gmail API client and impersonate one of the domain's accounts with:
credentials = oauth2client.SignedJwtAssertionCredentials(secret['client_email'], secret['private_key'], ['https://www.googleapis.com/auth/gmail.modify'], sub='accountToImpersonate#domain.com')
http = httplib2.Http()
credentials.authorize(http)
return discovery.build('gmail', 'v1', http=http)
I am attempting to set a push notification webhook on the impersonated user's account with the following. The specified topic name is valid.
request = {
'labelIds': ['INBOX'],
'topicName': 'projects/projectName/topics/topicName'
}
gmail.users().watch(userId='me', body=request).execute()
I receive the following error after the call to watch:
<HttpError 400 when requesting https://www.googleapis.com/gmail/v1/users/me/watch?alt=json returned "Invalid developer ID">
Calls to other gmail API methods (list messages, etc.) are successful.
Are service accounts with domain-wide delegation able to enable gmail push notification webhooks on a domain user's account?

I have successfully done this recently with a service account by doing just that ^^, simply pass the email address of the user instead of 'me' in the pubsub client setup call

Related

Google Admin API with service account -- bad credentials

I'm trying to write console app code to update Google Directory with values pulled from a SQL database. I can't seem to make the API connect successfully when using a service account. Any ideas? I've whittled the code down to just the essentials here.
static void Main(string[] args)
{
try
{
// Create a ServiceAccountCredential credential
var xCred = new ServiceAccountCredential(new ServiceAccountCredential.Initializer("saxxxxxxxx#directorysync-xxxxxx.iam.gserviceaccount.com")
{
Scopes = new[] {
DirectoryService.Scope.AdminDirectoryUser,
DirectoryService.Scope.AdminDirectoryUserReadonly
}
}.FromPrivateKey("-----BEGIN PRIVATE KEY-----\nMI...p9XnI4DZFO/QQJc=\n-----END PRIVATE KEY-----\n"));
// Create the service
DirectoryService service = new DirectoryService(
new BaseClientService.Initializer()
{
HttpClientInitializer = xCred,
}
);
var listReq = service.Users.List();
listReq.Domain = "mycompany.com";
listReq.MaxResults = 100;
listReq.OrderBy = UsersResource.ListRequest.OrderByEnum.Email;
Users results = listReq.Execute();
// process the users list here...
}
catch (Exception e)
{ Console.WriteLine(e.Message); }
}
The error happens at the .Execute() line:
Google.Apis.Requests.RequestError
Not Authorized to access this resource/api [403]
Errors [
Message[Not Authorized to access this resource/api] Location[ - ] Reason[forbidden] Domain[global]
]
I've tried code seen elsewhere (How to login to Google API with Service Account in C# - Invalid Credentials) to bring in the whole contents of the .JSON file that contains the credentials for the service account; that made no difference. I'm not the google domain admin, but the admin built the credential and promises that it does, indeed, have rights to the user resources. I'm utterly lost at what's not right.
Either:
You're missing the email address of an admin user to impersonate.
An Admin of the domain needs to assign user management privileges to the service account.
Background
There's two HTTP requests involved in making a Google API request with a service account:
Using the service account's credentials to request an access token from the Google OAuth 2.0 server. A HTTP POST is sent with a JWT signed with the private key of the service account. If successful, an access token is returned which is valid for one hour.
Making the service API request (Admin SDK Directory API in this case) using the OAuth access token obtained from the last step.
The error message you provided is not listed in the JWT error codes page so step 1 is working, and the error is coming from step 2 - the request to Directory API.
You should be able to confirm this using an HTTPS request interceptor like mitmproxy.
You'd get a 403 error for the Directory API users.list method for a few reasons:
You've authenticated as a service account, but that service account has no admin privileges which are sufficient for the request.
You've authenticated as a user in the domain (either using a service account with impersonation with the sub parameter in the JWT request, or using three-legged interactive OAuth), but that user has no admin privileges which are sufficient for the request.
You've authenticated as a service account or user with sufficient privileges, but the domain parameter is not owned by the customer for which the service account or user is associated with.
In your sample code, there is no email address of a domain user (admin) specified (the email address for ServiceAccountCredential.Initializer is actually the OAuth client name of the service account, not a real email address), so it is either case 1 or 3.
To address case 1
A service account has no association with a domain or access privileges by default.
An admin can open Admin console, go to "Account > Admin roles > "User Management Admin" > Admins > Assign service accounts", then:
Add the service account name (looks like an email address, ending #gserviceaccount.com).
Click "Assign role".
Now, the service account does not need to impersonate another admin in the domain, it will directly have admin privileges.
A couple of side-effects of this:
The service account cannot be "suspended" as such, only removed from the admin role.
Audit logging of the actions will show the service account name in the "Reporting > Audit > Admin" section of Admin console.
To address case 2
You may have meant to impersonate an admin user in the domain, which you could do by adding:
, User = "admin#example.com"
- after the Scopes array passed to ServiceAccountCredential.Initializer.
This adds the email address of a user to the JWT request to retrieve an access token for that user (by adding the sub field to the claim-set of the JWT assertion).
To address case 3
Replace the "mycompany.com" on the line:
listReq.Domain = "mycompany.com";
- with a domain that is associated with the customer, or instead, remove that line and add:
listReq.Customer = "my_customer";
(literally my_customer - see users.list query-parameters)
- Which will list users on all domains associated with the customer (Google Workspace and Cloud Identity customers can have many secondary domains).

How to authorise a service account to access the Google Admin API

We are trying to create an integration with the Google Admin SDK in order to be able to retrieve, update and create accounts within our domain. However, we keep receiving a 403 error indicating that we are not authorized to access the resource/api.
We are using the credentials obtained from a service account which has Domain-wide Delegation of Authority enabled and the following two scopes:
https://www.googleapis.com/auth/admin.directory.user.readonly, https://www.googleapis.com/auth/admin.directory.user. We are generating the JWT (which also includes these two scopes) and then sending a request to https://www.googleapis.com/oauth2/v4/token to retrieve the access token.
We are then using the access token to send a request to https://www.googleapis.com/admin/directory/v1/users?domain=XXXX.com. We are including the access token as a Bearer token, part of the headers.
In the response we are getting the following message:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "forbidden",
"message": "Not Authorized to access this resource/api"
}
],
"code": 403,
"message": "Not Authorized to access this resource/api"
}
}
Is it possible to clarify what are we doing incorrectly?
The problem was that the JWT must include the sub field: The email address of the user for which the application is requesting delegated access.
In order for this to work you must set up domain wide delegation by doing this your service account will then have access to the data in question.
Locate the newly-created service account in the table. Under Actions, click more_vert then Edit.
In the service account details, click expand_more Show domain-wide delegation, then ensure the Enable G Suite Domain-wide Delegation checkbox is checked.
If you haven't yet configured your app's OAuth consent screen, you must do so before you can enable domain-wide delegation. Follow the on-screen instructions to configure the OAuth consent screen, then repeat the above steps and re-check the checkbox.
Click Save to update the service account, and return to the table of service accounts. A new column, Domain-wide delegation, can be seen. Click View Client ID, to obtain and make a note of the client ID.

How to debug Authentication configuration?

We are experiencing problems with Authentication of Service Accounts for domain-wide delegation.
The main problem is it's hard to investigate and debug the auth configuration so
we would like to ask for some tips how to debug the configuration.
Or maybe we are missing some configuration options and you can point us to them.
Our process is:
Create SA(=Service Account) with enabled domain-wide delegation.
Authenticate SA in GSuite admin console(https://support.google.com/a/answer/162106?hl=en).
use client_id from the credentials file. (now email)
scopes are comma-separated without spaces between.
Ensure the "Security > API Reference > API Reference -> 'Enable API Access'" is checked.
For some GSuite domains this is working configuration, but we got some domains where this configuration results in:
google.auth.exceptions.RefreshError: ('unauthorized_client: Client is unauthorized to retrieve access tokens using this method.', '{\n "error": "unauthorized_client",\n "error_description": "Client is unauthorized to retrieve access tokens using this method."\n}')
In our understanding, this is the error saying the client_id and scopes were not added to the "Manage API client access" page. (=List of authenticated clients)
We really ensured that the GSuite domain we are requesting has the proper client_id and scopes added in the list of authenticated clients + has the 'Enabled API Access'.
We even created Shared Desktop with them and did it by ourselves to be fully sure of it.
But the error still persists.
However, we are not able to replicate this problem on our test GSuite domain.
We tried couple of options using same SA as the client:
The impersonated account hasn't permissions to access the resource.
This result in:
googleapiclient.errors.HttpError: https://www.googleapis.com/admin/directory/v1/users?customer=my_customer&alt=json returned "Not Authorized to access this resource/api">
The scopes are just partial:
google.auth.exceptions.RefreshError: ('access_denied: Requested client not authorized.', '{\n "error": "access_denied",\n "error_description": "Requested client not authorized."\n}')
The 'Enabled API Access' is not checked.
googleapiclient.errors.HttpError: https://www.googleapis.com/admin/directory/v1/users?customer=my_customer&alt=json returned "Domain cannot use apis.">
The error we are receiving from the client("Client is unauthorized to retrieve access tokens using this method."), we are able to replicate only if the client_id is not in the list of authenticated clients at all.
But we are sure, the problematic GSuite domains have the SA authenticated in "Manage API client access" page.
We are using these scopes: https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/gmail.readonly,https://www.googleapis.com/auth/plus.login,https://www.googleapis.com/auth/calendar.readonly,https://www.googleapis.com/auth/contacts.readonly, https://www.googleapis.com/auth/admin.directory.user.readonly
Do you have any ideas how to debug/solve this issue?
Here is what you need to do. Double check each step. If in doubt, start over.
Enable "Admin SDK API. This is enabled on a per project basis.
Create a service account. Do not add or remove any privileges. Don't change the service account in any way. If you do you will get an error that you are not authorized.
Enable Domain-wide Delegation on the service account.
Follow this document to delegate domain-wide authority to your service account:
Delegate domain-wide authority to your service account
When creating the service account credentials (from the downloaded Json) you will need the following scopes for full G Suite management:
"https://www.googleapis.com/auth/admin.directory.group",
"https://www.googleapis.com/auth/admin.directory.user"
Impersonate a user account which creates new credentials. The user account needs to be a G Suite superadmin. This account must have logged into G Suite at least once and accepted the Terms of Service.
Create your client using the credentials from step #5.
Working Python Example:
from googleapiclient.discovery import build
from google.oauth2 import service_account
# This is the service account credentials file
credentials_file = 'google-directory-api.json'
# In this example I only need to send email
credentials = service_account.Credentials.from_service_account_file(
credentials_file,
scopes=['https://www.googleapis.com/auth/gmail.send'])
# This user is a G Suite superadmin
impersonate = 'username#example.com'
credentials = credentials.with_subject(impersonate)
service = build('gmail', 'v1', credentials=credentials)
I think we are going to need to take this in stages. Lets start with your first error and see if that fixes your issue.
Background info
There are several types of clients that you can create in Google developer console. Here are the top three.
Browser client: Used for web applications
Native client (other): used for installed desktop applications
Service account: used for server to server communication.
The clients are different the client.json file you download is different and the code used to authenticated to the authentication server is also different.
Error 1: code / client missmatch
unauthorized_client: Client is unauthorized to retrieve access tokens using this method.
Can mean one of two things. Either you have created a service account client and you are not using service account code to authenticate or you are are using the code to authenticate with a service account but you have not created a service account client. You haven't posted what language you are using or any code so I cant tell you if the code you are using is intended to be used with a service account or not. Your going to have to look into this one a bit.
Check in developer console make sure your client is like this If it is check your code. If it isnt then create a real service account client and try again.

Creating email drafts using Gmail API on Ruby (google-api-ruby-client 0.9)

I am currently trying out Ruby and the Google API for Ruby and I am having difficulties accessing my Gmail account and creating drafts with it (via create_user_draft) using a Service Account. I have successfully authenticated my Service Account with the API (Access Tokens are being generated).
I can use it with the Google::Apis::DriveV2::DriveService::list_files but not on GmailV1 methods.
I use this code to authorise the service account and the scope https://www.googleapis.com/auth/gmail.compose
Authorisation
def authorise
#jsonKeyIo = self.loadCredentialsFile
gAuthDefaultCreds = ##gAuthDefaultCreds
serviceAccountCredentials = gAuthDefaultCreds.make_creds(
{json_key_io: #jsonKeyIo, scope: #scope})
#service.authorization = serviceAccountCredentials
#service.authorization.fetch_access_token!
end
It generates an access token with this format:
{"access_token"=>"ya29.access_token_codes_here", "token_type"=>"Bearer", "expires_in"=>3600}
Draft creator snippet
##gmail = Google::Apis::GmailV1
##service = ##gmail::GmailService.new
def createDraft(draftTitle, draftMessage)
draft = ##gmail::Draft.new
draft.message = draftMessage
#service.create_user_draft('my.email#gmail.com', draft)
end
It throws a failedPrecondition: Bad Request (Google::Apis::ClientError) with the above code but when I added options: {authorization: #accessToken } as a third parameter of create_user_draft, the exception becomes Unauthorized (Google::Apis::AuthorizationError).
Can you help me go to the right path? I find the API documentation, on the Google API sites and on the source code itself, lackluster.
UPDATE
I have read here that in order for Service Accounts to work on the Gmail API, a paid Google Apps account is required (normal #gmail.com accounts won't work) since on the Admin Console is where we should have to enable the scopes for our Service Accounts.
Currently trying out JWT Credentials login.

Gmail API with service account

Under mygmailaccount#gmail.com in the API console I created a project and I am successfully using a service account for the Analytics API and Search Console API services. I've now enabled the Gmail V1 API and ran this code:
gmail_scopes = [
"https://mail.google.com/",
"https://www.googleapis.com/auth/gmail.compose",
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.readonly"
]
gmail = Google::Apis::GmailV1::GmailService.new
gmail.authorization = Google::Auth.get_application_default(gmail_scopes)
This is the same code (with different scopes of course) that I use to authorize the Analytics and Search Console API.
However I get BAD REQUEST Failed Precondition errors when I run this:
gmail.get_user_profile("mygmailaccount#gmail.com")
Some googling tells me that is because the API is trying to access the non-existent inbox for the serviceaccount email address and that in the initiatlization process I need to include mygmailaccount#gmail.com so that the API service will use that inbox.
How do I do that?
I've tried the following '...' always being the email address
gmail.authorization = Google::Auth.get_application_default(gmail_scopes, username: '...')
gmail.authorization = Google::Auth.get_application_default(gmail_scopes, {username: '...'})
gmail.authorization.username = '...'
Service accounts cannot access #gmail.com mailboxes. You need to use an OAuth2 UserAuthorizer. See the Gmail API Ruby Quickstart guide for an example.

Resources