Skip to content

These pages describe PrivacyFence 7.0.2. Download it · What changed

Google setup (Gmail, Drive, Calendar, Contacts, Tasks, Apps Script)

You need this page only to use your organization's own app instead of the built-in one.

An organization server and the Docker image offer Google with PrivacyFence's built-in app: nothing to register, and people sign in from PrivacyFence. The built-in app shows Google's "unverified app" screen and is capped at 100 users until Google verifies it, and Google's verification for the Gmail and Drive scopes includes an annual security assessment. A Google Workspace organization that wants neither limit registers its own app, as described here, and can mark that app (its own client ID) as trusted in the Workspace Admin console. Do not mark the built-in PrivacyFence app as trusted or pre-approved: that trust would extend to every PrivacyFence server and to anyone using the public app. To trust an app, register your own.

How the app reaches PrivacyFence depends on the deployment:

  • Organization server (and the Docker image): the app is a signed connector config uploaded on Admin → Services. Its OAuth client must be a Web application client.
  • Desktop install: the app is a google section in the organization config bundle. Its client must be a Desktop app client. Desktop installs have no built-in app; desktop mode is deprecated and goes away in PrivacyFence 8 (moving to organization mode).

Users then sign in to each Google connector from PrivacyFence — they never open the Google Cloud console.

What you need

  • A Google Cloud project you can administer (create one at console.cloud.google.com).
  • For Workspace accounts, ideally the right to create an Internal app, so no Google verification is needed (see Consent screen and verification).
  • Python 3, to run scripts/sign_connector_config.py (organization server) or scripts/build_org_bundle.py (desktop install), or a browser for the offline signing page.
  • Which deployment you are building for: desktop installs, an organization server, or both. They need different OAuth client types (step 4).

Register the app

1. Select the project

In the Google Cloud console, pick your project in the project selector (or New Project, give it a name such as privacyfence, and select it).

2. Enable the APIs

Open APIs & Services → Library and enable each API the connectors you plan to offer use:

API Used by
Gmail API Gmail
Google Drive API Drive, and Apps Script (to list your script projects)
Google Docs API Drive's document editing tools (drive_write_doc_content, drive_docs_*)
Google Sheets API Drive's spreadsheet tools (drive_sheets_*)
People API Contacts
Google Calendar API Calendar
Tasks API Tasks
Apps Script API Apps Script

The Docs and Sheets APIs need no scope of their own (they accept Drive's), but each must still be enabled here or those tools fail with accessNotConfigured.

Open Google Auth Platform (also reachable as APIs & Services → OAuth consent screen):

  1. Branding: app name (for example PrivacyFence), user support email and developer contact email.
  2. Audience: choose the user type.
  3. Internal (Google Workspace only): only accounts in your Workspace organization can sign in, and Google requires no verification, whatever the scopes. Use this when you can.
  4. External: any Google account. The app starts in Testing: only the accounts you add as test users (up to 100) can sign in, and Google expires their sign-ins after 7 days, so each user has to Reconnect… weekly. To lift both limits you publish the app, which requires Google's verification (below).
  5. Data access: you can add the scopes from the values table here; they are requested at sign-in either way, but verification reviews this list.

Restricted scopes. Google classifies gmail.modify, drive and drive.metadata.readonly as restricted; the others PrivacyFence requests are sensitive. An External app in production that requests restricted scopes must pass Google's verification, which includes an annual third-party security assessment. Unverified, it shows users an "unverified app" warning and is capped in users. An Internal app avoids all of this. If you offer only some Google connectors, you only request those connectors' scopes: Calendar, Contacts and Tasks use no restricted scope.

4. Create the OAuth client

Open Google Auth Platform → Clients (or APIs & Services → Credentials → + Create Credentials → OAuth client ID):

  • Desktop installs: application type Desktop app. No redirect URI is registered: a Desktop app client accepts a loopback redirect on any port, which is what PrivacyFence uses.
  • Organization server: application type Web application, with every org redirect URI from the values table under Authorized redirect URIs. Use a client dedicated to the connectors, separate from the one your server uses for sign-in (see org-mode-setup-guide.md). A Desktop app client is refused at sign-in with redirect_uri_mismatch.

The two types are not interchangeable: a Desktop app client cannot hold an HTTPS redirect URI, and a Web application client cannot accept a loopback redirect on a port picked at sign-in time. If you run both kinds of deployment, create one client of each type (in the same project is fine).

Download each client's JSON (client_secret_….json). Treat it as a secret.

Values

Item Value
Local redirect (desktop installs) http://localhost:<port>/, port picked by the operating system at each sign-in. Nothing to register — requires a Desktop app client.
Org redirects (organization server) Register all six on the Web application client, with <server> your server's public address (PRIVACYFENCE_ISSUER_URL):
https://<server>/oauth/callback/gmail
https://<server>/oauth/callback/drive
https://<server>/oauth/callback/calendar
https://<server>/oauth/callback/contacts
https://<server>/oauth/callback/tasks
https://<server>/oauth/callback/apps_script
Gmail scopes https://www.googleapis.com/auth/gmail.modify, https://www.googleapis.com/auth/gmail.settings.basic
Drive scope https://www.googleapis.com/auth/drive
Calendar scope https://www.googleapis.com/auth/calendar
Contacts scope https://www.googleapis.com/auth/contacts
Tasks scope https://www.googleapis.com/auth/tasks
Apps Script scopes https://www.googleapis.com/auth/script.projects, https://www.googleapis.com/auth/script.processes, https://www.googleapis.com/auth/drive.metadata.readonly
Connector config (organization server) sign_connector_config.py sign --provider google --client-id <client ID> --client-secret-file <file holding the client secret> …, or the same in the browser signing page
Bundle flag (desktop install) --google-client-secret <path to the downloaded JSON>

Each Google connector is a separate sign-in with only its own scopes. PrivacyFence never requests Admin SDK or Workspace-directory scopes from users; room booking uses a separately synced directory (below). Apps Script's drive.metadata.readonly scope reads only file names, IDs and timestamps, to list your script projects; the connector has no tool that runs a script.

Give PrivacyFence the app

Organization server: a signed connector config

On an organization server the app is a small signed text file. Take the client ID and client secret from the downloaded JSON, sign them with your organization's connector config key, and upload the file on Admin → Services (the Google row, Upload a connector config…).

python3 scripts/sign_connector_config.py keygen --out connector_signing_key.pem
python3 scripts/sign_connector_config.py sign --provider google --serial 1 \
  --client-id 1234567890-abc.apps.googleusercontent.com \
  --client-secret-file google_client_secret.txt \
  --key connector_signing_key.pem --output google.connector.txt

keygen is run once per organization and shows the key's fingerprint; the first upload asks you to check that fingerprint, and every later file must be signed with the same key and a higher serial. The key tool, the serial rule and the signing page that works offline in a browser are described in org-mode-setup-guide.md. Uploading asks for a passkey, and Remove our own app on the same row goes back to the built-in app; either change makes everyone who connected Google sign in again.

Desktop install: the bundle

scripts/build_org_bundle.py is in the PrivacyFence source repository and is attached to every stable GitHub Release. It needs only Python 3 (signing with --sign-key also needs pip install cryptography); no PrivacyFence install is required.

python3 scripts/build_org_bundle.py \
  --org-name "Your Company" \
  --google-client-secret /path/to/client_secret.json \
  -o org_bundle.json

The bundle carries credentials only. The settings that used to go into it (which services are offered, step-up, unattended sessions and the others) are on the Admin page, and the script refuses their old flags. Add --merge to add Google to an existing bundle without losing its other sections; the google section itself is replaced by the client you pass. Install the bundle on Admin → Organization configuration (connecting-a-service.md). Signing and the other flags are covered in org-mode-setup-guide.md.

Optional: room directory for calendar_list_rooms

Skip this if your organization does not book Workspace rooms; every other Calendar tool works without it.

calendar_list_rooms reads a room list (name, email, building, floor, capacity) that you upload on the Admin page, not Google live. scripts/sync_room_directory.py (from the same repository and release) writes it. It needs the Workspace-admin scope admin.directory.resource.calendar.readonly, so it uses a second Google Cloud project, keeping that scope off the client every user signs in with:

  1. Create a second project (for example privacyfence-room-sync) and enable only the Admin SDK API.
  2. Configure its consent screen as in step 3; only the administrators who run the sync need access.
  3. Create a Desktop app OAuth client and download its JSON. Never put it in a connector config or bundle.
  4. Run the sync signed in as an account with the Workspace Directory Reader role (or super admin):
pip install google-auth google-auth-oauthlib google-api-python-client
python3 scripts/sync_room_directory.py \
  --admin-client-secret /path/to/room_sync_client_secret.json \
  --output rooms.json

It writes rooms.json (the default name) readable only by you. --query filters which rooms are fetched; --token-file (default .room_sync_token.json) caches the sync's own sign-in between runs — keep it private. 5. Upload rooms.json on Admin → Features (the room directory). It asks for a passkey and shows how many rooms were stored and when. Re-run the sync and upload again whenever your rooms change.

Users connect

Users click Authenticate… (desktop install, after the bundle is installed) or Connect (organization server) for each Google connector they want — the shared flow is described in connecting-a-service.md. Google-specific points:

  • Each Google connector is its own sign-in; connecting Gmail does not connect Drive.
  • Leave every permission checkbox on Google's consent page ticked. Unticking one fails the sign-in (see below).
  • Apps Script also needs a per-user switch in Google: turn on Google Apps Script API at script.google.com/home/usersettings. It is off by default and nothing in the Cloud console turns it on; include it in your user instructions.

Troubleshooting

"Access blocked: … has not completed the Google verification process" — the app is External and in Testing, and the account is not a test user. Add it under Audience → Test users, or use an Internal app.

"This app isn't verified" — expected for the built-in app, and for any unverified External app. Test users can continue through Advanced → Go to PrivacyFence. See verification.

"Error 403: org_internal" — the app is Internal and the account is outside your Workspace organization.

Sign-ins stop working after a week — an External app in Testing: Google expires test-user sign-ins after 7 days. Users can Reconnect…; to stop it, use an Internal app or publish and verify the External one.

"Error 400: redirect_uri_mismatch" — the client type does not match the deployment. Desktop installs need a Desktop app client; an organization server needs a Web application client with all six https://<server>/oauth/callback/<service> URIs registered, spelled exactly as PrivacyFence builds them from the server's public address (the Apps Script one is apps_script, with an underscore). Check which client the connector config or installed bundle actually contains.

"authentication failed: Google OAuth exchange failed: the granted scopes are missing [...]" — a permission was unticked on Google's consent page. Reconnect… and leave every box ticked.

accessNotConfigured / "API has not been used in project … or it is disabled" — the API behind that tool is not enabled in the project (step 2). Enabling it takes effect within a few minutes; users do not need to reconnect.

Apps Script calls fail with 403 "User has not enabled the Apps Script API" — the user has not turned on the per-user switch at script.google.com/home/usersettings.

Scopes changed on the client, or a 403 on a tool that worked before — Reconnect… the connector so Google issues a token with the current scopes.

calendar_list_rooms returns nothing — no room directory has been uploaded yet. Run the room directory sync and upload rooms.json on the Admin page.

sync_room_directory.py fails with "Room directory listing requires Google Workspace admin access" — the account you signed in with lacks the Directory Reader role. Run it again as an account that has it.