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
286 lines
14 KiB
Gherkin
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"
|