forked from cleveragents/cleveragents-core
b0ab61134d
Implement server-mode services for multi-tenant deployments: - TokenAuthClient: SHA-256 hashed bearer-token authentication with configurable TTL, register/revoke/validate operations, and constant-time comparison for timing side-channel protection - AuthorizationService: Namespace-scoped role-based access control with viewer/member/admin/owner hierarchy and grant/revoke/check_access - NamespaceService: In-memory namespace registry with list/show/members endpoints backing _cleveragents/namespace/* A2A extension methods - HealthService: Aggregated health-check probe registry returning composite healthy/unhealthy status with per-service details - DiagnosticsService: Runtime diagnostics collector (Python version, platform, uptime, loaded modules, custom checks) - Server DB tables: server_users, server_tokens (SHA-256 hashed), namespace_acls with Alembic migration s1_001 - Facade wiring: namespace/health/diagnostics handlers dispatch to real services when registered, fall back to stubs otherwise - Behave BDD: 23 scenarios covering all services and facade wiring - Robot integration: 11 test cases with helper script ISSUES CLOSED: #927
160 lines
6.8 KiB
Gherkin
160 lines
6.8 KiB
Gherkin
@phase2 @a2a @server
|
|
Feature: Server-mode authentication, authorization, and namespace service
|
|
As a server operator
|
|
I want token-based auth, namespace-scoped authorization, and namespace queries
|
|
So that multi-tenant server deployments are secure and queryable
|
|
|
|
# -----------------------------------------------------------------------
|
|
# TokenAuthClient — authentication
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: TokenAuthClient authenticates a valid token
|
|
Given a TokenAuthClient with default TTL
|
|
And a registered bearer token "tok_server_test_01"
|
|
When I authenticate with token "tok_server_test_01"
|
|
Then authentication should succeed
|
|
|
|
Scenario: TokenAuthClient rejects an unknown token
|
|
Given a TokenAuthClient with default TTL
|
|
When I authenticate with token "tok_unknown_999"
|
|
Then authentication should fail
|
|
|
|
Scenario: TokenAuthClient rejects an expired token
|
|
Given a TokenAuthClient with TTL of 1 second
|
|
And a registered bearer token "tok_expiring"
|
|
And I wait for the token to expire
|
|
When I authenticate with token "tok_expiring"
|
|
Then authentication should fail
|
|
|
|
Scenario: TokenAuthClient validates a registered token
|
|
Given a TokenAuthClient with default TTL
|
|
And a registered bearer token "tok_validate_test"
|
|
When I validate token "tok_validate_test"
|
|
Then token validation should succeed
|
|
|
|
Scenario: TokenAuthClient revokes a token
|
|
Given a TokenAuthClient with default TTL
|
|
And a registered bearer token "tok_to_revoke"
|
|
When I revoke token "tok_to_revoke"
|
|
Then the revocation should succeed
|
|
And authenticating with revoked token "tok_to_revoke" should fail
|
|
|
|
Scenario: TokenAuthClient rejects empty token in authenticate
|
|
Given a TokenAuthClient with default TTL
|
|
When I authenticate with an empty token
|
|
Then a ValueError should be raised for empty token in server auth
|
|
|
|
Scenario: TokenAuthClient reports active token count
|
|
Given a TokenAuthClient with default TTL
|
|
And a registered bearer token "tok_count_1"
|
|
And a registered bearer token "tok_count_2"
|
|
Then active token count should be 2
|
|
|
|
# -----------------------------------------------------------------------
|
|
# AuthorizationService — namespace-scoped access
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: AuthorizationService grants and checks access
|
|
Given an AuthorizationService
|
|
And user "u-1" is granted "admin" role in namespace "default"
|
|
When I check "write" access for "u-1" in "default"
|
|
Then access check should pass
|
|
|
|
Scenario: AuthorizationService denies insufficient role
|
|
Given an AuthorizationService
|
|
And user "u-2" is granted "viewer" role in namespace "proj-a"
|
|
When I check "write" access for "u-2" in "proj-a"
|
|
Then an AuthorizationError should be raised
|
|
|
|
Scenario: AuthorizationService denies unknown user
|
|
Given an AuthorizationService
|
|
When I check "read" access for "u-unknown" in "default"
|
|
Then an AuthorizationError should be raised
|
|
|
|
Scenario: AuthorizationService revokes access
|
|
Given an AuthorizationService
|
|
And user "u-3" is granted "member" role in namespace "ns-x"
|
|
When I revoke access for "u-3" in "ns-x"
|
|
Then the revocation should report success
|
|
And checking "read" access for "u-3" in "ns-x" should raise AuthorizationError
|
|
|
|
Scenario: AuthorizationService lists user grants
|
|
Given an AuthorizationService
|
|
And user "u-4" is granted "viewer" role in namespace "ns-a"
|
|
And user "u-4" is granted "admin" role in namespace "ns-b"
|
|
When I list grants for "u-4"
|
|
Then the grants list should contain 2 entries
|
|
|
|
# -----------------------------------------------------------------------
|
|
# NamespaceService — list / show / members
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: NamespaceService lists registered namespaces
|
|
Given a NamespaceService with namespaces "default" and "staging"
|
|
When I list namespaces
|
|
Then the namespace list should contain 2 entries
|
|
|
|
Scenario: NamespaceService shows namespace details
|
|
Given a NamespaceService with namespace "production" owned by "u-owner"
|
|
When I show namespace "production"
|
|
Then the namespace details should include owner "u-owner"
|
|
|
|
Scenario: NamespaceService returns members of a namespace
|
|
Given a NamespaceService with namespace "team-ns" having 3 members
|
|
When I list members of namespace "team-ns"
|
|
Then the members list should contain 3 entries
|
|
|
|
Scenario: NamespaceService raises on unknown namespace show
|
|
Given a NamespaceService with namespace "only-one"
|
|
When I show namespace "nonexistent"
|
|
Then a ResourceNotFoundError should be raised
|
|
|
|
Scenario: NamespaceService raises on unknown namespace members
|
|
Given a NamespaceService with namespace "only-one"
|
|
When I list members of namespace "nonexistent"
|
|
Then a ResourceNotFoundError should be raised
|
|
|
|
# -----------------------------------------------------------------------
|
|
# HealthService — health check aggregation
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: HealthService returns healthy when no probes fail
|
|
Given a HealthService with a healthy probe
|
|
When I run the health check
|
|
Then the overall status should be "healthy"
|
|
|
|
Scenario: HealthService returns unhealthy when a probe fails
|
|
Given a HealthService with an unhealthy probe
|
|
When I run the health check
|
|
Then the overall status should be "unhealthy"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# DiagnosticsService — runtime diagnostics
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: DiagnosticsService returns runtime info
|
|
Given a DiagnosticsService
|
|
When I run diagnostics
|
|
Then the diagnostics result should include python version
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Facade wiring — namespace handlers use real services
|
|
# -----------------------------------------------------------------------
|
|
|
|
Scenario: Facade dispatches namespace list to NamespaceService
|
|
Given a facade wired with a NamespaceService containing "default"
|
|
When I dispatch "_cleveragents/namespace/list"
|
|
Then the response should contain 1 namespace
|
|
|
|
Scenario: Facade dispatches health check to HealthService
|
|
Given a facade wired with a HealthService
|
|
When I dispatch "_cleveragents/health/check"
|
|
Then the server facade response status should be "ok"
|
|
And the server response data status should be "healthy"
|
|
|
|
Scenario: Facade dispatches diagnostics run to DiagnosticsService
|
|
Given a facade wired with a DiagnosticsService
|
|
When I dispatch "_cleveragents/diagnostics/run"
|
|
Then the server facade response status should be "ok"
|
|
And the server response data should include python info
|