Search code examples
pythongmail-api

How to access gmail data from a service account?


I would like to manage my filters with the Gmail API through a service account. I have my domain registered in GSuite and manage all the faily accounts there.

I wrote some code which allows me to access the calendars - it works fine across all calendars of my users.

After creating a credentials JSON file from the console (giving it ownership for the project), I replicated this working code for the gmail case:

import googleapiclient.discovery
from google.oauth2 import service_account as google_oauth2_service_account

secrets = {
    "type": "service_account",
    "project_id": "setfilter",
    "private_key_id": "3xxxb",
    "private_key": "-----BEGIN PRIVATE KEY-----xxxDpLU\n-----END PRIVATE KEY-----\n",
    "client_email": "[email protected]",
    "client_id": "1xxx2",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/setfilter%40setfilter.iam.gserviceaccount.com"
}
    
credentials = google_oauth2_service_account.Credentials.from_service_account_info(secrets, scopes=['https://mail.google.com/'], subject='MyAccountToAdministerTheConsole@mydomain')
service = googleapiclient.discovery.build('gmail', 'v1', credentials=credentials)
results = service.users().labels().list(userId='me').execute()
print(results)

Running this code yields the dreaded

Traceback (most recent call last):
  File "D:/Nextcloud/dev-perso/setfilter/setfilter.py", line 25, in <module>
    results = service.users().labels().list(userId='me').execute()
  File "C:\Python38\lib\site-packages\googleapiclient\_helpers.py", line 134, in positional_wrapper
    return wrapped(*args, **kwargs)
  File "C:\Python38\lib\site-packages\googleapiclient\http.py", line 892, in execute
    resp, content = _retry_request(
  File "C:\Python38\lib\site-packages\googleapiclient\http.py", line 177, in _retry_request
    resp, content = http.request(uri, method, *args, **kwargs)
  File "C:\Python38\lib\site-packages\google_auth_httplib2.py", line 189, in request
    self.credentials.before_request(
  File "C:\Python38\lib\site-packages\google\auth\credentials.py", line 133, in before_request
    self.refresh(request)
  File "C:\Python38\lib\site-packages\google\oauth2\service_account.py", line 359, in refresh
    access_token, expiry, _ = _client.jwt_grant(request, self._token_uri, assertion)
  File "C:\Python38\lib\site-packages\google\oauth2\_client.py", line 153, in jwt_grant
    response_data = _token_endpoint_request(request, token_uri, body)
  File "C:\Python38\lib\site-packages\google\oauth2\_client.py", line 124, in _token_endpoint_request
    _handle_error_response(response_body)
  File "C:\Python38\lib\site-packages\google\oauth2\_client.py", line 60, in _handle_error_response
    raise exceptions.RefreshError(error_details, response_body)
google.auth.exceptions.RefreshError: ('unauthorized_client: Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.', '{\n  "error": "unauthorized_client",\n  "error_description": "Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested."\n}')

Is this the correct approach? If so - which special scopes are required for Gmail? (and where to set them?)


Solution

  • Missing domain-wide access to scopes:

    You should set the appropriate scopes that the service account should have domain-wide access to in order to call Users.labels: list on behalf of other users: when you delegate domain-wide authority to a service account, you have to define which scopes the service account should have domain-wide access to.

    This is done on the Admin console, while managing domain-wide delegation. Follow the steps specified here and, when you get to the domain-wide delegation panel, add one or more of these scopes:

    https://mail.google.com/
    https://www.googleapis.com/auth/gmail.modify
    https://www.googleapis.com/auth/gmail.readonly
    https://www.googleapis.com/auth/gmail.labels
    https://www.googleapis.com/auth/gmail.metadata
    

    Note:

    • I assume that you have already granted domain-wide authority to the service account, since you are able to access other users' calendars, so you had already provided Calendar scopes but not the appropriate Gmail ones (please grant domain-wide authority if you haven't done so, following these steps).