fix: enforce role authority for user management (#616)

Centralize role-authority enforcement across user management, role management, configuration import, SCIM, GDPR, and MFA mutations. Add regression coverage for delegated custom roles and protect higher-privilege accounts from reset, deletion, or takeover.
This commit is contained in:
SnapOtter
2026-07-22 01:23:15 +08:00
committed by GitHub
parent 129e42b95c
commit 1f8a42e548
36 changed files with 4266 additions and 1584 deletions
+9 -5
View File
@@ -14,7 +14,7 @@ SCIM provisioning requires an **enterprise** license with the `scim` feature. It
- A running SnapOtter instance reachable at a public URL
- An enterprise license key with the `scim` feature
- Admin access to SnapOtter (the `users:manage` permission is required to generate or revoke a SCIM token)
- A built-in SnapOtter `admin` account with its full effective permission set. A delegated custom role or an admin API key missing any admin permission cannot generate or revoke the global SCIM token.
- Admin access to your identity provider's provisioning settings
## Quick start {#quick-start}
@@ -31,7 +31,7 @@ The response contains the token. Save it immediately; it cannot be retrieved aga
```json
{
"token": "a1b2c3d4e5f6...",
"token": "so_scim_v2_a1b2c3d4e5f6...",
"message": "Save this token - it cannot be retrieved again"
}
```
@@ -46,15 +46,19 @@ SCIM endpoints use a dedicated Bearer token, separate from user sessions and API
### Generating a token {#generating-a-token}
`POST /api/v1/enterprise/scim/token` generates a new SCIM token. This endpoint requires a valid session with the `users:manage` permission.
`POST /api/v1/enterprise/scim/token` generates a new SCIM token. Because the token can provision and mutate users across the instance, this endpoint requires the built-in `admin` role with the complete effective admin permission set. Holding `users:manage` in a custom role is not sufficient.
The token is returned in plaintext exactly once. SnapOtter stores only a scrypt hash. If you lose the token, revoke it and generate a new one.
Only one SCIM token is active at a time. Generating a new token replaces the previous one.
::: warning Token reissue after upgrade
Legacy unversioned SCIM tokens are rejected. After upgrading to a release that issues `so_scim_v2_...` tokens, generate a new token and update your identity provider before resuming provisioning.
:::
### Revoking a token {#revoking-a-token}
`DELETE /api/v1/enterprise/scim/token` revokes the current SCIM token. This endpoint also requires `users:manage`.
`DELETE /api/v1/enterprise/scim/token` revokes the current SCIM token. It has the same full built-in admin requirement as token generation.
### Rate limiting {#rate-limiting}
@@ -276,7 +280,7 @@ The SCIM request did not include an `Authorization: Bearer <token>` header. Chec
### 401 "Invalid token" {#_401-invalid-token}
The token does not match the stored hash. This happens if the token was revoked and regenerated. Update the token in your IdP's provisioning settings.
The token is malformed, uses the retired unversioned format, or does not match the stored hash. Generate a current `so_scim_v2_...` token and update the token in your IdP's provisioning settings.
### 401 "SCIM not configured" {#_401-scim-not-configured}
+10 -8
View File
@@ -79,12 +79,12 @@ All 17 permissions. Full control over the instance.
| `pipelines:all` | View and manage all users' pipelines |
| `settings:read` | View instance settings |
| `settings:write` | Modify instance settings |
| `users:manage` | Create, update, and delete user accounts |
| `users:manage` | Create and manage user accounts within the actor's authority boundary |
| `teams:manage` | Create, update, and delete teams |
| `features:manage` | Install and manage AI feature bundles |
| `system:health` | Access health and readiness endpoints |
| `audit:read` | View the audit log and list roles |
| `compliance:manage` | Manage GDPR lifecycle and compliance features |
| `compliance:manage` | Manage GDPR lifecycle and compliance features; destructive user operations remain authority-bounded |
| `webhooks:manage` | Configure outbound webhooks |
| `security:manage` | Manage security settings (IP allowlist, SSO enforcement) |
@@ -107,15 +107,17 @@ curl -X POST http://localhost:1349/api/v1/roles \
Role names must be 2-30 characters, lowercase alphanumeric with hyphens and underscores.
### Admin-reserved permissions {#admin-reserved-permissions}
### Delegated administration boundaries {#delegated-administration-boundaries}
Three permissions are reserved for built-in roles and cannot be assigned to custom roles:
All 17 permissions can be delegated through custom roles, but an administrative permission does not make that role equivalent to the built-in `admin` role. User mutations authorized by `users:manage`, destructive operations authorized by `compliance:manage`, and custom-role management authorized by `security:manage` are bounded by the actor's current authority:
- `compliance:manage`
- `webhooks:manage`
- `security:manage`
- Built-in roles follow `admin` > `editor` > `user`; custom roles are below built-in roles.
- The target's permissions must be contained by the actor's **effective** permissions. A scoped API key therefore cannot exercise permissions omitted from its scope.
- A target role's tool access must be contained by the actor's own tool access.
- A disabled account is checked against its original role when that role is recorded as `disabled:<original-role>`.
- Deleting a custom role also requires authority to assign the built-in `user` fallback; disabled members remain disabled as `disabled:user`.
The roles API rejects any request that includes these permissions. Only the built-in `admin` role has access to them.
Global credentials and configuration are stricter: issuing or revoking the SCIM token and importing instance configuration require the built-in `admin` role with complete effective admin authority.
### Tool-level permissions {#tool-level-permissions}