# bitbucket One connection is one Bitbucket Cloud workspace (`edition: cloud`, the default) or one Bitbucket Data Center instance (`edition: datacenter`). Both answer the same actions. **Cloud**: toolpass finds the user among the workspace's members by email, reads the user's effective repository permission (the highest of direct, group and project grants, as Bitbucket computes it), the user's explicit project permission, whether the user is a workspace owner, and for `@branch` questions the repository's branch restrictions. **Data Center**: toolpass finds the user by email, lists the groups the user belongs to, and combines the direct, group, project, project-default, public and global grants into the effective level itself, then the ref restrictions for `@branch` questions. Every call is a read with toolpass's own token; nothing is written. ## Credential ### Cloud A **workspace access token** (Premium) with the read `repository`, `project` and `account` (workspace) scopes, sent as a bearer. Reading repository permissions and branch restrictions needs admin on the repository (`repository:admin`), and reading explicit project permissions needs `project:admin`; a workspace access token holds those for the whole workspace. Alternatively an **Atlassian API token** of a workspace administrator with `auth_mode: basic` and `username` set to the account's email. App passwords no longer work. Filtering members by email, which is how the user is found, is allowed only for a workspace administrator, an integration or a workspace access token. ### Data Center An **HTTP access token** of a user with the global `ADMIN` permission, sent as a bearer. Listing repository and project permissions needs `REPO_ADMIN` or `PROJECT_ADMIN` on each object, listing global permissions needs `ADMIN`, and listing a user's groups needs `LICENSED_USER`; a global administrator has them all. With a lesser token, objects and grants toolpass cannot read answer `unknown`, never `deny`. `toolpass probe` checks the token: on Cloud that it can look members up by email, on Data Center that it authenticates and whether it can read global permissions. ## Connection ```yaml - id: bitbucket-acme integration: bitbucket workspace: acme # Cloud: every resource is in this workspace credential: env:BITBUCKET_TOKEN # workspace access token # auth_mode: basic # Atlassian API token instead: username + credential # username: bot@acme.com - id: bitbucket-dc integration: bitbucket edition: datacenter url: https://bitbucket.acme.internal credential: env:BITBUCKET_DC_TOKEN # HTTP access token of an administrator ``` | Key | Meaning | |---|---| | `edition` | `cloud` (default) or `datacenter` | | `url` | Data Center base URL, required there; Cloud default `https://api.bitbucket.org` | | `workspace` | Cloud only, required: the workspace slug | | `auth_mode` | `bearer` (default): `Authorization: Bearer`; `basic`: HTTP Basic with `username` and the token | | `username` | `auth_mode: basic`: the Atlassian account email the API token belongs to | | `credential` | the token, `env:` or `file:` | ### Identity Cloud: `GET /2.0/workspaces/{workspace}/members?q=user.email IN ("")` with `fields=values.user.email,values.user.account_id,...`. The address is lowercased and must be a plain email (no quotes, so the filter cannot be broken out of); the member whose `email` equals it is the user and the Atlassian `account_id` is the identity. No member is `user_not_found`, two are `user_ambiguous`. A person with a Bitbucket account who is not in the workspace is `user_not_found`. Data Center: `GET /rest/api/latest/users?filter=`, every page; the filter is a substring match on name and email, so only the record whose `emailAddress` equals the address counts. The user name is the identity; `active: false` is denied every action. The user's groups come from `GET /rest/api/latest/admin/users/more-members?context=`; when that answers 403 the identity still resolves, and any group grant that would change the answer makes it `unknown`. Groups sent by the caller are ignored. ## Resources | Resource | Cloud | Data Center | |---|---|---| | `repo:[@branch]` | a repository of the workspace | not accepted | | `repo:/[@branch]` | not accepted | a repository; `~user` keys are personal projects | | `project:` | a project of the workspace | a project | | `workspace` | the workspace | the instance | Slugs and keys are `[A-Za-z0-9][A-Za-z0-9._-]*` (or a Cloud `{uuid}`); a branch name follows git's rules (no `..`, no leading `-`, no `?*[\^~:` or spaces). Everything is path-escaped before it reaches a URL. ## Actions | Action | Needs | Resource | |---|---|---| | `repo.read` | read | repo | | `repo.push` | write; on `@branch`, no `push` restriction (Cloud) or `read-only` / `pull-request-only` restriction (Data Center) that stops the user | repo | | `pr.merge` | write; on `@branch`, no `restrict_merges` restriction (Cloud) or `read-only` restriction (Data Center) that stops the user | repo | | `repo.admin` | admin | repo | | `project.read` / `project.write` / `project.admin` | read / write / admin on the project | project | | `repo.create` | Cloud: `create-repo` (or admin); Data Center: project admin, see Unverified | project | | `workspace.member` | Cloud: member of the workspace; Data Center: an active user | workspace | | `workspace.admin` | Cloud: workspace owner; Data Center: global `ADMIN` or `SYS_ADMIN` | workspace | ### Cloud evaluation - Repository: `GET /2.0/repositories/{ws}/{slug}` (404 is `resource_not_visible`), then `GET /2.0/workspaces/{ws}/permissions/repositories/{slug}?q=user.account_id=""`, whose entries are effective permissions. A Bitbucket that rejects the filter (400) is read whole. No entry: a workspace owner has admin, a public repository grants read, otherwise none. - Branch: `GET /2.0/repositories/{ws}/{slug}/branch-restrictions?kind=push|restrict_merges`. A restriction whose glob `pattern` matches the branch stops the user unless the user is in its `users`; a matching restriction that exempts `groups` is `unknown`, since Cloud exposes no group membership. `branching_model` restrictions and patterns with `[]{}` are `unknown`. - Project: `GET .../projects/{key}/permissions-config/users/{account_id}` is the explicit permission. When it does not suffice, a public project grants read and a workspace owner is allowed; otherwise the project's group permissions are read and a group that would suffice makes the answer `unknown`. - Workspace: `GET .../members/{account_id}` (404 is not a member); owners from `GET .../permissions?q=permission="owner"`. ### Data Center evaluation - The level is the highest of: the user's direct grant and the grants of the user's groups on the repository (`.../repos/{slug}/permissions/users?filter=` exact-matched, and `/permissions/groups`), the same on the project, the project's default permission (`.../permissions/PROJECT_READ|WRITE|ADMIN/all`), `public` on the repository or project (read), and the global permissions (`/rest/api/latest/admin/permissions/users|groups`, where `ADMIN` and `SYS_ADMIN` carry admin everywhere). Project levels carry to repositories. - Branch: `GET /rest/branch-permissions/2.0/projects/{key}/repos/{slug}/restrictions` and the project's own `.../projects/{key}/restrictions`, which every repository inherits. `read-only` stops pushes and merges, `pull-request-only` stops direct pushes; `ANY_REF` matchers match every branch, `BRANCH` matchers compare the name, `PATTERN` matchers are globs; the listed `users` and `groups` are exempt. Branching-model matchers are `unknown`. Globs are Ant-style: `*` and `?` stay within one path segment, `**` crosses segments, and `refs/heads/` is stripped from a pattern. Bitbucket Cloud does not document whether its `*` crosses `/`, and neither edition documents whether a pattern without `/` matches a branch of that name inside a folder (`main` against `release/main`), so a pattern whose readings disagree for the branch asked about answers `unknown` rather than guessing. Character classes and alternations are `unknown` on both editions. ## Decisions | Situation | toolpass answers | |---|---| | level held, and for `@branch` no restriction stops the user | allow, naming the source (direct, group, project, default, public, owner, global) | | level not held | deny ("has read, needs write") | | a restriction matches and the user is not exempt | deny, naming the restriction | | a matching restriction exempts a group toolpass cannot resolve (Cloud always; Data Center without `LICENSED_USER`) | unknown (`unsupported`) | | a `branching_model` restriction, a pattern with character classes, or on Cloud a pattern whose glob semantics are ambiguous for the branch, could apply | unknown (`unsupported`) | | Cloud project: explicit permission insufficient, not an owner, a group grant would suffice | unknown (`unsupported`) | | Data Center: level insufficient and a group or global grant toolpass could not read would matter | unknown (`unsupported`) | | deactivated user (Data Center) | deny | | no member / user with the email | deny (`user_not_found`) | | repository or project answers 404 | unknown (`resource_not_visible`) | | 401, 403 (the token lacks the permission the read needs; Data Center `workspace.admin` without `ADMIN`) | unknown (`credential_rejected`) | | a next page outside the API base | unknown (`upstream_error`) | | 429, 5xx, timeout | unknown (`upstream_rate_limited` / `upstream_error` / `upstream_timeout`) | Error bodies are never copied into a decision text. ## What it cannot see - Cloud group membership: the 2.0 API exposes none, so grants that come only through a group answer `unknown` (repository permissions are the exception: Bitbucket folds groups into the effective permission itself). - Cloud project-level grants folded into a repository answer are trusted as Bitbucket reports them; toolpass does not recompute them. - Branching-model branch types, merge checks other than who may merge (approvals, builds, tasks), and Data Center access keys as exemptions. - Data Center: the `permission` filters of `/rest/api/latest/users`, personal repository visibility rules beyond `public`, and repository-level default permissions. ## Unverified Marked `# UNVERIFIED:` in the code: - Cloud: the filter grammar `q=user.account_id="..."` on the repository permissions list; the spec says the list "may be filtered by user" and documents only `permission>"read"`. A 400 falls back to reading the list whole, so a wrong grammar costs calls, not correctness. - Cloud: whether `*` in a branch restriction pattern matches across `/`; both editions: whether a pattern without `/` matches inside folders. toolpass answers `unknown` whenever the readings differ for the branch asked about. - Data Center: repository creation in a project is taken to need `PROJECT_ADMIN`. If Bitbucket lets `PROJECT_WRITE` create repositories, users with write are denied `repo.create` although they could. ## Observed requests For toolpass tools, the route table (`toolpass routes bitbucket`) maps requests to `api.bitbucket.org/2.0` (Cloud) or to the instance's `/rest/api/1.0`, `/rest/api/latest` and `/rest/branch-utils` (Data Center) to this integration's actions: - Cloud resources belong to the connection's workspace, so a request must name that workspace in its path (`/2.0/repositories//...`, `/2.0/workspaces//...`); one naming another workspace, or the same one by UUID or in other letter case, is refused. - Reads check `repo.read`, `project.read` or `workspace.member`; hooks, deploy keys, permissions and pipeline variables check `repo.admin`, `project.admin` or `workspace.admin`. - `repo.push` and `pr.merge` evaluate branch restrictions only when the resource names the branch, so a write that lands on a branch must name it: creating or deleting a branch, committing files (`POST /src` with one `branch` field; Data Center `PUT /browse/...`). Merging a pull request does not name its destination branch, so it is refused rather than checked on write access alone. - Workspace reads, code search included (`GET /search/code` and `POST /codesearch`), check `workspace.member`. - Opening a pull request, commenting and approving check `repo.read`; a pull request from another repository (a fork) is refused. Editing or deleting a comment checks `repo.admin`. Forks (they land in a project named in the body), moving a repository to another project, Data Center tags (ref restrictions can cover them), Connect app properties and git over HTTPS are not routed. ## Test Unit tests run against fakes of both editions: the Cloud fake validates every request against the official description (`bitbucket-cloud` in `test/specs/fetch.sh`, with the undeclared `q`, `fields` and paging parameters allowed) and serves members, permissions, branch restrictions and projects; the Data Center fake serves users, groups, grant listings with substring filters, defaults, public flags, restrictions and start/limit paging. There is no live test; after configuring, run `toolpass probe` and one check for a user you know is allowed.