feat: govern campaign archive encryption

This commit is contained in:
2026-08-20 12:12:14 +02:00
parent 8fcc12dbb2
commit be5e3a7d72
11 changed files with 1070 additions and 5 deletions
@@ -9,8 +9,10 @@ from sqlalchemy.orm import Session
from govoplan_policy.backend.db.models import PolicyOverride
POLICY_SCOPE_TYPES = frozenset({"system", "tenant", "group", "user"})
POLICY_FAMILIES = frozenset({"definition", "distribution_channels", "view"})
POLICY_SCOPE_TYPES = frozenset({"system", "tenant", "group", "user", "campaign"})
POLICY_FAMILIES = frozenset(
{"campaign_archive_encryption", "definition", "distribution_channels", "view"}
)
class PolicyOverrideError(ValueError):
@@ -22,7 +24,7 @@ def normalize_policy_target(policy_family: str, target_key: str) -> tuple[str, s
target = target_key.strip().casefold()
if family not in POLICY_FAMILIES:
raise PolicyOverrideError(
"Policy family must be definition, distribution_channels, or view"
"Policy family must be campaign_archive_encryption, definition, distribution_channels, or view"
)
if not target or len(target) > 120:
raise PolicyOverrideError("Policy target must contain 1 to 120 characters")
@@ -40,7 +42,9 @@ def normalize_policy_scope(
clean_scope = scope_type.strip().casefold()
clean_id = str(scope_id or "").strip() or None
if clean_scope not in POLICY_SCOPE_TYPES:
raise PolicyOverrideError("Policy scope must be system, tenant, group, or user")
raise PolicyOverrideError(
"Policy scope must be system, tenant, group, user, or campaign"
)
if clean_scope == "system":
if clean_id is not None:
raise PolicyOverrideError("System policy cannot declare a scope ID")
@@ -142,6 +146,7 @@ def resolution_policy_overrides(
tenant_id: str,
group_ids: Iterable[str] = (),
user_ids: Iterable[str] = (),
campaign_ids: Iterable[str] = (),
) -> tuple[PolicyOverride, ...]:
family = policy_family.strip().casefold()
targets = tuple(
@@ -151,6 +156,9 @@ def resolution_policy_overrides(
)
groups = tuple(sorted({str(value) for value in group_ids if str(value)}))
users = tuple(sorted({str(value) for value in user_ids if str(value)}))
campaigns = tuple(
sorted({str(value) for value in campaign_ids if str(value)})
)
scope_filters = [
PolicyOverride.scope_key == "system",
PolicyOverride.scope_key == f"tenant:{tenant_id}",
@@ -167,6 +175,15 @@ def resolution_policy_overrides(
[f"user:{tenant_id}:{user_id}" for user_id in users]
)
)
if campaigns:
scope_filters.append(
PolicyOverride.scope_key.in_(
[
f"campaign:{tenant_id}:{campaign_id}"
for campaign_id in campaigns
]
)
)
rows = (
session.query(PolicyOverride)
.filter(
@@ -177,7 +194,13 @@ def resolution_policy_overrides(
.all()
)
target_order = {target: index for index, target in enumerate(targets)}
scope_order = {"system": 0, "tenant": 1, "group": 2, "user": 3}
scope_order = {
"system": 0,
"tenant": 1,
"group": 2,
"user": 2,
"campaign": 3,
}
return tuple(
sorted(
rows,