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?
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()