# confluence For pages and blog posts toolpass asks Confluence directly: `POST /wiki/rest/api/content/{id}/permission/check` with the user's `accountId` and the operation (`read`, `update`, `delete`), which weighs site access, space permissions and content restrictions. Confluence has no such call for space-level operations, so for `space:` resources toolpass reads the space's permission list (`GET /wiki/api/v2/spaces/{id}/permissions`) and, when a group holds the permission, the user's groups (`GET /wiki/rest/api/user/memberof`), and matches them itself. A space administrator (`administer`/`space`) holds every space operation, and a grant to a principal toolpass cannot resolve (anonymous, a role, licensed users) answers unknown rather than deny. Confluence's user search has no email field, so the email is resolved through a **jira** connection on the same Atlassian site. Nothing in Confluence is changed. Cloud only; Data Center is out of scope. ## Credential Checking another user's permissions needs **Confluence Administrator** on toolpass's account (a plain user gets HTTP 403 on the permission check, answered as `credential_rejected`). The space permission and group reads need view access to the space and Browse users. As with jira, prefer a dedicated service account with a scoped API token (`auth_mode: scoped_token`) limited to read scopes such as `read:confluence-content.all`, `read:confluence-space.summary`, `read:confluence-user` and `read:confluence-groups`. The auth modes are the jira integration's: `basic` (`username` + API token against `{url}/wiki/...`), `scoped_token` (Bearer against `https://api.atlassian.com/ex/confluence/{cloudId}/wiki/...`) and `oauth_client` (client credentials, then the same gateway). The cloud id is discovered once from `GET {url}/_edge/tenant_info`. ## Connection ```yaml - id: confluence-acme integration: confluence url: https://acme.atlassian.net auth_mode: basic username: toolpass@acme.com credential: env:CONFLUENCE_TOKEN identity_connection: jira-acme # jira connection on the same site ``` | Key | Meaning | |---|---| | `url` | site URL (without `/wiki`) | | `auth_mode` | `basic`, `scoped_token` or `oauth_client` | | `username` | email of the bot account; required for `basic` | | `credential` | API token or OAuth client secret; `env:` or `file:` | | `client_id` | OAuth 2.0 client id; required for `oauth_client` | | `identity_connection` | id of a jira connection on the same Atlassian site, used for the email lookup. Without it every check answers unknown (`unsupported`, "Confluence cannot look up users by email") | The key `strict_email_match` no longer exists here or in jira (see the jira page for why); the loader rejects it (`integration confluence does not accept key "strict_email_match"`), so remove the line from any existing connection. The email lookup's answers (`user_not_found`, `user_ambiguous`, the hidden-email and too-many-candidates `unsupported`, `invalid_request` for an empty email) are the jira connection's. ## Resources | Resource | Meaning | |---|---| | `page:` | a page by numeric content id | | `blogpost:` | a blog post by numeric content id | | `space:` | a space by key (`^[A-Za-z0-9~_-]{1,255}$`) | ## Actions Native, through the content permission check: | Action | Resource | Operation | |---|---|---| | `page.read`, `page.update`, `page.delete` | `page:` | `read`, `update`, `delete` | | `blogpost.read`, `blogpost.update`, `blogpost.delete` | `blogpost:` | `read`, `update`, `delete` | Evaluated from the space permission list, on `space:`: | Action | Space permission (operation key / target type) | |---|---| | `space.read` | `read` / `space` | | `page.create` | `create` / `page` | | `blogpost.create` | `create` / `blogpost` | | `comment.create` | `create` / `comment` | | `attachment.create` | `create` / `attachment` | | `space.export` | `export` / `space` | | `page.restrict` | `restrict_content` / `space` | | `space.admin` | `administer` / `space` | An action on the wrong resource type is `invalid_request`. ## Decisions | Situation | Answer | |---|---| | `hasPermission: true` | allow | | `hasPermission: false` with an empty `errors` list | deny | | `hasPermission: false` with a non-empty `errors` list | unknown (`unsupported`): Confluence reported errors instead of an answer | | a `user` principal with the user's accountId, or a `group` principal whose id is among the user's groups, holds the operation and target type | allow | | a `user` principal with the user's accountId, or a `group` principal among the user's groups, holds `administer`/`space` | allow, for every space action ("as a space administrator") | | no principal holds the operation or `administer`/`space`, or only groups the user is not in | deny | | no match, and the operation or `administer`/`space` is held by a principal that is neither `user` nor `group` (`anonymous`, `role`, ...) | unknown (`unsupported`, "-based space permissions not evaluated") | | 404 on the content check, or `spaces?keys=` returns nothing | unknown (`resource_not_visible`) | | `spaces?keys=` returns only keys that differ from the request (a case variant), or several spaces with the exact key | unknown (`unsupported`): the space key must match exactly | | 403 | unknown (`credential_rejected`): toolpass's account is not a Confluence Administrator, or cannot read the space or groups | | no `identity_connection` | unknown (`unsupported`) | | 401, 429, 5xx, timeout | unknown (`credential_rejected`, `upstream_rate_limited`, `upstream_error`, `upstream_timeout`) | ## Route table What a toolpass tool's request to the site means (`toolpass routes confluence` prints the table): | Request | Action, resource | |---|---| | `GET`, `PUT`, `DELETE /wiki/api/v2/pages/{id}` (and `/title`, `/properties`, `/classification-level`) | `page.read`, `page.update`, `page.delete` on `page:{id}` | | the same under `/wiki/api/v2/blogposts/{id}` | `blogpost.read`, `blogpost.update`, `blogpost.delete` on `blogpost:{id}` | | `POST /wiki/rest/api/content` with `type` `page` or `blogpost` and `space.key` | `page.create` or `blogpost.create` on `space:{key}` | | `GET /wiki/rest/api/content/{id}` and below | `page.read` on `page:{id}` (v1 does not say the type; the check is on the content id) | | `GET /wiki/rest/api/space/{key}` and below | `space.read` on `space:{key}` | | `PUT`, `DELETE /wiki/rest/api/space/{key}`, its labels, permissions, settings and theme | `space.admin` on `space:{key}` | | CQL search, `GET /wiki/rest/api/user/current` | no check (the agent's own access) | Refused, so the tool stops there: - an update with `parentId` or `ownerId` (a move or a hand-over), a body whose `id` is another page's, and a delete with `purge`; - every v2 create and `/wiki/api/v2/spaces/{id}`: they name the space by numeric id and toolpass checks spaces by key; create through `POST /wiki/rest/api/content` with `space.key` instead; - a v1 create under a parent page (`ancestors`), or naming `space.id`, a container or restrictions: the parent's restrictions would need a second check; - v1 writes to content by id (`PUT /wiki/rest/api/content/{id}`, labels, versions): v1 does not say whether the id is a page or a blog post; - comments, attachments, restrictions, folders, whiteboards, databases, tasks and the other content types: the integration has no action or resource for them; - a body that is not one JSON object sent as `application/json`, or that names a member twice. ## Probe `GET /wiki/rest/api/user/current` proves the credential. The probe warns when `identity_connection` is unset and always reminds that checking other users needs Confluence Administrator, which toolpass cannot verify without a resource. ## What it cannot see - Creating under a restricted parent: `page.create` is a space-level answer; a parent page whose restrictions exclude the user still blocks the create. - Guests and anonymous access, and the product-access ("use Confluence") permission that gates everything else: a user without a Confluence licence may still show up as holding a space permission through a group. - Who is behind an anonymous, role or licensed-users grant: such a grant for the operation, or for `administer`/`space`, makes the answer unknown, never deny. - Archived spaces and anything an app enforces on top of Confluence's permissions. ## Unverified - The operation keys and target types the v2 space permissions API uses for `space.export` (`export`/`space`), `page.restrict` (`restrict_content`/`space`) and `space.admin` (`administer`/`space`). A different key would make those actions deny for everyone; compare against a live site's `GET /wiki/api/v2/spaces/{id}/permissions` before relying on them. - The shape of `_links.next` in the two paginated calls. toolpass accepts a v1 link relative to `{url}/wiki`, a v2 link relative to the site and an absolute URL, and normalises all three to the connection's base. - The meaning of a non-empty `errors` list on `permission/check`: toolpass takes it as "the check could not be evaluated" (unknown subject, operation not applicable). If a live site also fills it on an ordinary refusal, those refusals answer unknown instead of deny, never the other way round. - How the v2 permission list represents anonymous and licensed-user (site-wide) grants: toolpass treats every principal type other than `user` and `group` as unresolved, whatever its name. - That `administer`/`space` implies every other space operation is Confluence's documented rule for space admins; whether the v2 list already materialises the implied grants is not verified (if it does, the rule is redundant, not wrong). - Whether `spaces?keys=` matches keys case-insensitively; toolpass insists on an exact match either way. - The auth-mode behaviours listed as unverified in the jira integration apply here as well. ## Test Unit tests run against one fake Atlassian site that serves both Jira's user search and Confluence's content, space and group APIs (`toolpass-py/tests/integrations/confluence/test_confluence.py`). They wire a real jira connection as `identity_connection`, and cover content checks (including a `false` with an `errors` list), space evaluation through user and group principals with pagination, the space administrator rule, anonymous and role principals, exact space-key matching, the missing-identity case, every action, the injected failure modes and the probe. With `TOOLPASS_SPECS_DIR` set, every request is validated against the Confluence v1/v2 and Jira API descriptions. No live-site test exists yet.