Search code examples
pythongmail-apigoogle-workspace

Set delegation in Gmail via API with Python


I'm trying to get a Python script to run which is setting delegations in Gmail.

As a basic starting point, I used the "Python quickstart" from Google.

from __future__ import print_function

import os.path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/gmail.settings.sharing']


def main():
    """Shows basic usage of the Docs API.
    Prints the title of a sample document.
    """

    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    try:

        # Create a Gmail service client
        service = build('gmail', 'v1', credentials=creds)

        # Set delegation for a specific sender
        sender_email = 'sender@domain.com'
        delegate_email = 'delegate@domain.com'

        settings = {
            'delegateEmail': delegate_email
        }

        result = service.users().settings().sendAs().update(userId='me', sendAsEmail=sender_email, body=settings).execute()
        print('Delegation set successfully!')
    except HttpError as err:
        print(err)


if __name__ == '__main__':
    main()

I created a project and the authorization credentials for a desktop application.

When I started the script for the first time, I got the following error message:

"Access restricted to service accounts that have been delegated domain-wide authority". Details: "[{'message': 'Access restricted to service accounts that have been delegated domain-wide authority', 'domain': 'global', 'reason': 'forbidden'}]"

So I checked the Gmail API specification and I saw that for this scope a Service Account was needed. I created a Service Account and added the scope into Google Workspace.

After trying the script with the newly downloaded Jason for the Service Account, the following error pops up:

Traceback (most recent call last):
  File "D:\quickstart.py", line 58, in <module>
    main()
  File "D:\quickstart.py", line 31, in main
    flow = InstalledAppFlow.from_client_secrets_file(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\PC\AppData\Local\Programs\Python\Python311\Lib\site-packages\google_auth_oauthlib\flow.py", line 201, in from_client_secrets_file
    return cls.from_client_config(client_config, scopes=scopes, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\PC\AppData\Local\Programs\Python\Python311\Lib\site-packages\google_auth_oauthlib\flow.py", line 159, in from_client_config
    raise ValueError("Client secrets must be for a web or installed app.")
ValueError: Client secrets must be for a web or installed app.

What is meant with "installed app"? The OAuth client for the project I created is set to "desktop app".

Is something wrong with the code, or is it the Service Account setup?


Solution

  • When you use delegate user, you need to use impersonation (delegated domain-wide authority) as mention in this documentation.

    You need to do some changes to your code, to use the services account to authenticate. You can see the example in Google Documentation here.

    This is a working example code:

    Note: In this sample, user1 is grating access to user2 to access to the inbox.

    from __future__ import print_function
    
    import os.path
    
    from google.auth.transport.requests import Request
    from google.oauth2.credentials import Credentials
    from google_auth_oauthlib.flow import InstalledAppFlow
    from googleapiclient.discovery import build
    from googleapiclient.errors import HttpError
    from google.oauth2 import service_account
    
    
    
    SCOPES = ['https://www.googleapis.com/auth/gmail.settings.sharing']
    
    
    def main():
    
        # Location and name of the JSON file / Key of the services account. 
    
        SERVICE_ACCOUNT_FILE = 'ServiceAccountCredentials.json'
        credentials = service_account.Credentials.from_service_account_file(
            SERVICE_ACCOUNT_FILE, scopes=SCOPES)
        
        # User that will be impersonated
        # Email address of who will grant permission to access to the inbox
        
        delegated_credentials = credentials.with_subject('user1@domain.com')
    
    
        try:
            service = build('gmail', 'v1', credentials=delegated_credentials)
    
            # Email address of how is getting permission to manage the inbox 
    
            body = {
                'delegateEmail': 'user2@domain.com',
                'verificationStatus': 'accepted'
                }
            request = service.users().settings().delegates().create(userId='me', body=body).execute()
            print(request)
           
    
        except HttpError as error:
            # TODO(developer) - Handle errors from gmail API.
            print(f'An error occurred: {error}')
    
    
    if __name__ == '__main__':
        main()