Build authorization-gated tenancy evidence harness
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0260c-4067-7052-9647-ad000d576e38
This commit is contained in:
parent
2c8e1d41ad
commit
beab2a04d1
32 changed files with 1816 additions and 11 deletions
91
src/whitehat_security/fixtures.py
Normal file
91
src/whitehat_security/fixtures.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .differential import DifferentialProbe
|
||||
from .model import Response
|
||||
|
||||
|
||||
@dataclass
|
||||
class FixtureService:
|
||||
enforce_tenant: bool
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.objects = {
|
||||
"object-a": {"id": "object-a", "tenant": "tenant-a", "label": "fixture-a"},
|
||||
"object-b": {"id": "object-b", "tenant": "tenant-b", "label": "fixture-b"},
|
||||
}
|
||||
|
||||
def snapshot_b(self) -> object:
|
||||
return deepcopy(self.objects.get("object-b"))
|
||||
|
||||
def read(self, actor_tenant: str, object_id: str) -> Response:
|
||||
item = self.objects.get(object_id)
|
||||
if item is None or (self.enforce_tenant and item["tenant"] != actor_tenant):
|
||||
return Response(404, {"error": "not_found"})
|
||||
return Response(200, deepcopy(item))
|
||||
|
||||
def list_for(self, actor_tenant: str, requested_tenant: str) -> Response:
|
||||
tenant = actor_tenant if self.enforce_tenant else requested_tenant
|
||||
items = [deepcopy(item) for item in self.objects.values() if item["tenant"] == tenant]
|
||||
return Response(200, {"items": items})
|
||||
|
||||
def create_for(self, actor_tenant: str, requested_tenant: str, object_id: str) -> Response:
|
||||
if self.enforce_tenant and actor_tenant != requested_tenant:
|
||||
return Response(403, {"error": "forbidden"})
|
||||
self.objects[object_id] = {
|
||||
"id": object_id,
|
||||
"tenant": requested_tenant,
|
||||
"label": "fixture-created",
|
||||
}
|
||||
return Response(201, {"id": object_id, "tenant": requested_tenant})
|
||||
|
||||
def update(self, actor_tenant: str, object_id: str) -> Response:
|
||||
item = self.objects.get(object_id)
|
||||
if item is None or (self.enforce_tenant and item["tenant"] != actor_tenant):
|
||||
return Response(404, {"error": "not_found"})
|
||||
item["label"] = "fixture-mutated"
|
||||
return Response(200, deepcopy(item))
|
||||
|
||||
def delete(self, actor_tenant: str, object_id: str) -> Response:
|
||||
item = self.objects.get(object_id)
|
||||
if item is None or (self.enforce_tenant and item["tenant"] != actor_tenant):
|
||||
return Response(404, {"error": "not_found"})
|
||||
del self.objects[object_id]
|
||||
return Response(200, {"deleted": object_id})
|
||||
|
||||
|
||||
def probe_suite(service: FixtureService) -> list[DifferentialProbe]:
|
||||
markers = ("object-b", "tenant-b", "fixture-b", "fixture-mutated")
|
||||
return [
|
||||
DifferentialProbe(
|
||||
"read-by-id", "read", lambda: service.read("tenant-b", "object-b"),
|
||||
lambda: service.read("tenant-a", "object-b"),
|
||||
lambda: service.read("tenant-a", "absent"), markers,
|
||||
),
|
||||
DifferentialProbe(
|
||||
"list-filter", "read", lambda: service.list_for("tenant-b", "tenant-b"),
|
||||
lambda: service.list_for("tenant-a", "tenant-b"),
|
||||
lambda: Response(200, {"items": []}), markers, hide_existence=False,
|
||||
),
|
||||
DifferentialProbe(
|
||||
"create-attributed-to-b", "create",
|
||||
lambda: Response(201, {"id": "owner-control"}),
|
||||
lambda: service.create_for("tenant-a", "tenant-b", "object-created"),
|
||||
lambda: Response(403, {"error": "forbidden"}), markers + ("object-created",),
|
||||
state=lambda: deepcopy(service.objects), hide_existence=False,
|
||||
),
|
||||
DifferentialProbe(
|
||||
"update-b", "update", lambda: Response(200, service.snapshot_b()),
|
||||
lambda: service.update("tenant-a", "object-b"),
|
||||
lambda: service.read("tenant-a", "absent"), markers,
|
||||
state=service.snapshot_b,
|
||||
),
|
||||
DifferentialProbe(
|
||||
"delete-b", "delete", lambda: Response(200, service.snapshot_b()),
|
||||
lambda: service.delete("tenant-a", "object-b"),
|
||||
lambda: service.read("tenant-a", "absent"), markers,
|
||||
state=service.snapshot_b,
|
||||
),
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue