Skip to main content

Google Calendar

Proxy for the Google Calendar v3 REST API. Dual auth by design — the typical production setup runs two managed resources at once:

  • calendar-user in idp_passthrough mode for human users who want an agent to read their schedule, find free slots, or update their own events.
  • calendar-rooms in google_sa mode, which uses a Workspace service account with domain-wide delegation. For each call it impersonates the calling user, so the user does not link a Google account, but the call still needs a user token. The user is the token's sub when that is an email, otherwise the user's email, and its domain must match the Workspace domain setup field. No setup field names a user to impersonate. A client_credentials call has no user and gets 502 Upstream auth failed: No user identity in context; cannot impersonate (GoogleServiceAccountProvider.java). The connector sets no Google scopes for this mode, so the gateway asks Google for drive.readonly unless the resource's upstream auth carries a scopes list.

google_sa set up from the Admin Portal fails in 0.65.0. Every call gets 502 Upstream auth failed: No Google SA key configured, because the form stores the secret reference under sa_key_env (ConnectorV1Adapter.java:316-320) and the gateway reads it only from service_account_key_env (GoogleServiceAccountProvider.java:187-196). After you create the resource, rename the key with the admin API:

NAME=calendar-rooms   # the managed resource's name
curl -s -H "X-Admin-API-Key: $PBAC_ADMIN_API_KEY" "$PBAC_AS_URL/admin/api/managed-resources/$NAME" \
| jq '.upstreamAuth.service_account_key_env = .upstreamAuth.sa_key_env | del(.upstreamAuth.sa_key_env)' \
| curl -s -X PUT -H "X-Admin-API-Key: $PBAC_ADMIN_API_KEY" -H "Content-Type: application/json" \
"$PBAC_AS_URL/admin/api/managed-resources/$NAME" -d @-

To ask Google for other scopes, append | .upstreamAuth.scopes = ["<scope URL>"] to the jq filter. We ran this on a test instance with a stub token endpoint standing in for Google. After the rename, the gateway sent a signed JWT-bearer assertion with the calling user as sub and drive.readonly, or the scopes we set, as scope, and forwarded the token it got back to the upstream. A user outside the Workspace domain got 502 Email domain '...' does not match configured domain. We have not run a google_sa call through to Google.

Overview​

Two operator settings shape what an agent can reach. Both apply to every Google Calendar resource on the instance, because both are keyed by connector, not by managed resource:

  • A write_blocked list on the general file-access control, for calendars an agent may read but not edit (shared team, executive). governed lists the connector's resource type.
  • room_only_mode: true, which refuses a call whose calendar_id argument isn't a Workspace resource calendar (*@resource.calendar.google.com). A call with no calendar_id argument, such as list_calendars, is not refused. Turned on, it applies to calendar-user as well as calendar-rooms. It constrains calls made with a Google Calendar token only (see the room_only_mode deny below).

Default policy​

The connector ships policy/calendar.rego (package pbac.connectors.identos.google_calendar). It has one rule, room_only_mode. It emits no require_authn_at obligation. Blocked and read-only calendar ids are operator config on the general file-access control (see below), not rules here.

room_only_mode deny​

deny contains msg if {
input.resource.type == "urn:connector:identos:google-calendar"
data[...].room_only_mode == true
cal := input.context.rs_context.tool_arguments.calendar_id
not endswith(cal, "@resource.calendar.google.com")
...
}

A booking-agent safety net. When enabled, every calendar_id argument must look like a Workspace resource calendar. The rule reads only that argument, so a call without one passes it. If the agent (or, more importantly, a prompt-injected response it's acting on) tries to touch a human calendar with a Google Calendar token, the request is denied.

The rule keys on input.resource.type (calendar.rego:36-37), so a token of another connector type that carries the same scope name is not covered. identos.ms-calendar also names its read scope calendar:read. In our run with room_only_mode on, a Google Calendar token got 403 on /calendar/v3/calendars/ceo@example.com/events, and a client_credentials token issued for the identos.ms-calendar type with calendar:read got 200 on the same Google Calendar resource, over REST and over MCP. See Check the scope and resource.

Supplying operator data​

room_only_mode is the connector's own setting — a suffix test, not an id set, so it stays connector rego:

KEY=pbac.operator.connectors.identos.google-calendar

curl -X PUT "http://localhost:8080/admin/api/policy-data/by-key?dataKey=$KEY" \
-H "X-Admin-API-Key: $PBAC_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg k "$KEY" --argjson s '{
"room_only_mode": true
}' \
'{dataKey:$k, payload:($s|tojson)}')"

File access control​

Blocking a specific calendar is operator config on the general file-access control, shared with the Google file connectors — not a rule in this connector. An identity source matches a calendar by its own id: ids in blocked are denied on reads and writes, ids in write_blocked are denied on writes only, so reads still work.

curl -X PUT "http://localhost:8080/admin/api/policy-data/by-key?dataKey=pbac.operator.controls.file_access" \
-H "X-Admin-API-Key: $PBAC_ADMIN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"dataKey": "pbac.operator.controls.file_access",
"payload": {
"sources": {
"calendars": {
"type": "identity",
"blocked": ["ceo@example.com"],
"write_blocked": ["team-alpha@example.com"]
}
},
"governed": {
"urn:connector:identos:google-calendar": ["calendars"]
}
}
}'

The call replaces the whole row, so keep the sources and governed entries of every other connector you govern in the payload.

The control reads the id from the calendar_id tool argument and applies to calendar:read, calendar:events:write and calendar:admin calls alike. Listing a connector under governed is what activates it; omit it and the control is inert. Full mechanics (pooling, deny-wins, fail-closed) live in the File Access Control guide.

Scope model​

ScopeUsed for
PBAC scopes (internal)calendar:read, calendar:events:write, calendar:adminRoute authorization
Upstream OAuth scopesopenid, userinfo.email, calendar.readonly, calendar.events, calendar.acls.readonlyGoogle-side grants the user consents to (idp_passthrough mode). google_sa mode sets none and gets drive.readonly

See Google Workspace: multi-connector setup for how calendar's scopes compose with drive and gmail on one Google IdP.

Manifest reference​

  • ID: identos.google-calendar
  • Version: 1.2.0
  • Resource type: urn:connector:identos:google-calendar

Supported auth modes​

TypeDetails
idp_passthroughrequires IdP google
google_sasetup fields: sa_key_env, domain

Setup fields​

IDLabelDefaultSecret?Notes
base_urlAPI base URLhttps://www.googleapis.comno—
upstream_auth.typeAuthenticationidp_passthroughnoidp_passthrough forwards each user's own Google OAuth token (recommended). google_sa uses a Workspace service account with domain-wide delegation — useful for shared / room calendars where no human caller is involved.
sa_key_envService account key—yesPick a secret containing the service-account JSON. Required only for the google_sa auth mode. / shown when upstream_auth.type == 'google_sa'
domainWorkspace domain—noplaceholder: example.com / Required only for the google_sa auth mode. / shown when upstream_auth.type == 'google_sa'

Scopes​

Scope
calendar:read
calendar:events:write
calendar:admin

Routes​

MethodPatternScopeResource template
GET/calendar/v3/users/me/calendarListcalendar:read—
GET/calendar/v3/calendars/{calendar_id}calendar:readcalendar://{{calendar_id}}
GET/calendar/v3/calendars/{calendar_id}/eventscalendar:readcalendar://{{calendar_id}}
GET/calendar/v3/calendars/{calendar_id}/events/{event_id}calendar:readcalendar://{{calendar_id}}/event/{{event_id}}
POST/calendar/v3/calendars/{calendar_id}/eventscalendar:events:writecalendar://{{calendar_id}}
PATCH/calendar/v3/calendars/{calendar_id}/events/{event_id}calendar:events:writecalendar://{{calendar_id}}/event/{{event_id}}
DELETE/calendar/v3/calendars/{calendar_id}/events/{event_id}calendar:events:writecalendar://{{calendar_id}}/event/{{event_id}}
POST/calendar/v3/calendars/{calendar_id}/events/quickAddcalendar:events:writecalendar://{{calendar_id}}
POST/calendar/v3/freeBusycalendar:read—
GET/calendar/v3/calendars/{calendar_id}/aclcalendar:admincalendar://{{calendar_id}}

MCP tools​

NameScopeDescription
list_calendarscalendar:readList every calendar the authenticated user has on their calendar list (primary + shared + subscribed).
get_calendarcalendar:readGet a calendar's metadata (summary, timezone, description).
list_eventscalendar:readList events on a calendar. Supports time filters (timeMin/timeMax), single-event expansion, and free-text search.
get_eventcalendar:readGet a single event by ID.
create_eventcalendar:events:writeCreate a new event on a calendar. Provide either the Google Calendar event body (full schema) or the convenience fields summary/start/end/attendees.
update_eventcalendar:events:writeUpdate an event (partial). Only supplied fields are modified.
delete_eventcalendar:events:writeDelete an event. Recurring events: pass sendUpdates=all to notify attendees.
quick_add_eventcalendar:events:writeCreate an event from natural-language text (e.g. "Lunch with Alice Thursday 1pm"). Uses Calendar's quickAdd parser.
query_free_busycalendar:readQuery free/busy information for a set of calendars over a time window. Returns only busy intervals — no event details.
list_aclcalendar:adminList the access-control rules on a calendar (who it's shared with and how).

Operator data schema​

Keys the operator can supply under data.pbac.operator.connectors["identos.google-calendar"].* — consumed by the connector's policy.

KeyTypeDescription
room_only_modebooleanWhen true, only calendars whose ID ends with @resource.calendar.google.com (Google Workspace resource/room calendars) are addressable. Useful for booking agents that should never touch human calendars.