Skip to content

Commit 27f0b34

Browse files
feat: add Azure DevOps Cloud user permission syncing
1 parent c0d0df9 commit 27f0b34

25 files changed

Lines changed: 1778 additions & 10 deletions

‎docs/docs/configuration/idp.mdx‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,38 @@ A Keycloak connection can be used for [authentication](/docs/configuration/auth)
445445
</Steps>
446446
</Accordion>
447447

448+
### Azure DevOps Cloud
449+
450+
Use the `azuredevops` provider to sign in or link an Azure DevOps Cloud account through Microsoft Entra ID. This provider requests an Azure DevOps access token for [user-driven permission syncing](/docs/features/permission-syncing#azure-devops-cloud).
451+
452+
1. Register a web application in the Microsoft Entra tenant connected to your Azure DevOps organizations. Select **Accounts in this organizational directory only**.
453+
2. Add the redirect URI `<sourcebot_url>/api/auth/callback/azuredevops`. If you use a custom provider ID, replace `azuredevops` in the callback with that ID.
454+
3. Create a client secret. Under **API permissions**, add the **Azure DevOps** delegated permission **Code (Read)** (`vso.code`). Grant consent according to your tenant's policies. The provider requests the app's configured Azure DevOps permissions through the `.default` scope, so only configure the permissions you need.
455+
4. Set `ADO_CLIENT_ID` and `ADO_CLIENT_SECRET` in your environment, then add the provider to your config:
456+
457+
```json
458+
{
459+
"identityProviders": {
460+
"azuredevops": {
461+
"provider": "azuredevops",
462+
"purpose": "account_linking",
463+
"tenantId": "YOUR_ENTRA_TENANT_GUID",
464+
"clientId": { "env": "ADO_CLIENT_ID" },
465+
"clientSecret": { "env": "ADO_CLIENT_SECRET" },
466+
"accountLinkingRequired": true
467+
}
468+
}
469+
}
470+
```
471+
472+
Use `purpose: "account_linking"` to keep your existing sign-in provider and let users connect Azure DevOps through **Settings → Linked Accounts**. Use `purpose: "sso"` to sign in and authorize Azure DevOps access in one flow.
473+
474+
Your users authorize access through Entra. Sourcebot stores encrypted access and refresh tokens so it can refresh permissions in the background. Guest users must sign in to the tenant connected to Azure DevOps.
475+
476+
This provider supports Azure DevOps Cloud with organizational Entra accounts. Azure DevOps Server and standalone personal Microsoft accounts are not supported. Your repository connection continues to use its configured PAT for indexing.
477+
478+
An existing `microsoft-entra-id` provider handles sign-in only. Configure the `azuredevops` provider to authorize Azure DevOps API access. For protocol details, see [Microsoft's Entra OAuth guide](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/entra-oauth?view=azure-devops).
479+
448480
### Microsoft Entra ID (Azure AD)
449481

450482
[Auth.js Microsoft Entra ID Provider Docs](https://authjs.dev/getting-started/providers/microsoft-entra-id)

‎docs/docs/connections/ado-cloud.mdx‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import AzureDevopsSchema from '/snippets/schemas/v3/azuredevops.schema.mdx'
88

99
If you're not familiar with Sourcebot [connections](/docs/connections/indexing-your-code), please read that overview first.
1010

11+
## Permission syncing
12+
13+
You can enforce repository access with [user-driven permission syncing](/docs/features/permission-syncing#azure-devops-cloud). Configure the [Azure DevOps Cloud identity provider](/docs/configuration/idp#azure-devops-cloud) to authorize each user's access through Microsoft Entra OAuth. The PAT on this connection continues to handle repository discovery and indexing.
14+
1115
## Examples
1216

1317
<AccordionGroup>
@@ -125,4 +129,4 @@ Next, provide the access [token](/docs/configuration/config-file#tokens) via an
125129

126130
<AzureDevopsSchema />
127131

128-
</Accordion>
132+
</Accordion>

‎docs/docs/features/permission-syncing.mdx‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ We are actively working on supporting more code hosts. If you'd like to see a sp
4141
| [GitLab (Self-managed & Cloud)](/docs/features/permission-syncing#gitlab) | ✅ |
4242
| [Bitbucket Cloud](/docs/features/permission-syncing#bitbucket-cloud) | 🟠 Partial |
4343
| [Bitbucket Data Center](/docs/features/permission-syncing#bitbucket-data-center) | 🟠 Partial |
44-
| Azure DevOps Cloud | 🛑 |
44+
| [Azure DevOps Cloud](/docs/features/permission-syncing#azure-devops-cloud) | 🟠 User-driven only |
4545
| Azure DevOps Server | 🛑 |
4646
| Gitea | 🛑 |
4747
| Gerrit | 🛑 |
@@ -132,6 +132,22 @@ If your instance relies heavily on project or group-level permissions, we recomm
132132
- 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).
133133
- OAuth tokens require the `REPO_READ` scope to list accessible repositories during [User driven syncing](/docs/features/permission-syncing#how-it-works).
134134

135+
### Azure DevOps Cloud
136+
137+
Prerequisites:
138+
139+
- Configure an [Azure DevOps Cloud connection](/docs/connections/ado-cloud) with `url: "https://dev.azure.com"` and permission enforcement enabled.
140+
- Configure the [Azure DevOps Cloud identity provider](/docs/configuration/idp#azure-devops-cloud) through Microsoft Entra OAuth.
141+
- Sign in with that provider or link your Azure DevOps account through **Settings → Linked Accounts**.
142+
143+
Azure DevOps Cloud supports **user-driven syncing only**. Sourcebot checks whether your delegated token can read root Git item metadata for each indexed private repository. Azure DevOps evaluates your access, including group membership and explicit denies. Sourcebot grants access only after a successful read check.
144+
145+
Your permissions sync after you first sign in or link your account, on the `userDrivenPermissionSyncIntervalMs` schedule (24 hours by default), and when you manually refresh them. Newly indexed repositories become visible after your next account sync. Repository-driven syncing through a service credential is not supported.
146+
147+
Empty repositories and repositories whose default revision cannot be read do not receive a grant. They are checked again on the next sync. If Azure DevOps rejects your token, Sourcebot clears your cached grants and asks you to reconnect. Temporary API failures preserve your last successful permission set and retry through the background queue.
148+
149+
Public repositories follow the connection's public-repository enforcement settings. Azure DevOps Server is not supported.
150+
135151
# Manually refreshing permissions
136152

137153
If a user's permissions have changed and they need access updated immediately (without waiting for the next scheduled sync), they can trigger a manual refresh from the **Linked Accounts** page:

‎docs/snippets/schemas/v3/identityProvider.schema.mdx‎

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,101 @@
204204
"clientSecret"
205205
]
206206
},
207+
"AzureDevOpsIdentityProviderConfig": {
208+
"type": "object",
209+
"additionalProperties": false,
210+
"properties": {
211+
"provider": {
212+
"const": "azuredevops"
213+
},
214+
"displayName": {
215+
"type": "string",
216+
"description": "Optional label for the Microsoft Entra-backed Azure DevOps Cloud provider. Defaults to 'Azure DevOps'."
217+
},
218+
"purpose": {
219+
"enum": [
220+
"sso",
221+
"account_linking"
222+
]
223+
},
224+
"clientId": {
225+
"anyOf": [
226+
{
227+
"type": "object",
228+
"properties": {
229+
"env": {
230+
"type": "string",
231+
"description": "The name of the environment variable that contains the token."
232+
}
233+
},
234+
"required": [
235+
"env"
236+
],
237+
"additionalProperties": false
238+
},
239+
{
240+
"type": "object",
241+
"properties": {
242+
"googleCloudSecret": {
243+
"type": "string",
244+
"description": "The resource name of a Google Cloud secret. Must be in the format `projects/<project-id>/secrets/<secret-name>/versions/<version-id>`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets"
245+
}
246+
},
247+
"required": [
248+
"googleCloudSecret"
249+
],
250+
"additionalProperties": false
251+
}
252+
]
253+
},
254+
"clientSecret": {
255+
"anyOf": [
256+
{
257+
"type": "object",
258+
"properties": {
259+
"env": {
260+
"type": "string",
261+
"description": "The name of the environment variable that contains the token."
262+
}
263+
},
264+
"required": [
265+
"env"
266+
],
267+
"additionalProperties": false
268+
},
269+
{
270+
"type": "object",
271+
"properties": {
272+
"googleCloudSecret": {
273+
"type": "string",
274+
"description": "The resource name of a Google Cloud secret. Must be in the format `projects/<project-id>/secrets/<secret-name>/versions/<version-id>`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets"
275+
}
276+
},
277+
"required": [
278+
"googleCloudSecret"
279+
],
280+
"additionalProperties": false
281+
}
282+
]
283+
},
284+
"tenantId": {
285+
"type": "string",
286+
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
287+
"description": "Microsoft Entra directory tenant ID. Register the application in the tenant connected to your Azure DevOps organizations."
288+
},
289+
"accountLinkingRequired": {
290+
"type": "boolean",
291+
"default": false
292+
}
293+
},
294+
"required": [
295+
"provider",
296+
"purpose",
297+
"clientId",
298+
"clientSecret",
299+
"tenantId"
300+
]
301+
},
207302
"GoogleIdentityProviderConfig": {
208303
"type": "object",
209304
"additionalProperties": false,
@@ -1204,6 +1299,101 @@
12041299
}
12051300
},
12061301
"oneOf": [
1302+
{
1303+
"type": "object",
1304+
"additionalProperties": false,
1305+
"properties": {
1306+
"provider": {
1307+
"const": "azuredevops"
1308+
},
1309+
"displayName": {
1310+
"type": "string",
1311+
"description": "Optional label for the Microsoft Entra-backed Azure DevOps Cloud provider. Defaults to 'Azure DevOps'."
1312+
},
1313+
"purpose": {
1314+
"enum": [
1315+
"sso",
1316+
"account_linking"
1317+
]
1318+
},
1319+
"clientId": {
1320+
"anyOf": [
1321+
{
1322+
"type": "object",
1323+
"properties": {
1324+
"env": {
1325+
"type": "string",
1326+
"description": "The name of the environment variable that contains the token."
1327+
}
1328+
},
1329+
"required": [
1330+
"env"
1331+
],
1332+
"additionalProperties": false
1333+
},
1334+
{
1335+
"type": "object",
1336+
"properties": {
1337+
"googleCloudSecret": {
1338+
"type": "string",
1339+
"description": "The resource name of a Google Cloud secret. Must be in the format `projects/<project-id>/secrets/<secret-name>/versions/<version-id>`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets"
1340+
}
1341+
},
1342+
"required": [
1343+
"googleCloudSecret"
1344+
],
1345+
"additionalProperties": false
1346+
}
1347+
]
1348+
},
1349+
"clientSecret": {
1350+
"anyOf": [
1351+
{
1352+
"type": "object",
1353+
"properties": {
1354+
"env": {
1355+
"type": "string",
1356+
"description": "The name of the environment variable that contains the token."
1357+
}
1358+
},
1359+
"required": [
1360+
"env"
1361+
],
1362+
"additionalProperties": false
1363+
},
1364+
{
1365+
"type": "object",
1366+
"properties": {
1367+
"googleCloudSecret": {
1368+
"type": "string",
1369+
"description": "The resource name of a Google Cloud secret. Must be in the format `projects/<project-id>/secrets/<secret-name>/versions/<version-id>`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets"
1370+
}
1371+
},
1372+
"required": [
1373+
"googleCloudSecret"
1374+
],
1375+
"additionalProperties": false
1376+
}
1377+
]
1378+
},
1379+
"tenantId": {
1380+
"type": "string",
1381+
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
1382+
"description": "Microsoft Entra directory tenant ID. Register the application in the tenant connected to your Azure DevOps organizations."
1383+
},
1384+
"accountLinkingRequired": {
1385+
"type": "boolean",
1386+
"default": false
1387+
}
1388+
},
1389+
"required": [
1390+
"provider",
1391+
"purpose",
1392+
"clientId",
1393+
"clientSecret",
1394+
"tenantId"
1395+
]
1396+
},
12071397
{
12081398
"type": "object",
12091399
"additionalProperties": false,

0 commit comments

Comments
 (0)