Search code examples
pythongoogle-cloud-platformgoogle-calendar-apigoogle-api-python-clientservice-accounts

Create Google Calendar invites using service account securely


I created a service account using my Enterprise Google Workspace account and I need to automate calendar invites creation using Python.

I have added the service account email into the calendar's shared people and when I try to get the calendar using service.calendarList().list().execute() it works fine.

However, if I try to send an invite it doesn't work, and I get the error below:

googleapiclient.errors.HttpError: <HttpError 403 when requesting https://www.googleapis.com/calendar/v3/calendars/xxxxxx%group.calendar.google.com/events?alt=json returned "You need to have writer access to this calendar.". Details: "[{'domain': 'calendar', 'reason': 'requiredAccessLevel', 'message': 'You need to have writer access to this calendar.'}]">

Looking at the docs I found that I need to delegate domain-wide authority to the service account for this to work, but my company isn't allowing this service account to have this type of access because of security issues.

So I wanted to know if there is any way that I could do this without delegating domain wide access to this service account ? Because delegating domain wide access gives me access to impersonated anyone in the domain, so it's a security issue. I want the service account to be able to impersonate just the parent google account from where it was created.

Below is the full code that I used to get the calendar and create the invite

from google.oauth2 import service_account
from googleapiclient.discovery import build


class GoogleCalendar:
    SCOPES = [
        "https://www.googleapis.com/auth/calendar",
        "https://www.googleapis.com/auth/calendar.events",
    ]

    def __init__(self, credentials, calendar_id) -> None:
        credentials = service_account.Credentials.from_service_account_file(
            credentials, scopes=self.SCOPES
        )
        self.service = build("calendar", "v3", credentials=credentials)
        self.id = calendar_id

    def get_calendar_list(self):
        return self.service.calendarList().list().execute()

    def add_calendar(self):
        entry = {"id": self.id}
        return self.service.calendarList().insert(body=entry).execute()

    def create_invite(self):
        event = {
            "summary": "Google I/O 2015",
            "location": "800 Howard St., San Francisco, CA 94103",
            "description": "A chance to hear more about Google's developer products.",
            "start": {
                "dateTime": "2023-09-16T09:00:00-07:00",
                "timeZone": "America/Los_Angeles",
            },
            "end": {
                "dateTime": "2023-09-16T17:00:00-07:00",
                "timeZone": "Indian/Mauritius",
            },
            "attendees": [{"email": "[email protected]"}],
        }

        event = self.service.events().insert(calendarId=self.id, body=event).execute()

work_cal_id = "[email protected]"

cal = GoogleCalendar(
credentials="work.json", 
calendar_id=work_cal_id
)

cal.add_calendar()

print(cal.get_calendar_list())

cal.create_invite()

Solution

  • AFAIK you have 2 options available to you. Google has removed several other options.

    1. Serviceaccount with delegation. You state that this is not an option for you, but technically it is.
    2. Give serviceaccount access to Calendar API for specific user, using OAuth 2.0.

    Option 1

    This is actually the best (and the "proper" way), but I can understand the security problems that this option entails, as delegation can be a very big security risk if the SA gets compromised.


    Option 2

    This is currently the only other option. See this article for OAuth 2.0 details.

    It's basically the implementation of giving an app access to Google API endpoints for a user.

    This picture does a nice recap:
    enter image description here

    The user in the picture above should be an existing user in your company that has edit access to the calendar in question. You might want to create a specific user for this (such as [email protected] or [email protected]), to not tie the invitations to a specific "real" user.

    Basically the only thing that changes compared to your existing code is the generation of the token/credentials. This article explains how to create the credentials object you need to authenticate the requests.

    Whether or not this works for you depends on the security requirements for your company, and the specific Workspace settings applied to the workspace account.

    The article linked above for OAuth 2.0 is a very good starting point, followed by the specific article for server-side apps.


    Please do take token expiration into account. Not using the refresh token for 6 months fe. will invalidate it...