Skip to content

Commit 6d7c810

Browse files
committed
refactor: isolate authority admin HTTP routes
1 parent 9c90829 commit 6d7c810

2 files changed

Lines changed: 155 additions & 89 deletions

File tree

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"""Authenticated entitlement and temporary-elevation administration routes."""
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Awaitable, Callable, Mapping
6+
from typing import Any
7+
8+
from pydantic import BaseModel, ConfigDict, Field
9+
from starlette.requests import Request
10+
from starlette.responses import JSONResponse, Response
11+
from starlette.routing import Route
12+
13+
from agentnet.authorization.elevation import ElevationRequest, ElevationService
14+
from agentnet.authorization.evidence import IssuanceAuthority, SignedAuthorityCommand
15+
from agentnet.authorization.policy import HumanEntitlement
16+
from agentnet.core.app import CommunicationCore
17+
from agentnet.identity.actors import VerifiedActor
18+
from agentnet.security.signatures import canonical_digest
19+
20+
21+
BodyAndActor = Callable[
22+
[Request, CommunicationCore],
23+
Awaitable[tuple[bytes, VerifiedActor]],
24+
]
25+
DecisionIssuer = Callable[..., IssuanceAuthority]
26+
27+
28+
class EntitlementIssueBody(BaseModel):
29+
model_config = ConfigDict(extra="forbid")
30+
31+
entitlement: HumanEntitlement
32+
command: SignedAuthorityCommand
33+
34+
35+
class EntitlementRevokeBody(BaseModel):
36+
model_config = ConfigDict(extra="forbid")
37+
38+
command: SignedAuthorityCommand
39+
40+
41+
class ElevationIssueBody(BaseModel):
42+
model_config = ConfigDict(extra="forbid")
43+
44+
request: ElevationRequest
45+
independent_approvals: tuple[dict[str, Any], ...] = Field(
46+
min_length=1,
47+
max_length=5,
48+
)
49+
50+
51+
def create_authority_admin_routes(
52+
core: CommunicationCore,
53+
body_and_actor: BodyAndActor,
54+
elevations: ElevationService,
55+
issue_decision: DecisionIssuer,
56+
response_headers: Mapping[str, str],
57+
) -> list[Route]:
58+
"""Mount only entitlement and elevation administration routes."""
59+
60+
async def issue_entitlement(request: Request) -> Response:
61+
body, actor = await body_and_actor(request, core)
62+
parsed = EntitlementIssueBody.model_validate_json(body)
63+
resource, mutation = core.policy.entitlement_issuance_binding(
64+
parsed.entitlement,
65+
reason=parsed.command.reason,
66+
)
67+
authority = issue_decision(
68+
core,
69+
actor=actor,
70+
action="authorization.entitlement.issue",
71+
resource=resource,
72+
request_digest=canonical_digest(mutation),
73+
)
74+
result = core.policy.add_entitlement(
75+
parsed.entitlement,
76+
command=parsed.command,
77+
authority=authority,
78+
)
79+
return JSONResponse(
80+
result.model_dump(mode="json"),
81+
status_code=201,
82+
headers=response_headers,
83+
)
84+
85+
async def revoke_entitlement(request: Request) -> Response:
86+
body, actor = await body_and_actor(request, core)
87+
parsed = EntitlementRevokeBody.model_validate_json(body)
88+
entitlement_id = request.path_params["entitlement_id"]
89+
resource, mutation = core.policy.entitlement_revocation_binding(
90+
entitlement_id,
91+
expected_entity_revision=parsed.command.expected_entity_revision,
92+
reason=parsed.command.reason,
93+
)
94+
authority = issue_decision(
95+
core,
96+
actor=actor,
97+
action="authorization.entitlement.revoke",
98+
resource=resource,
99+
request_digest=canonical_digest(mutation),
100+
)
101+
core.policy.revoke_entitlement(
102+
entitlement_id,
103+
command=parsed.command,
104+
authority=authority,
105+
)
106+
return JSONResponse(
107+
{"entitlement_id": entitlement_id, "revoked": True},
108+
headers=response_headers,
109+
)
110+
111+
async def issue_elevation(request: Request) -> Response:
112+
body, actor = await body_and_actor(request, core)
113+
parsed = ElevationIssueBody.model_validate_json(body)
114+
resource, mutation = elevations.authority_binding(parsed.request)
115+
authority = issue_decision(
116+
core,
117+
actor=actor,
118+
action="authorization.elevation.request",
119+
resource=resource,
120+
request_digest=mutation["request_digest"],
121+
)
122+
result = elevations.issue(
123+
parsed.request,
124+
beneficiary=actor,
125+
authority=authority,
126+
approvals=parsed.independent_approvals,
127+
)
128+
return JSONResponse(
129+
result.model_dump(mode="json"),
130+
status_code=201,
131+
headers=response_headers,
132+
)
133+
134+
return [
135+
Route("/v1/admin/entitlements", issue_entitlement, methods=["POST"]),
136+
Route(
137+
"/v1/admin/entitlements/{entitlement_id}/revoke",
138+
revoke_entitlement,
139+
methods=["POST"],
140+
),
141+
Route("/v1/admin/elevations", issue_elevation, methods=["POST"]),
142+
]
143+
144+
145+
__all__ = ["create_authority_admin_routes"]

src/agentnet/identity_admin_http.py

Lines changed: 10 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
from starlette.routing import Route
2020

2121
from agentnet.approval.service import IndependentApprovalVerifier
22-
from agentnet.authorization.elevation import ElevationRequest
22+
from agentnet.authorization.admin_http import create_authority_admin_routes
2323
from agentnet.authorization.evidence import IssuanceAuthority, SignedAuthorityCommand
24-
from agentnet.authorization.policy import AuthorizationRequest, HumanEntitlement
24+
from agentnet.authorization.policy import AuthorizationRequest
2525
from agentnet.core.app import CommunicationCore
2626
from agentnet.errors import AuthenticationError, GateBlocked, ValidationError
2727
from agentnet.identity.revocation import HarnessRevocationRequest, HarnessRevocationService
@@ -48,26 +48,6 @@ def _headers() -> dict[str, str]:
4848
}
4949

5050

51-
class EntitlementIssueBody(BaseModel):
52-
model_config = ConfigDict(extra="forbid")
53-
54-
entitlement: HumanEntitlement
55-
command: SignedAuthorityCommand
56-
57-
58-
class EntitlementRevokeBody(BaseModel):
59-
model_config = ConfigDict(extra="forbid")
60-
61-
command: SignedAuthorityCommand
62-
63-
64-
class ElevationIssueBody(BaseModel):
65-
model_config = ConfigDict(extra="forbid")
66-
67-
request: ElevationRequest
68-
independent_approvals: tuple[dict[str, Any], ...] = Field(min_length=1, max_length=5)
69-
70-
7151
class HarnessRevocationPrepareBody(BaseModel):
7252
model_config = ConfigDict(extra="forbid")
7353

@@ -206,69 +186,6 @@ def create_identity_admin_routes(
206186
task_grants=core.grants,
207187
)
208188

209-
async def issue_entitlement(request: Request) -> Response:
210-
body, actor = await body_and_actor(request, core)
211-
parsed = EntitlementIssueBody.model_validate_json(body)
212-
resource, mutation = core.policy.entitlement_issuance_binding(
213-
parsed.entitlement,
214-
reason=parsed.command.reason,
215-
)
216-
authority = _decision(
217-
core,
218-
actor=actor,
219-
action="authorization.entitlement.issue",
220-
resource=resource,
221-
request_digest=canonical_digest(mutation),
222-
)
223-
result = core.policy.add_entitlement(
224-
parsed.entitlement,
225-
command=parsed.command,
226-
authority=authority,
227-
)
228-
return JSONResponse(result.model_dump(mode="json"), status_code=201, headers=_headers())
229-
230-
async def revoke_entitlement(request: Request) -> Response:
231-
body, actor = await body_and_actor(request, core)
232-
parsed = EntitlementRevokeBody.model_validate_json(body)
233-
entitlement_id = request.path_params["entitlement_id"]
234-
resource, mutation = core.policy.entitlement_revocation_binding(
235-
entitlement_id,
236-
expected_entity_revision=parsed.command.expected_entity_revision,
237-
reason=parsed.command.reason,
238-
)
239-
authority = _decision(
240-
core,
241-
actor=actor,
242-
action="authorization.entitlement.revoke",
243-
resource=resource,
244-
request_digest=canonical_digest(mutation),
245-
)
246-
core.policy.revoke_entitlement(
247-
entitlement_id,
248-
command=parsed.command,
249-
authority=authority,
250-
)
251-
return JSONResponse({"entitlement_id": entitlement_id, "revoked": True}, headers=_headers())
252-
253-
async def issue_elevation(request: Request) -> Response:
254-
body, actor = await body_and_actor(request, core)
255-
parsed = ElevationIssueBody.model_validate_json(body)
256-
resource, mutation = elevations.authority_binding(parsed.request)
257-
authority = _decision(
258-
core,
259-
actor=actor,
260-
action="authorization.elevation.request",
261-
resource=resource,
262-
request_digest=mutation["request_digest"],
263-
)
264-
result = elevations.issue(
265-
parsed.request,
266-
beneficiary=actor,
267-
authority=authority,
268-
approvals=parsed.independent_approvals,
269-
)
270-
return JSONResponse(result.model_dump(mode="json"), status_code=201, headers=_headers())
271-
272189
async def prepare_harness_revocation(request: Request) -> Response:
273190
body, actor = await body_and_actor(request, core)
274191
parsed = HarnessRevocationPrepareBody.model_validate_json(body)
@@ -533,10 +450,14 @@ async def expire_mailbox_due(request: Request) -> Response:
533450
)
534451
return JSONResponse({"expired": count}, headers=_headers())
535452

536-
routes = [
537-
Route("/v1/admin/entitlements", issue_entitlement, methods=["POST"]),
538-
Route("/v1/admin/entitlements/{entitlement_id}/revoke", revoke_entitlement, methods=["POST"]),
539-
Route("/v1/admin/elevations", issue_elevation, methods=["POST"]),
453+
routes = create_authority_admin_routes(
454+
core,
455+
body_and_actor,
456+
elevations,
457+
_decision,
458+
_headers(),
459+
)
460+
routes += [
540461
Route("/v1/admin/harness-revocations/prepare", prepare_harness_revocation, methods=["POST"]),
541462
Route("/v1/admin/harness-revocations/commit", commit_harness_revocation, methods=["POST"]),
542463
Route("/v1/admin/workloads", register_workload, methods=["POST"]),

0 commit comments

Comments
 (0)