Skip to content

Commit 2e30395

Browse files
fix(backend): remove project-level permission call from Bitbucket Server repo-driven sync
Repo-driven syncing for Bitbucket Server now only covers users with direct repo-level grants. Project-level and group-level access remains covered by account-driven syncing, consistent with the Bitbucket Cloud approach. This avoids redundant API calls (one per repo for the same project) that could cause rate limiting issues at scale. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6947303 commit 2e30395

File tree

3 files changed

+15
-39
lines changed

3 files changed

+15
-39
lines changed

docs/docs/connections/bitbucket-data-center.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview),
7171
In order to index private repositories, you'll need to provide a [HTTP Access Token](https://confluence.atlassian.com/bitbucketserver/http-access-tokens-939515499.html). Tokens can be scoped to a user account, a project, or an individual repository. Only repositories visible to the token will be able to be indexed by Sourcebot.
7272

7373
<Note>
74-
If [permission syncing](/docs/features/permission-syncing#bitbucket-data-center) is enabled, the token must have **Repository Admin** and **Project Admin** permissions so Sourcebot can read repository and project-level user permissions.
74+
If [permission syncing](/docs/features/permission-syncing#bitbucket-data-center) is enabled, the token must have **Repository Admin** permissions so Sourcebot can read repository-level user permissions.
7575
</Note>
7676

7777
<Tabs>

docs/docs/features/permission-syncing.mdx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,19 @@ Prerequisites:
113113
Permission syncing works with **Bitbucket Data Center**. OAuth tokens must assume the `PUBLIC_REPOS` and `REPO_READ` scopes.
114114

115115
<Warning>
116-
**Partial coverage for repo-driven syncing.** Bitbucket Data Center's permissions APIs only return users who have been **directly and explicitly** granted access at the repository or project level. Users who have access via group membership are **not** captured by repo-driven syncing.
116+
**Partial coverage for repo-driven syncing.** Repo-driven syncing only captures users who have been **directly and explicitly** granted access to the repository. Users who have access via any of the following are **not** captured by repo-driven syncing:
117+
118+
- Project-level permissions (inherited by all repos in the project)
119+
- Group membership
117120

118121
These users **will** still gain access via [user-driven syncing](/docs/features/permission-syncing#how-it-works), which fetches all repositories accessible to each authenticated user using the `REPO_READ` scope. However, there may be a delay between when access is granted and when affected users see the repository in Sourcebot (up to the `experiment_userDrivenPermissionSyncIntervalMs` interval, which defaults to 24 hours).
119122

120-
If your instance relies heavily on group-level permissions, we recommend reducing the `experiment_userDrivenPermissionSyncIntervalMs` interval to limit the window of delay.
123+
If your instance relies heavily on project or group-level permissions, we recommend reducing the `experiment_userDrivenPermissionSyncIntervalMs` interval to limit the window of delay.
121124
</Warning>
122125

123126
**Notes:**
124127
- A Bitbucket Data Center [external identity provider](/docs/configuration/idp#bitbucket-server) must be configured to (1) correlate a Sourcebot user with a Bitbucket Data Center user, and (2) to list repositories that the user has access to for [User driven syncing](/docs/features/permission-syncing#how-it-works).
125-
- The connection token must have **Repository Admin** and **Project Admin** permissions so Sourcebot can read repository and project-level user permissions for [Repo driven syncing](/docs/features/permission-syncing#how-it-works).
128+
- The connection token must have **Repository Read** permissions so Sourcebot can read repository-level user permissions for [Repo driven syncing](/docs/features/permission-syncing#how-it-works).
126129
- OAuth tokens require the `REPO_READ` scope to list accessible repositories during [User driven syncing](/docs/features/permission-syncing#how-it-works).
127130

128131
# How it works

packages/backend/src/bitbucket.ts

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -697,24 +697,20 @@ export const getReposForAuthenticatedBitbucketServerUser = async (
697697
};
698698

699699
/**
700-
* Returns the user IDs of users who have been explicitly granted permission on a Bitbucket Server repository
701-
* at the repo level (direct grants) or project level (inherited by all repos in the project).
700+
* Returns the user IDs of users who have been explicitly granted direct access to a Bitbucket Server repository.
702701
*
703-
* @note This does NOT include users who have access via groups. As a result, permission syncing
704-
* may under-grant access for instances that rely heavily on group-level permissions. Those users
705-
* will still gain access through account-driven syncing (accountPermissionSyncer).
702+
* @note This only covers direct user-to-repo grants. It does NOT include users who have access via:
703+
* - Project-level permissions (inherited by all repos in the project)
704+
* - Group membership
705+
* These users will still gain access through account-driven syncing (accountPermissionSyncer).
706706
*
707707
* @see https://developer.atlassian.com/server/bitbucket/rest/v906/api-group-repository/#api-rest-api-latest-projects-projectkey-repos-reposlug-permissions-users-get
708-
* @see https://developer.atlassian.com/server/bitbucket/rest/v906/api-group-project/#api-rest-api-latest-projects-projectkey-permissions-users-get
709708
*/
710709
export const getUserPermissionsForServerRepo = async (
711710
client: BitbucketClient,
712711
projectKey: string,
713712
repoSlug: string,
714713
): Promise<Array<{ userId: string }>> => {
715-
const userIdSet = new Set<string>();
716-
717-
// Fetch repo-level permissions
718714
const repoUsers = await fetchWithRetry(() => getPaginatedServer<{ user: { id: number } }>(
719715
`/rest/api/1.0/projects/${projectKey}/repos/${repoSlug}/permissions/users` as ServerGetRequestPath,
720716
async (url, start) => {
@@ -728,31 +724,8 @@ export const getUserPermissionsForServerRepo = async (
728724
return data;
729725
}
730726
), `repo-level permissions for ${projectKey}/${repoSlug}`, logger);
731-
for (const entry of repoUsers) {
732-
if (entry.user?.id != null) {
733-
userIdSet.add(String(entry.user.id));
734-
}
735-
}
736-
737-
// Fetch project-level permissions (inherited by all repos in the project)
738-
const projectUsers = await fetchWithRetry(() => getPaginatedServer<{ user: { id: number } }>(
739-
`/rest/api/1.0/projects/${projectKey}/permissions/users` as ServerGetRequestPath,
740-
async (url, start) => {
741-
const response = await client.apiClient.GET(url, {
742-
params: { query: { limit: 100, start } },
743-
});
744-
const { data, error } = response;
745-
if (error) {
746-
throw new Error(`Failed to fetch project-level permissions for ${projectKey}: ${JSON.stringify(error)}`);
747-
}
748-
return data;
749-
}
750-
), `project-level permissions for ${projectKey}`, logger);
751-
for (const entry of projectUsers) {
752-
if (entry.user?.id != null) {
753-
userIdSet.add(String(entry.user.id));
754-
}
755-
}
756727

757-
return Array.from(userIdSet).map(userId => ({ userId }));
728+
return repoUsers
729+
.filter(entry => entry.user?.id != null)
730+
.map(entry => ({ userId: String(entry.user.id) }));
758731
};

0 commit comments

Comments
 (0)