Skip to main content

Google Drive

Proxy for the Google Drive v3 REST API. Ships with a tenant-filled deny template — the connector author knows the shape of Drive, the operator fills in the specifics (which tags are sensitive; folder/label-based governance is configured through the shared file access control below).

Overview​

Drive is the first dual-auth connector. Pick:

  • idp_passthrough when the caller is a real human and the agent should act as them — reads and writes apply to the user's own Drive, respecting every share / membership the user already has. Requires the operator to register Google as an IdP on the AS.
  • google_sa 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=drive-sa   # 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.

A single managed resource binds to one auth mode. To serve both shapes of caller, create two managed resources (e.g. drive-user and drive-sa). The policy can key off input.context.rs_context.resource_name to apply different obligations.

Default policy​

This connector ships no Rego package. policy/drive.rego, its test, and policy/data.json are gone, and the manifest no longer declares a policy block. Everything Drive enforces comes from shared controls — see File access control below.

What the files held when they were removed (0.57.0):

  • drive.rego: nothing. Comments only. Label filtering, location filtering and create-time default labelling had all migrated to pbac.controls.file_access, and the rules went with them. OPA reported the package as {}.
  • data.json: unreachable placeholder defaults (default_page_size, known_sensitive_tag_hints). Nothing read those keys, and nothing could have by the obvious path: BundleService serves a connector's data as a FLAT key with dots and the manifest's hyphenated id — data["pbac.connectors.identos.google-drive"] — while the package path is underscored (data.pbac.connectors.identos.google_drive). Two different places in the data tree.

Gone before that, and not replaced: this section used to document a require_authn_at subject obligation — a caller on a non-Google IdP hitting an idp_passthrough Drive resource was told to log in at Google first. That rule was not in drive.rego when it was deleted, so the behaviour had already lapsed; the README simply kept describing it. Sibling connectors (identos.google-gmail, identos.google-directory, identos.confluence, identos.jira-user) still ship theirs, and none of them fires: each reads input.subject.idp_provider, which the AS does not set. The gateway's own 401 idp_link_required is what asks a caller to link Google. If Drive needs the obligation back, it belongs in a shared control rather than a per-connector package.

Read-only files by tag — removed, not moved​

A sensitive_tags deny rule used to be documented here: files carrying an operator-flagged custom-property tag were read-only. It never worked. It gated on input.action.method and input.resource.tags, neither of which the gateway sends to the PDP, so it was inert on every instance that ever ran it, while the connector's operator_data_schema advertised sensitive_tags as configurable.

The rule and the setting are both gone. Nothing enforces less as a result — nothing was enforcing. The capability was not rebuilt: unlike folder and label governance below, tag-valued read-only had no home in the general file-access control (Drive custom properties are a different API surface from Drive Labels), and it was invented rather than asked for.

Folder-based blocking used to live here too, as a blocked_folders deny rule keyed on input.resource.parent_folder_id. That rule has been retired — folder governance is now a location source on the shared file access control (see below), which also closes the direct-fetch gap the old rule had (it only ever covered Drive's own routes).

Operator data​

This connector declares no operator settings of its own. Governance comes from the shared controls: file access below, and the general controls in pbac.controls.* (read-only mode, capability opt-in, IdP alignment).

File access control​

list_files/get_file results are governed by the shared file access control — one general mechanism (typed label and location sources, one pooled predicate) used by every governed Google file connector (Drive, Docs, Sheets, Slides), plus auto-labelling of files an agent creates so the same gate governs them. All governing sources pool into a single OR-of-approvals, deny-wins decision: a file is returnable if it carries an approved label or sits in an approved folder, and withheld if it carries a restricted label or lives in a blocked folder. Full design — the pooled predicate, fail-closed rules, and the two enforcement points — lives in the File Access Control guide; for getting label/folder/drive IDs and the required OAuth scopes, see the Google file access setup guide.

This is response-side suppression on the inline path: the file is fetched from Drive and then pruned/denied before it reaches the agent — not a "never fetched" control.

Label and folder/location governance (shared control)​

Neither label nor folder/location governance is configured under this connector's own operator key anymore. The old Drive-only label_filter, location_filter, and blocked_folders keys have all been removed — any config still setting them is silently ignored, so it gets zero enforcement. Both now go through the single shared control, pbac.operator.controls.file_access, the same mechanism Docs, Sheets, and Slides use. Drive's projection runs in inline mode — label/parent/drive metadata comes back on the list_files/get_file response itself, so the gateway filters/denies from that response directly instead of a separate resolve round trip — but the operator-facing configuration is identical to every other governed connector:

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": {
"drive-labels": {
"type": "label",
"required": ["<approved-label-id>"],
"blocked": ["<restricted-label-id>"]
},
"drive-location": {
"type": "location",
"required": ["<approved-folder-id>"],
"blocked": ["<hr-folder-id>"]
}
},
"governed": {
"urn:connector:identos:google-drive": ["drive-labels", "drive-location"]
}
}
}'

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

  1. Under sources, declare a policy per source: type (label or location), required (allowlist, any-of) and blocked (denylist, always wins). Approving a folder — making the folder itself and its direct contents returnable regardless of their own labels — is a location source with the folder ID in required; blocking a folder ID hides the folder itself and its direct contents (deny-wins). A location source matches an item by its own ID as well as its parent container(s), which is what lets the folder itself surface (or be hidden). Google Drive does not allow labels on folders, so folder governance is always by ID via a location source, never by a folder label (and there is no label inheritance).
  2. Under governed, list (as an array) which sources govern this connector's resource type. All listed sources pool into one OR-of-approvals, deny-wins decision — a file is returnable if it matches any source's required, and withheld if it matches any source's blocked. The deciding rule (blocked_match / not_in_required) shows up in the audit reason. Omitting the connector leaves it ungoverned (inert; no check).
  3. Folders and shared drives on a location source are configured by ID, not path — Drive has no path metadata. Location matching is own-ID + direct-parent + shared-drive only (one level, no tree walking); for a whole-subtree boundary (e.g. an entire department), put the tree in a dedicated shared drive and match on the drive's ID instead of walking folders.

Drive Labels were chosen over appProperties because they're visible/editable in the Drive UI and admin-governed — appProperties is invisible and freely rewritable by anything with write access, so it isn't a real control.

Folder viewability (allow_containers)​

A Drive folder cannot carry a label, so the moment a label source has a required allowlist every folder is pruned out of list_files — an agent cannot browse or search because it never sees a folder to look inside. Naming each folder in a location source's required fixes that folder; allow_containers fixes it in general:

{
"sources": { "drive-labels": { "type": "label", "required": ["<approved-label-id>"] } },
"governed": { "urn:connector:identos:google-drive": ["drive-labels"] },
"allow_containers": { "urn:connector:identos:google-drive": true }
}

Folders become returnable; the files inside them still need their own approval. A folder ID on any source's blocked list stays hidden, together with its direct contents — the exemption answers the not_in_required objection only, never blocked_match. Reads only (it rides on the inline filter), and off by default: folder names are metadata an operator may not want enumerated.

The connector half is file_governance.container in this manifest — mimeType = application/vnd.google-apps.folder. mimeType must stay in both read routes' pinned query_inject.fields, or the test can never match and folders stay hidden while the config reads as enabled; ConnectorLinter enforces that coupling.

Write-side default labelling (now part of the shared control)​

Auto-labelling agent-created files is now a feature of the shared file_access control, not a per-connector key: give a label source a default and newly-created files are stamped with it (so an agent can read back what it makes). It's connector-agnostic — the same config governs Drive, Docs, Sheets, and Slides — with the write performed by the google-drive-modify-labels applier (shared-drive-aware). Configure it on the label source under pbac.operator.controls.file_access:

"sources": {
"org-labels": {
"type": "label", "required": ["<approved-label-id>"], "blocked": [],
"default": { "label_id": "<approved-label-id>", "field_id": "<field-id>", "value": "general" }
}
}

See the File Access Control guide and the design spec for the general model. The per-connector create_default_label key and the connector rego rule that read it are gone: that rule emitted a flat {label_id, field_id, value} payload, which ApplyLabelAction stopped understanding when it became a dispatcher keyed on applier — so it had been silently skipping. Move the same payload to the label source's default, as above.

Gating note: the create-time apply_label is gated on the route's scope (input.context.rs_context.scopes, drive:files:write), not input.action.method — which is never populated for connector policy on the real gateway path.

Scope model​

Two vocabularies are at play:

ScopeUsed for
PBAC scopes (internal)drive:files:read, drive:files:write, drive:adminRoute authorization inside PBAC — the PDP decides which MCP tools the caller can invoke
Upstream OAuth scopeshttps://www.googleapis.com/auth/drive.readonly, https://www.googleapis.com/auth/drive.file, https://www.googleapis.com/auth/drive.labels.readonly, https://www.googleapis.com/auth/drive.labelsWhat the IdP (Google) must grant in the user's access token for idp_passthrough to work. google_sa mode sets none and gets drive.readonly

drive.labels (write) is required for the apply_label follow-up to call files.modifyLabels. drive.labels.readonly is required for labelInfo to appear on list_files/get_file responses so the shared file access control's filter_file_access obligation can enforce a label source; drive.readonly already covers the parents/driveId fields a location source reads (the standalone drive.metadata.readonly scope is only needed when no Drive connector is installed and the control has to resolve those fields via a direct API call instead — see the Google file access setup guide).

When this connector is provisioned in idp_passthrough mode, the gateway automatically merges its declared upstream scopes into the Google IdP's scopes list. Users who authenticated before the connector was installed get 401 with "error": "idp_link_required" and a link_url until they open the link and consent at Google again.

See Google Workspace: multi-connector setup for how this works across drive + gmail + calendar sharing a single Google IdP.

Manifest reference​

  • ID: identos.google-drive
  • Version: 1.5.1
  • Resource type: urn:connector:identos:google-drive

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 impersonating a fixed user.
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
drive:files:read
drive:files:write
drive:admin

Routes​

MethodPatternScopeResource template
GET/drive/v3/filesdrive:files:read—
GET/drive/v3/files/{file_id}drive:files:readdrive://{{file_id}}
GET/drive/v3/files/{file_id}/permissionsdrive:admindrive://{{file_id}}
POST/drive/v3/filesdrive:files:write—
PATCH/drive/v3/files/{file_id}drive:files:writedrive://{{file_id}}
DELETE/drive/v3/files/{file_id}drive:admindrive://{{file_id}}

MCP tools​

NameScopeDescription
list_filesdrive:files:readList files in Google Drive. Supports standard Drive query syntax for filtering (e.g. "name contains 'report'" or "mimeType = 'application/pdf'").
get_filedrive:files:readGet metadata for a single Drive file by its file ID.
list_file_permissionsdrive:adminList the sharing permissions on a Drive file.
create_file_metadatadrive:files:writeCreate a Drive file's metadata record (no binary content) — a folder, placeholder doc, or shortcut. Pass mimeType application/vnd.google-apps.folder for a folder or application/vnd.google-apps.document for an empty Doc, and parents to create it inside a folder. For binary uploads, use the Drive web UI or a dedicated upload client.
update_file_metadatadrive:files:writeUpdate a Drive file's metadata (name, description, starred flag) and/or move it between folders. To move a file, pass addParents (folder IDs to add) and removeParents (folder IDs to remove).
delete_filedrive:adminPermanently delete a Drive file. Irreversible — prefer trashing via update_file_metadata when possible.