Skip to content

Commit 5bffff6

Browse files
authored
feat(search): update GitLab permission CSVs through the API (#9841)
* feat(search): update GitLab permission CSVs through the API * fix(search): align GitLab CSV updates with CLI and API policies * test(cli): cover confirmation for GitLab permission replacement
1 parent ce4f216 commit 5bffff6

23 files changed

Lines changed: 1249 additions & 41 deletions

File tree

‎.github/scripts/http-e2e.sh‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ case "$group" in
259259
bun --no-env-file scripts/test-assistant-actions-e2e.ts
260260
;;
261261
organization-search)
262+
GITLAB_CSV_E2E_BASE_URL="$local_app_url" \
263+
GITLAB_CSV_E2E_REPORT_PATH="$report_dir/gitlab-csv-permissions-http-report.json" \
264+
bun --no-env-file scripts/test-gitlab-csv-permissions-e2e.ts
262265
ORGANIZATION_SEARCH_E2E_BASE_URL="$local_app_url" \
263266
ORGANIZATION_SEARCH_E2E_DATABASE_URL="$DATABASE_URL" \
264267
ORGANIZATION_SEARCH_E2E_AUTH_SECRET="$BETTER_AUTH_SECRET" \

‎apps/docs/content/docs/cli/organizations.mdx‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,74 @@ sim organizations members remove <userId> [options]
229229

230230
API request and response schemas: [removeOrganizationMember](/api-reference/organizations/removeOrganizationMember).
231231

232+
## GitLab Search permissions
233+
234+
### Get GitLab search permission revision
235+
236+
OAuth login or personal API key required.
237+
238+
```bash
239+
sim organizations gitlab-permissions get <sourceId> [options]
240+
```
241+
242+
**Arguments**
243+
244+
<CommandTable>
245+
246+
| Argument | Required | Description |
247+
| --- | --- | --- |
248+
| `sourceId` | Yes | Identifier of the configured GitLab Search source. |
249+
250+
</CommandTable>
251+
252+
**Options**
253+
254+
<CommandTable>
255+
256+
| Option | Required | Description |
257+
| --- | --- | --- |
258+
| `--organization <value>` | Yes | Organization identifier. |
259+
260+
</CommandTable>
261+
262+
API request and response schemas: [getGitLabSearchPermissions](/api-reference/organizations/getGitLabSearchPermissions).
263+
264+
### Replace GitLab search permission CSVs
265+
266+
OAuth login or personal API key required.
267+
268+
This replaces both GitLab permission files. Readers omitted from the new grants lose access.
269+
270+
```bash
271+
sim organizations gitlab-permissions replace <sourceId> [options]
272+
```
273+
274+
**Arguments**
275+
276+
<CommandTable>
277+
278+
| Argument | Required | Description |
279+
| --- | --- | --- |
280+
| `sourceId` | Yes | Identifier of the configured GitLab Search source. |
281+
282+
</CommandTable>
283+
284+
**Options**
285+
286+
<CommandTable>
287+
288+
| Option | Required | Description |
289+
| --- | --- | --- |
290+
| `--organization <value>` | Yes | Organization identifier. |
291+
| `--expected-revision <value>` | Yes | Current permission revision obtained before generating this snapshot. |
292+
| `--user-mapping <json\|@file>` | Yes | GitLab user IDs and their verified Sim email addresses. (JSON, or @path / @- to read a file or stdin). |
293+
| `--project-permissions <json\|@file>` | Yes | GitLab project paths and the IDs of users granted access. (JSON, or @path / @- to read a file or stdin). |
294+
| `-y, --yes` | Yes | Confirm this operation. |
295+
296+
</CommandTable>
297+
298+
API request and response schemas: [replaceGitLabSearchPermissions](/api-reference/organizations/replaceGitLabSearchPermissions).
299+
232300
## Invitations
233301

234302
### List organization invitations

‎apps/docs/content/docs/cli/reference.mdx‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4288,6 +4288,72 @@ sim organizations sso policy update [options]
42884288

42894289
API request and response schemas: [updateSsoPolicy](/api-reference/organizations/updateSsoPolicy).
42904290

4291+
### sim organizations gitlab-permissions get
4292+
4293+
Get GitLab search permission revision (OAuth login or personal API key required)
4294+
4295+
```bash
4296+
sim organizations gitlab-permissions get <sourceId> [options]
4297+
```
4298+
4299+
**Arguments**
4300+
4301+
<CommandTable>
4302+
4303+
| Argument | Required | Description |
4304+
| --- | --- | --- |
4305+
| `sourceId` | Yes | Identifier of the configured GitLab Search source. |
4306+
4307+
</CommandTable>
4308+
4309+
**Options**
4310+
4311+
<CommandTable>
4312+
4313+
| Option | Required | Description |
4314+
| --- | --- | --- |
4315+
| `--organization <value>` | Yes | Organization identifier. |
4316+
4317+
</CommandTable>
4318+
4319+
API request and response schemas: [getGitLabSearchPermissions](/api-reference/organizations/getGitLabSearchPermissions).
4320+
4321+
### sim organizations gitlab-permissions replace
4322+
4323+
Replace GitLab search permission CSVs (OAuth login or personal API key required)
4324+
4325+
This replaces both GitLab permission files. Readers omitted from the new grants lose access.
4326+
4327+
```bash
4328+
sim organizations gitlab-permissions replace <sourceId> [options]
4329+
```
4330+
4331+
**Arguments**
4332+
4333+
<CommandTable>
4334+
4335+
| Argument | Required | Description |
4336+
| --- | --- | --- |
4337+
| `sourceId` | Yes | Identifier of the configured GitLab Search source. |
4338+
4339+
</CommandTable>
4340+
4341+
**Options**
4342+
4343+
<CommandTable>
4344+
4345+
| Option | Required | Description |
4346+
| --- | --- | --- |
4347+
| `--organization <value>` | Yes | Organization identifier. |
4348+
| `--expected-revision <value>` | Yes | Current permission revision obtained before generating this snapshot. |
4349+
| `--user-mapping <json\|@file>` | Yes | GitLab user IDs and their verified Sim email addresses. (JSON, or @path / @- to read a file or stdin). |
4350+
| `--project-permissions <json\|@file>` | Yes | GitLab project paths and the IDs of users granted access. (JSON, or @path / @- to read a file or stdin). |
4351+
| `-y, --yes` | Yes | Confirm this operation. |
4352+
4353+
</CommandTable>
4354+
4355+
API request and response schemas: [replaceGitLabSearchPermissions](/api-reference/organizations/replaceGitLabSearchPermissions).
4356+
42914357
### sim organizations get
42924358

42934359
Get organization (OAuth login or personal API key required)

‎apps/docs/content/docs/search/gitlab.mdx‎

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ GitLab Search uses **Service account** mode, with one self-managed project per s
1111
| --- | --- | --- |
1212
| Token owner | Active instance administrator | Account that can read the project's content |
1313
| PAT scopes | `read_api`; `admin_mode` when Admin Mode is enabled | `read_api` |
14-
| Reader authorization | Current GitLab identity, membership, roles, and supported restrictions | Admin-uploaded email mapping and project grants |
15-
| Membership changes | Checked during the request | Admin updates the CSV files |
14+
| Reader authorization | Current GitLab identity, membership, roles, and supported restrictions | Email mapping and project grants supplied by your organization |
15+
| Membership changes | Checked during the request | After the next successful CSV update |
1616
| Confidential issues | Only when supported permissions allow the reader | Always excluded |
1717

1818
The administrator path requires self-managed GitLab 17.4 or later and its administrator identity/settings APIs. A project Maintainer or group Owner is not an instance administrator. Non-admin mode does not require those administrator APIs.
@@ -69,22 +69,58 @@ Use the full namespace path even if **Project** contains a numeric ID. Include i
6969
- Use UTF-8 CSV, at most 4 MiB and 100,000 data rows per file. The full request must fit 10 MiB.
7070
- Identical rows are deduplicated. Conflicting user-ID/email mappings and malformed rows are rejected.
7171

72-
These grants are admin-managed authorization. A granted reader can search allowed non-confidential content; Sim does not infer that person's actual GitLab role, restrictions, or membership from the non-admin token. Update both files when access changes.
72+
In this example, Alice can search `engineering/platform` because the files link GitLab user `123` to both that project and `alice@example.com`. Sim checks that Alice is a current organization member and that her verified Sim email uniquely matches the grant. It checks again when she opens a result. GitLab receives the request under the source's read-only service account; Alice does not sign in to GitLab or authorize Sim individually.
73+
74+
Saving the CSVs grants the access listed in them. Changing a membership in GitLab alone does not change these grants. Replace the CSVs to add or revoke access; the change applies to the next search or read without re-indexing.
7375

7476
## Access checks
7577

7678
The source token retrieves content. Administrator mode proves the reader's current GitLab identity from verified Sim email, then checks project/group membership and policy during the request. Unsupported restrictions fail closed.
7779

7880
Non-admin mode uses saved source-local CSV grants and revalidates the token's current host/project identity. Confidential issues are excluded, including issues that became confidential after search.
7981

80-
Reads recheck current settings, permissions, and metadata. A reference from another member, account, organization, project, or host cannot authorize a read. Live Search does not use a stored content or permission index.
82+
Reads recheck current settings, permissions, and metadata. A reference from another member, account, organization, project, or host cannot authorize a read. Sim fetches content live from GitLab. In CSV mode, Sim stores the uploaded mappings and resulting project grants.
8183

8284
## Update permissions or rotate the token
8385

8486
In project settings, replace either CSV file, retaining the other when appropriate. Sim validates the resulting pair before saving. Invalid or stale updates preserve saved grants. Removed readers lose access on the next operation.
8587

8688
To rotate the PAT, supply its replacement in source settings. A blank token retains the saved token. Changing host, project, or permission strategy requires valid configuration for the new identity.
8789

90+
### Automate updates with GitLab CI
91+
92+
After an admin configures a source in **Non-admin token** mode, a job can [read its revision](/api-reference/organizations/getGitLabSearchPermissions) and [replace both CSVs](/api-reference/organizations/replaceGitLabSearchPermissions). Copy `SIM_ORG_ID` and `SIM_SOURCE_ID` from the source settings URL: `/o/<organizationId>/settings/integrations/sources/<sourceId>`. Set `SIM_BASE_URL` to your Sim origin (for hosted Sim, `https://www.sim.ai`).
93+
94+
Store an organization admin's [personal Sim API key](/api-reference/authentication) as a [masked, protected GitLab CI variable](https://docs.gitlab.com/ci/variables/) named `SIM_API_KEY`. A personal key inherits its owner's access. This endpoint can only read the revision or replace both CSVs; it cannot change the GitLab PAT, host, or project.
95+
96+
This example runs on a schedule against a protected branch. Replace `./ci/export-gitlab-permissions` with your existing export command. It must write a fresh, complete `users.csv` and `project-permissions.csv` and exit nonzero if it cannot finish.
97+
98+
```yaml title=".gitlab-ci.yml"
99+
sync_gitlab_permissions:
100+
image: alpine:3.22
101+
resource_group: "sim-permissions-$SIM_SOURCE_ID"
102+
rules:
103+
- if: '$CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_REF_PROTECTED == "true"'
104+
before_script:
105+
- apk add --no-cache curl jq
106+
script:
107+
- |
108+
set -eu
109+
url="${SIM_BASE_URL%/}/api/v2/organizations/$SIM_ORG_ID/search/sources/$SIM_SOURCE_ID/gitlab-permissions"
110+
auth="X-API-Key: $SIM_API_KEY"
111+
current=$(curl --fail --silent --show-error -H "$auth" "$url")
112+
revision=$(printf '%s' "$current" | jq -er '.data.revision')
113+
./ci/export-gitlab-permissions
114+
jq -n --argjson rev "$revision" \
115+
--rawfile users users.csv --rawfile grants project-permissions.csv \
116+
'{expectedRevision: $rev, userMapping: {filename: "users.csv", content: $users},
117+
projectPermissions: {filename: "project-permissions.csv", content: $grants}}' > update.json
118+
curl --fail --silent --show-error --request PUT -H "$auth" \
119+
-H 'Content-Type: application/json' --data-binary @update.json "$url"
120+
```
121+
122+
A successful update returns the new revision. A `409 Conflict` means someone updated the source after the job read its revision. Rerun the job from the beginning so it exports current grants. An invalid or header-only export leaves the previous grants in place. Alert on failed jobs so revocations aren't missed. If the project should have no readers, choose **Pause search** in the source settings. You can update the grants while the source is paused, then resume search when the grants are correct.
123+
88124
## Troubleshooting
89125

90126
| Problem | Check |

0 commit comments

Comments
 (0)