Workspace app: Service account impersonation, then delegate to query specific user emails

Hello community.

I'm developing a Workspace app that after being installed for all users using an admin user, allows to impersonate a project service account using the OAuth2 client ID. With that service account impersonation, then I try to query user's emails using the Gmail API. I'm using the Google Python client to handle all the flow:

1. Collect the consent token with the OAuth2 client ID

2. Query all users using the Directory API

3. Impersonating a service account with a domain-wide delegation

4. Query all emails for each user

This is the overall flow

 

 

impersonated_creds = impersonated_credentials.Credentials(source_credentials=credentials, target_principal=SERVICE_ACCOUNT, target_scopes=IMPERSONATED_SCOPES, lifetime=3600)
service = build('gmail', 'v1', credentials=creds)
service.users().messages().list(userId="<any domain email>").execute()

 

 

The last line returns the following

 

 

googleapiclient.errors.HttpError: <HttpError 400 when requesting https://gmail.googleapis.com/gmail/v1/users/cloud-sec-av%40integrationstools.com/messages?alt=json returned "Precondition check failed.". Details: "[{'message': 'Precondition check failed.', 'domain': 'global', 'reason': 'failedPrecondition'}]">

 

 

I appreciate any hint you could give me in order to be able to impersonate/delegate and query emails for each user.

Thanks

Solved Solved
1 3 6,011
1 ACCEPTED SOLUTION

Wrong credentials.

It's a bit confusing since there are different meanings for impersonation in this context. From your code, you're impersonating a service account, not a user. Service accounts aren't valid gmail users and can't directly use the gmail API. Instead, you need to impersonate users. A service account is required to impersonate users, so you're at least part way there ๐Ÿ™‚

If you can download the service account key, that's the easiest way to do so. See https://github.com/googleapis/google-auth-library-python/blob/main/google/oauth2/service_account.py#...for some snippets on how to do user impersonation. 

If you can't download the key and have to impersonate the service account first, then that requires a little extra work. Unfortunately the client libraries don't handle that flow as easily. In that case, you'll need to implement the flow yourself. The protocol can be found at https://developers.google.com/identity/protocols/oauth2/service-account#httprest

While that looks long, most of it is about constructing and signing the JWT which you won't do that way. Use the signJWT method in the IAM API instead. Once you have the signed JWT, follow the steps to exchange it for an access token.


View solution in original post

3 REPLIES 3

Running others tests setting the delegate list

impersonated_creds = impersonated_credentials.Credentials(source_credentials=credentials, target_principal=SERVICE_ACCOUNT, target_scopes=IMPERSONATED_SCOPES, lifetime=3600, delegates=["EMAIL"])
service = build('admin', 'directory_v1', credentials=impersonated_creds)
service.users().messages().list(userId="me").execute()

I got this result

google.auth.exceptions.RefreshError: ('Unable to acquire impersonated credentials', '{\n  "error": {\n    "code": 404,\n    "message": "Not found; Gaia id not found for email EMAIL",\n    "status": "NOT_FOUND"\n  }\n}\n')

 

Wrong credentials.

It's a bit confusing since there are different meanings for impersonation in this context. From your code, you're impersonating a service account, not a user. Service accounts aren't valid gmail users and can't directly use the gmail API. Instead, you need to impersonate users. A service account is required to impersonate users, so you're at least part way there ๐Ÿ™‚

If you can download the service account key, that's the easiest way to do so. See https://github.com/googleapis/google-auth-library-python/blob/main/google/oauth2/service_account.py#...for some snippets on how to do user impersonation. 

If you can't download the key and have to impersonate the service account first, then that requires a little extra work. Unfortunately the client libraries don't handle that flow as easily. In that case, you'll need to implement the flow yourself. The protocol can be found at https://developers.google.com/identity/protocols/oauth2/service-account#httprest

While that looks long, most of it is about constructing and signing the JWT which you won't do that way. Use the signJWT method in the IAM API instead. Once you have the signed JWT, follow the steps to exchange it for an access token.


Thanks, @sbazyl! I followed your suggestion with the service account and it worked like a charm.

I'm going to explore the impersonate first, delegate latter approach for academic purposes. I was exploring that before your answer. I got a dead end with the current client libraries.

Thanks again!

Top Solution Authors