Repository navigation
Show restricted members only the connectors their roles reach - #1666
Merged
Merged
Conversation
8 of 14 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third step of "member visibility" (after #1664 and #1665). In a workspace set to restricted mode, a member who is not an admin now sees on the connectors, tools and File Search routes only what their AI client can use: the connectors they own, and the connectors that hold at least one tool their roles allow. Of another member's connector they see only the allowed tools.
Admins and open workspaces take today's code path. No workspace has the setting yet and the switch in the UI comes last, so nothing changes on deploy. Writes keep their owner-or-admin rule.
Changes
Every handler has the same shape:
scopeFor(req.user), and when the scope is not restricted, today's code unchanged. The rules per route:Connectors controller (
api/connectors):GET /andGET /health-check: visible connectors only, filtered in the Prismawhere(newConnectorsService.findVisibleByOrg, workspace filter AND visibility filter), solimitandoffsetpage over visible rows.toolsnarrowed to the allowed ones unless the caller owns the connector. The setup state is still computed from all tools.GET /:id: 404 for a hidden connector, with exactly the body an unknown id gets (Connector <id> not found). Tools narrowed.mcpServersfiltered withserverWhere. A server that holds a visible connector is visible by that rule, so today the filter keeps every entry; it is there so the two rules cannot drift.GET /:id/oauth-config,GET /:id/my-credential,DELETE /:id/my-credential,POST /:id/oauth/authorize: 404 for a hidden connector. Per-user sign-in keeps working for a member whose role allows the connector.GET /:id/webhook/url,GET /:id/webhook/events,GET /:id/catalog-diff: these were already owner-or-admin. Now a hidden connector answers 404 first, then today's owner-or-admin check.GET /export-all: owned connectors only.GET /:id/export: 404 (Connector not found, as for an unknown id) for a connector the member does not own.GET /proxy-availability,GET /oauth/redirect-uri: no connector data, unchanged.Tools controller (
api/connectors/:connectorId/tools):GET /: 404 for a hidden connector; the list is filtered in SQL with the new helpertoolWhere(allowed ids, or all tools for the owner).GET /:toolId/annotations,GET /:toolId/response-mapping: already owner-or-admin. Now a hidden connector answers 404 first. Owners see every tool of their connector, so no tool-level rule is needed there.File Search controller (
api/file-search):GET connectors/:id,connectors/:id/scans/:scanId,connectors/:id/tree,connectors/:id/problems,connectors/:id/runs,connectors/:id/access-rules: 404 (File Search connector not found.) for a hidden connector, before any File Search data is read.POST connectors/:id/sync: same 404. It is the one File Search write that is not admin-only (any EDITOR can start a sync), so a member could otherwise act on a connector they cannot see.GET capabilities,GET policy: workspace settings, no connector data, unchanged.Wiring:
ConnectorsModuleandFileSearchModuleimportMemberVisibilityModule, neverSettingsModule.SettingsModuleandFileSearchCoreModule. A new module spec walks its import graph (noMemberVisibilityModule,FileSearchModuleorConnectorsModule) and compilesFilesWorkerModulewithoutRolesModule. I checked that both tests fail whenMemberVisibilityModuleis added toFileSearchCoreModule.@Optional), so the app does not boot without it. In the connectors and tools controllers it is the last constructor parameter. Existing unit specs build those controllers by hand without it; there the controller takes today's path. This keeps every existing spec untouched.Type
Testing
New specs:
connectors.member-visibility.spec.ts(1511 tests): route coverage, so the suite fails when a GET route of the connectors, tools or File Search controller has no declared rule. Then the matrix: ADMIN / EDITOR / VIEWER x open / restricted x (no whitelist, role with tools, role without tools), with the caller owning one connector of each kind. 404 bodies are compared with the body an unknown id gets. Pagination with the filter, per-user sign-in, and fail closed (a failing role lookup fails the request and reads nothing). I checked that removing one route's check, or the list filter, makes 12 tests fail.connectors.member-visibility.integration.spec.ts(38 tests, real PostgreSQL throughAUDIT_TEST_DATABASE_URL, as in CI): the same matrix over HTTP with the realConnectorsService,RolesServiceandorg_settingsrows. Includes pages of a filtered list and a 500 when the role lookup throws.file-search.module.spec.ts(4 tests): the wiring described above.member-visibility.service.spec.ts: three tests for the newtoolWherehelper.Commands, in the worktree rebased on
origin/main45e0fdf (packages/backendunless noted). The real-DB runs used a database created for this PR on the local 5433 server, migrated withnpx prisma migrate deployand dropped afterwards.npx jest src/connectors src/file-search src/settings src/roles src/audit src/common/openapi.spec.ts src/auth/jwt-or-management-key.guard.boot.spec.ts --maxWorkers=4withAUDIT_TEST_DATABASE_URLset: 163 suites passed, 2 skipped, 1 failed; 5280 tests passed, 11 skipped, 1 failed. The failure isaudit/tool-performance.integration.spec.ts("weights ERROR and TIMEOUT counts"): local Postgres returns284.99999999999994where the spec expects285. This PR does not touch audit; the spec reads only the audit query.npx jest --maxWorkers=4(before the rebase): 1096 suites passed, 7 skipped, 2 failed. The failures are the one above andauth/jwt-or-management-key.guard.spec.ts, which needsDATABASE_URLpointing at a migrated database and fails in its setup locally. Neither file is touched here; CI runs both against its database.npx eslint <changed files>: 0 errors, 0 warnings.npx tsc --noEmit -p tsconfig.json: pass.node scripts/generate-cli-operations.mjs, then--check: up to date (213 operations), no diff.What
treeandproblemsreturn (unchanged, now behind the visibility check):treeis the folder tree of the index, 4 levels deep, with file count and bytes per folder.problemsis a page of files that failed or were skipped (path, status, last error, size, attempts, updated at). Neither applies the File Search folder rules by role, which only apply on the MCP tools today. So a member who can see a File Search connector sees its whole folder tree and problem list. That is unchanged by this PR and could be a follow-up.Not changed, on purpose:
/auth/connect/:connectorIdand the OAuth callbacks (plan §10, separate PR).OnboardingRedirectreadsGET /api/connectors; it is handled in the frontend PR, together with the switch in the UI.Cloud and self-hosted: identical, off by default. No workspace has the setting, so every workspace reads
open. No migration.Checklist
npm test). All pass except the two local database failures above, which are not touched here.Related Issues
Relates to #1664 and #1665 (same feature, earlier steps).