如何安全使用Service Account创建Google Calendar邀请(无需全域权限)
使用企业Google Workspace账号创建了Service Account,需要通过Python自动化创建日历邀请。已将服务账号邮箱添加到日历共享人员列表中,调用service.calendarList().list().execute()可正常获取日历,但创建邀请时失败,报错:
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.'}]"
查阅文档得知需为服务账号配置全域权限,但公司因安全问题不允许此权限。想咨询是否无需配置全域权限,仅让服务账号模拟创建它的父账号即可实现功能?
以下是使用的完整代码:
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": "myemail@domain.com"}], } event = self.service.events().insert(calendarId=self.id, body=event).execute() work_cal_id = "xxxxx@group.calendar.google.com" cal = GoogleCalendar( credentials="work.json", calendar_id=work_cal_id ) cal.add_calendar() print(cal.get_calendar_list()) cal.create_invite()
可以通过让服务账号**模拟创建它的父账号(你的企业Google Workspace账号)**实现功能,无需配置全域权限,只需管理员为服务账号配置针对特定用户的域范围委派权限即可,具体操作如下:
管理员配置域范围委派
- 登录Google Workspace管理员控制台,定位到目标服务账号
- 启用「域范围委派」,并添加所需的OAuth2范围:
https://www.googleapis.com/auth/calendarhttps://www.googleapis.com/auth/calendar.events
- 此配置仅允许服务账号模拟域内用户,只需指定它模拟你自己的企业账号即可,无需授予全域权限,安全可控。
修改Python代码
在创建服务账号凭证时,添加subject参数指定要模拟的父账号邮箱(即拥有该日历写入权限的你的企业账号):
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, impersonate_email) -> None: # 添加subject参数,指定要模拟的用户邮箱 credentials = service_account.Credentials.from_service_account_file( credentials, scopes=self.SCOPES, subject=impersonate_email ) 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": "myemail@domain.com"}], } event = self.service.events().insert(calendarId=self.id, body=event).execute() # 替换为你的实际信息 work_cal_id = "xxxxx@group.calendar.google.com" your_work_email = "your-email@domain.com" # 你的企业账号邮箱 cal = GoogleCalendar( credentials="work.json", calendar_id=work_cal_id, impersonate_email=your_work_email ) cal.add_calendar() print(cal.get_calendar_list()) cal.create_invite()
- 权限验证
- 确保你的父账号(被模拟的账号)对目标日历拥有写入权限
- 服务账号通过模拟该账号,将继承其日历操作权限,从而成功创建邀请
这种方式的核心是服务账号以你的身份操作日历,而非自身身份,既避免了给服务账号单独分配日历权限的问题,也规避了全域权限的安全风险。
内容的提问来源于stack exchange,提问作者Mervin Hemaraju

