64af753aab
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 17s
CI / quality (pull_request) Successful in 19s
CI / security (pull_request) Successful in 32s
CI / typecheck (pull_request) Successful in 36s
CI / integration_tests (pull_request) Successful in 2m40s
CI / unit_tests (pull_request) Successful in 11m42s
CI / docker (pull_request) Successful in 40s
CI / benchmark-regression (pull_request) Successful in 21m35s
CI / coverage (pull_request) Successful in 45m10s
CI / lint (push) Successful in 12s
CI / quality (push) Successful in 18s
CI / build (push) Successful in 23s
CI / security (push) Successful in 32s
CI / typecheck (push) Successful in 57s
CI / benchmark-regression (push) Has been skipped
CI / integration_tests (push) Successful in 2m43s
CI / unit_tests (push) Successful in 12m13s
CI / benchmark-publish (push) Successful in 12m19s
CI / docker (push) Successful in 1m2s
CI / coverage (push) Successful in 1h26m6s
Implemented namespace/project/plan/skill permission model with role bindings (owner/admin/editor/viewer) and default deny policy. Added enforcement hooks at CLI/service boundaries that are server-only; local mode returns permissive defaults. Includes role enums, permission check service, role matrix documentation. Includes Behave BDD scenarios, Robot integration tests, ASV benchmarks, and reference documentation. ISSUES CLOSED: #344
186 lines
5.4 KiB
Markdown
186 lines
5.4 KiB
Markdown
# Permission System
|
|
|
|
## Overview
|
|
|
|
CleverAgents implements a namespace/project/plan/skill permission model
|
|
with role bindings and a default-deny policy. An ``enforce_permission``
|
|
decorator is available for gating function calls; however, the decorator
|
|
is **not yet wired** into CLI or service call sites — that integration
|
|
is deferred to a future pass.
|
|
|
|
**Server-only enforcement**: in local mode the permission system returns
|
|
permissive defaults (all actions allowed). Server-side role evaluation
|
|
uses the role bindings registered on the ``PermissionService``.
|
|
|
|
## Roles
|
|
|
|
| Role | Description |
|
|
| -------- | ---------------------------------------- |
|
|
| `owner` | Full control — all actions on all scopes |
|
|
| `admin` | Administrative access — all actions |
|
|
| `editor` | Read, write, and execute |
|
|
| `viewer` | Read-only access |
|
|
|
|
OWNER and ADMIN currently grant the same set of actions. The
|
|
distinction is intentional — OWNER designates the resource creator or
|
|
namespace owner, while ADMIN is a delegated administrative role.
|
|
Future server-mode enforcement may differentiate them (e.g. ownership
|
|
transfer, namespace deletion) without requiring a schema change.
|
|
|
|
## Role → Action Matrix
|
|
|
|
| Action | Owner | Admin | Editor | Viewer |
|
|
| --------- | :---: | :---: | :----: | :----: |
|
|
| `read` | yes | yes | yes | yes |
|
|
| `write` | yes | yes | yes | — |
|
|
| `execute` | yes | yes | yes | — |
|
|
| `delete` | yes | yes | — | — |
|
|
| `admin` | yes | yes | — | — |
|
|
|
|
## Scopes
|
|
|
|
Permissions can be scoped to:
|
|
|
|
- **Namespace** — top-level organisational boundary
|
|
- **Project** — a project within a namespace
|
|
- **Plan** — an execution plan within a project
|
|
- **Skill** — a skill registered in the system
|
|
|
|
### Scope Hierarchy (Known Limitation)
|
|
|
|
Scopes are currently evaluated with **exact match** semantics.
|
|
A binding at `namespace` scope does not automatically cascade to
|
|
`project`, `plan`, or `skill` scopes within that namespace.
|
|
Hierarchical scope resolution (namespace > project > plan > skill)
|
|
is deferred to a future server-mode release.
|
|
|
|
## Role Bindings
|
|
|
|
A `RoleBinding` associates a *principal* (user or service identifier)
|
|
with a `PermissionRole` at a specific `PermissionScope` and scope ID.
|
|
|
|
Duplicate bindings (same principal, role, scope, and scope_id) are
|
|
rejected by `add_binding()`.
|
|
|
|
```python
|
|
from cleveragents.domain.models.core.permission import (
|
|
PermissionRole,
|
|
PermissionScope,
|
|
RoleBinding,
|
|
)
|
|
|
|
binding = RoleBinding(
|
|
principal="alice",
|
|
role=PermissionRole.EDITOR,
|
|
scope=PermissionScope.PROJECT,
|
|
scope_id="my-project",
|
|
)
|
|
```
|
|
|
|
## Permission Policy
|
|
|
|
The `PermissionPolicy` model controls the default-deny behaviour and
|
|
optional allow-overrides:
|
|
|
|
```python
|
|
from cleveragents.domain.models.core.permission import (
|
|
PermissionPolicy,
|
|
RoleBinding,
|
|
)
|
|
|
|
policy = PermissionPolicy(
|
|
default_deny=True,
|
|
allow_overrides=[binding],
|
|
)
|
|
```
|
|
|
|
## Permission Service
|
|
|
|
`PermissionService` is the entry point for checking permissions:
|
|
|
|
```python
|
|
from cleveragents.application.services.permission_service import (
|
|
PermissionService,
|
|
)
|
|
from cleveragents.domain.models.core.permission import (
|
|
PermissionAction,
|
|
PermissionScope,
|
|
)
|
|
|
|
svc = PermissionService()
|
|
check = svc.check_permission(
|
|
principal="alice",
|
|
action=PermissionAction.WRITE,
|
|
scope=PermissionScope.PROJECT,
|
|
scope_id="my-project",
|
|
)
|
|
print(check.result) # True in local mode
|
|
```
|
|
|
|
### Module-Level Default Service
|
|
|
|
A module-level default ``PermissionService`` is available so that
|
|
the ``enforce_permission`` decorator (and other call sites) share a
|
|
single service instance with pre-registered bindings:
|
|
|
|
```python
|
|
from cleveragents.application.services.permission_service import (
|
|
get_default_permission_service,
|
|
set_default_permission_service,
|
|
PermissionService,
|
|
)
|
|
|
|
# During application startup:
|
|
svc = PermissionService(bindings=[...])
|
|
set_default_permission_service(svc)
|
|
|
|
# Later — the decorator (and any code that calls
|
|
# get_default_permission_service()) will use this instance.
|
|
```
|
|
|
|
## Enforcement Decorator
|
|
|
|
The `enforce_permission` decorator is available for gating function
|
|
calls. It is not yet applied to any CLI or service entry point — call
|
|
sites will be wired in a future integration pass:
|
|
|
|
```python
|
|
from cleveragents.application.services.permission_service import (
|
|
enforce_permission,
|
|
)
|
|
from cleveragents.domain.models.core.permission import (
|
|
PermissionAction,
|
|
PermissionScope,
|
|
)
|
|
|
|
@enforce_permission(
|
|
PermissionAction.WRITE,
|
|
PermissionScope.PROJECT,
|
|
"my-project",
|
|
)
|
|
def create_resource():
|
|
...
|
|
```
|
|
|
|
In local mode the decorator is a no-op. In server mode it raises
|
|
`PermissionError` when the check fails. When no explicit `service`
|
|
is passed, the decorator uses the module-level default service (see
|
|
above).
|
|
|
|
## Local Mode Behaviour
|
|
|
|
When `CLEVERAGENTS_SERVER_MODE` is unset (or not `true`), the system
|
|
operates in **local mode**:
|
|
|
|
- `PermissionService.is_local_mode()` returns `True`
|
|
- All permission checks return `result=True`
|
|
- `get_role_bindings()` returns a synthetic OWNER binding
|
|
- The `DEFAULT_LOCAL_ROLE_MAPPING` maps `"*"` → `PermissionRole.OWNER`
|
|
|
|
## Audit Logging
|
|
|
|
Permission denials in server mode are logged as `permission_denied`
|
|
events via structlog at the WARNING level. Binding additions and
|
|
removals are logged as `binding_added` / `binding_removed` at INFO
|
|
level.
|