Files
cleveragents-core/features/permission_system.feature
Luis Mendes 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
feat(security): add permission system
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
2026-02-28 20:21:18 +00:00

286 lines
14 KiB
Gherkin

Feature: Permission system — role-based access control
As a CleverAgents operator
I want a namespace/project/plan/skill permission model
So that access to resources is controlled by role bindings with default deny
# Role enum values
Scenario: PermissionRole enum contains the expected members
Given the PermissionRole enum
Then the enum should contain "owner"
And the enum should contain "admin"
And the enum should contain "editor"
And the enum should contain "viewer"
Scenario: PermissionScope enum contains the expected members
Given the PermissionScope enum
Then the enum should contain "namespace"
And the enum should contain "project"
And the enum should contain "plan"
And the enum should contain "skill"
Scenario: PermissionAction enum contains the expected members
Given the PermissionAction enum
Then the enum should contain "read"
And the enum should contain "write"
And the enum should contain "execute"
And the enum should contain "delete"
And the enum should contain "admin"
# ── ROLE_PERMISSIONS constant ────────────────────────────────────
Scenario: OWNER role has all actions
Given the ROLE_PERMISSIONS constant
Then the "owner" role should have 5 allowed actions
Scenario: ADMIN role has all actions
Given the ROLE_PERMISSIONS constant
Then the "admin" role should have 5 allowed actions
Scenario: EDITOR role has read, write, and execute
Given the ROLE_PERMISSIONS constant
Then the "editor" role should have 3 allowed actions
Scenario: VIEWER role has only read
Given the ROLE_PERMISSIONS constant
Then the "viewer" role should have 1 allowed actions
# ── RoleBinding model ────────────────────────────────────────────
Scenario: Create a valid RoleBinding
Given a role binding for principal "alice" with role "editor" on "project" scope "proj-1"
Then the binding principal should be "alice"
And the binding role should be "editor"
And the binding scope should be "project"
And the binding scope_id should be "proj-1"
# ── PermissionPolicy model ──────────────────────────────────────
Scenario: Default policy has default_deny enabled
Given a default PermissionPolicy
Then default_deny should be true
And allow_overrides should be empty
Scenario: Policy with allow overrides
Given a PermissionPolicy with an override for "bob" as "admin" on "namespace" scope "ns-1"
Then the policy should have 1 allow override
# ── Local mode permission check ─────────────────────────────────
Scenario: Local mode always permits actions
Given a PermissionService in local mode
When I check permission for "alice" to "write" on "project" scope "proj-1"
Then the permission check result should be true
And the reason should contain "local mode"
Scenario: Local mode permits admin action
Given a PermissionService in local mode
When I check permission for "alice" to "admin" on "namespace" scope "ns-1"
Then the permission check result should be true
Scenario: Local mode permits delete action
Given a PermissionService in local mode
When I check permission for "bob" to "delete" on "plan" scope "plan-42"
Then the permission check result should be true
# ── Server mode — default deny ──────────────────────────────────
Scenario: Server mode denies action when no binding exists
Given a PermissionService in server mode with no bindings
When I check permission for "alice" to "write" on "project" scope "proj-1"
Then the permission check result should be false
And the reason should contain "denied by default-deny"
Scenario: Server mode denies action for missing role
Given a PermissionService in server mode with no bindings
When I check permission for "unknown-user" to "read" on "skill" scope "sk-99"
Then the permission check result should be false
# ── Server mode — role binding grants ───────────────────────────
Scenario: Server mode allows action when binding grants the role
Given a PermissionService in server mode
And a role binding for principal "alice" with role "editor" on "project" scope "proj-1"
When I check permission for "alice" to "read" on "project" scope "proj-1"
Then the permission check result should be true
And the reason should contain "allowed by role"
Scenario: Server mode denies action not in role permissions
Given a PermissionService in server mode
And a role binding for principal "alice" with role "viewer" on "project" scope "proj-1"
When I check permission for "alice" to "write" on "project" scope "proj-1"
Then the permission check result should be false
# ── Server mode — allow overrides ───────────────────────────────
Scenario: Allow override grants access despite no regular binding
Given a PermissionService in server mode with no bindings
And a policy override for "bob" as "admin" on "namespace" scope "ns-1"
When I check permission for "bob" to "admin" on "namespace" scope "ns-1"
Then the permission check result should be true
And the reason should contain "override"
# ── Default local role mapping ──────────────────────────────────
Scenario: DEFAULT_LOCAL_ROLE_MAPPING grants OWNER to wildcard
Given the DEFAULT_LOCAL_ROLE_MAPPING constant
Then the wildcard entry should map to "owner"
# ── PermissionCheck model ───────────────────────────────────────
Scenario: PermissionCheck captures all fields
Given a PermissionCheck for "carol" action "execute" scope "plan" scope_id "plan-7" result true reason "ok"
Then the check principal should be "carol"
And the check action should be "execute"
And the check scope should be "plan"
And the check scope_id should be "plan-7"
And the check result should be true
And the check reason should be "ok"
# ── enforce_permission decorator ────────────────────────────────
Scenario: enforce_permission decorator allows in local mode
Given a function decorated with enforce_permission for "write" on "project" scope "proj-1"
When I call the decorated function in local mode
Then it should succeed without raising
Scenario: enforce_permission decorator raises in server mode when denied
Given a function decorated with enforce_permission for "delete" on "namespace" scope "ns-1"
When I call the decorated function in server mode with no bindings
Then it should raise a PermissionError
# ── Binding management ──────────────────────────────────────────
Scenario: Add and remove bindings on the service
Given a PermissionService in server mode with no bindings
And I add a binding for "dave" as "editor" on "skill" scope "sk-1"
When I check permission for "dave" to "write" on "skill" scope "sk-1"
Then the permission check result should be true
When I remove the binding for "dave" as "editor" on "skill" scope "sk-1"
And I check permission for "dave" to "write" on "skill" scope "sk-1"
Then the permission check result should be false
Scenario: Remove non-existent binding returns false
Given a PermissionService in server mode with no bindings
When I try to remove a binding for "ghost" as "viewer" on "plan" scope "p-0"
Then the remove result should be false
# ── get_role_bindings ───────────────────────────────────────────
Scenario: get_role_bindings in local mode returns synthetic OWNER binding
Given a PermissionService in local mode
When I get role bindings for "anyone" on "project" scope "proj-1"
Then I should receive 1 binding
And the first binding role should be "owner"
Scenario: get_role_bindings in server mode returns matching bindings
Given a PermissionService in server mode with no bindings
And I add a binding for "eve" as "viewer" on "project" scope "proj-2"
And I add a binding for "eve" as "editor" on "project" scope "proj-2"
When I get role bindings for "eve" on "project" scope "proj-2"
Then I should receive 2 bindings
# ── Policy default_deny=False ───────────────────────────────────
Scenario: Server mode with default_deny disabled permits unbound action
Given a PermissionService in server mode with default_deny disabled
When I check permission for "frank" to "read" on "namespace" scope "ns-9"
Then the permission check result should be true
And the reason should contain "default-deny disabled"
# ── Scope mismatch ──────────────────────────────────────────────
Scenario: Binding on project scope does not grant access at namespace scope
Given a PermissionService in server mode
And a role binding for principal "alice" with role "editor" on "project" scope "proj-1"
When I check permission for "alice" to "read" on "namespace" scope "proj-1"
Then the permission check result should be false
Scenario: Binding on skill scope does not grant access at plan scope
Given a PermissionService in server mode
And a role binding for principal "bob" with role "admin" on "skill" scope "sk-1"
When I check permission for "bob" to "admin" on "plan" scope "sk-1"
Then the permission check result should be false
# ── Duplicate binding prevention ────────────────────────────────
Scenario: Adding a duplicate binding is rejected
Given a PermissionService in server mode with no bindings
And I add a binding for "dave" as "editor" on "project" scope "proj-1"
When I try to add a duplicate binding for "dave" as "editor" on "project" scope "proj-1"
Then the add result should be false
# ── RoleBinding validation ──────────────────────────────────────
Scenario: RoleBinding rejects empty principal
When I try to create a role binding with empty principal
Then a permission validation error should be raised
Scenario: RoleBinding rejects empty scope_id
When I try to create a role binding with empty scope_id
Then a permission validation error should be raised
# ── PermissionCheck with result=False ───────────────────────────
Scenario: PermissionCheck accepts result false
Given a PermissionCheck for "denied-user" action "write" scope "project" scope_id "proj-1" result false reason "denied"
Then the check result should be false
And the check reason should be "denied"
# ── enforce_permission with pre-loaded service ──────────────────
Scenario: enforce_permission decorator uses pre-loaded service with bindings
Given a pre-loaded permission service with "alice" as "editor" on "project" scope "proj-1" for action "write"
When I call the pre-loaded decorated function in server mode
Then it should succeed without raising
Scenario: enforce_permission decorator uses module-level default service
Given a module-level default service with "bob" as "admin" on "namespace" scope "ns-1"
And a function decorated with enforce_permission for "admin" on "namespace" scope "ns-1"
When I call the decorated function in server mode via module default
Then it should succeed without raising
# ── Input validation — check_permission ─────────────────────────
Scenario: check_permission rejects empty principal
Given a PermissionService in local mode
When I check permission with empty principal
Then a permission ValueError should mention "principal"
Scenario: check_permission rejects whitespace-only principal
Given a PermissionService in local mode
When I check permission with whitespace-only principal
Then a permission ValueError should mention "principal"
Scenario: check_permission rejects empty scope_id
Given a PermissionService in local mode
When I check permission with empty scope_id
Then a permission ValueError should mention "scope_id"
Scenario: check_permission rejects whitespace-only scope_id
Given a PermissionService in local mode
When I check permission with whitespace-only scope_id
Then a permission ValueError should mention "scope_id"
Scenario: check_permission strips leading/trailing whitespace from principal
Given a PermissionService in local mode
When I check permission for " alice " to "read" on "project" scope "proj-1"
Then the permission check result should be true
Scenario: check_permission strips leading/trailing whitespace from scope_id
Given a PermissionService in local mode
When I check permission for "alice" to "read" on "project" scope " proj-1 "
Then the permission check result should be true
# ── Input validation — get_role_bindings ────────────────────────
Scenario: get_role_bindings rejects empty principal
Given a PermissionService in local mode
When I get role bindings with empty principal
Then a permission ValueError should mention "principal"
Scenario: get_role_bindings rejects whitespace-only scope_id
Given a PermissionService in local mode
When I get role bindings with whitespace-only scope_id
Then a permission ValueError should mention "scope_id"