perm=a|b+c now means (a or b) and c; repeated perm args remain ANDed. Out-of-spec values (empty alternatives, chars outside the scope charset, stray %2B) are rejected with 400 instead of being silently misparsed; extra spaces between groups are tolerated. Forward endpoint 400/500 details name /auth/api/forward as origin without echoing query args. Wildcards are now filename-like: * stays within a :- or /-separated segment, ** spans segments, partial segments allowed. Slash added to allowed scope characters for path-based permissions.
97 lines
3.7 KiB
Markdown
97 lines
3.7 KiB
Markdown
# The perm argument
|
|
|
|
[API overview](../API.md) · [`/auth/api/validate`](validate.md) · [`/auth/api/forward`](../forward.md)
|
|
|
|
The perm argument is used by endpoints that validate sessions to require one or more permission scopes. It is supported by:
|
|
|
|
- [POST /auth/api/validate](validate.md)
|
|
- [GET /auth/api/forward](forward.md)
|
|
|
|
## Passing the argument
|
|
|
|
Repeat the query parameter for each required scope:
|
|
|
|
```text
|
|
?perm=myapp:read&perm=myapp:write
|
|
```
|
|
|
|
You can also pass multiple scopes in one parameter by separating them with whitespace (a literal space, i.e. `+` or `%20` in the query string):
|
|
|
|
```text
|
|
?perm=myapp:read%20myapp:write
|
|
```
|
|
|
|
Both forms produce the same result.
|
|
|
|
## Semantics
|
|
|
|
The perm argument uses **AND** semantics between groups: **every** listed scope group must be satisfied by the effective permissions for the request to succeed. If any required group is not satisfied, the endpoint returns 403.
|
|
|
|
Within a single group, use `|` to list alternatives with **OR** semantics — the group is satisfied when **any one** of the alternatives is present:
|
|
|
|
```text
|
|
?perm=myapp:read|myapp:write+myapp:login
|
|
```
|
|
|
|
This requires `myapp:login` **and** (`myapp:read` **or** `myapp:write`). No whitespace is allowed around the `|` operator.
|
|
|
|
## Syntax errors
|
|
|
|
Parsing is strict: anything out of spec is rejected with **400 Bad Request** rather than guessed at. This includes:
|
|
|
|
- Empty values or alternatives (`?perm=`, `?perm=a||b`, `?perm=|a`, `?perm=a|`)
|
|
- Whitespace around `|` (`?perm=a+|+b`)
|
|
- Characters not allowed in scopes other than the operators (space and `|`); scopes match `^[A-Za-z0-9:._~/-]+$` plus the `*` wildcard. In particular a literal `+` in the decoded value (from a `%2B` in the query string) is rejected — use `+` or `%20` to encode a space, never `%2B`.
|
|
|
|
Extra spaces between groups (leading, trailing, or repeated) are tolerated, since they can easily result from URL formatting and carry no ambiguity — they only ever add required permissions, never remove them. The `|` operator is parsed strictly: `?perm=foo&perm=|bar` is an error, never a way to make `foo` optional.
|
|
|
|
## Wildcards
|
|
|
|
Wildcards work like filenames, with `:` and `/` acting as path separators:
|
|
|
|
- `*` matches any sequence of characters **within a single segment** (it never crosses a `:` or `/`)
|
|
- `**` matches any sequence of characters, **across separators**
|
|
- Part of a segment can be wildcarded, with required text on either or both sides
|
|
|
|
```text
|
|
?perm=myapp:*
|
|
```
|
|
|
|
This matches myapp:read and myapp:write, but **not** myapp:read:all — use `myapp:**` for that. Partial wildcards like `myapp:re*` or `myapp:r*d` match myapp:read. The same applies to path-based scopes: `myapp:path:/api/*` matches myapp:path:/api/clients but not myapp:path:/api/v2/clients — use `myapp:path:/api/**` to span path segments.
|
|
|
|
## Effective permissions
|
|
|
|
The permissions available to a session are determined as follows:
|
|
|
|
1. **Role permissions** — the role assigned to the user contains a set of permission UUIDs.
|
|
2. **Org grantable permissions** — only permissions that the user's organization is allowed to grant are effective.
|
|
3. **Domain filtering** — a permission can be restricted to a specific domain via its domain field. If the request's Host header does not match that domain, the permission is excluded.
|
|
|
|
The result is the set of effective permission scopes used for the perm check. Domain-restricted permissions let you grant a scope only for a specific site or subdomain without making it global.
|
|
|
|
## Examples
|
|
|
|
Require a single permission:
|
|
|
|
```text
|
|
?perm=myapp:login
|
|
```
|
|
|
|
Require two permissions:
|
|
|
|
```text
|
|
?perm=myapp:login&perm=myapp:api
|
|
```
|
|
|
|
Require any scope under myapp:
|
|
|
|
```text
|
|
?perm=myapp:*
|
|
```
|
|
|
|
Require myapp:login and either myapp:read or myapp:write:
|
|
|
|
```text
|
|
?perm=myapp:login&perm=myapp:read|myapp:write
|
|
```
|