mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
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:
@@ -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}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user