feat: govern legacy archive encryption
This commit is contained in:
@@ -770,6 +770,14 @@ def validate_version(
|
||||
except HTTPException:
|
||||
raise
|
||||
except CampaignPersistenceError as exc:
|
||||
if _is_archive_encryption_denial(exc):
|
||||
_audit_archive_encryption_denial(
|
||||
session, principal, version_id=version_id, error=exc
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
@@ -883,6 +891,9 @@ def build_version(
|
||||
"attachment_reuse": _attachment_reuse_audit_evidence(
|
||||
result.get("attachment_reuse")
|
||||
),
|
||||
"archive_encryption": _archive_encryption_audit_evidence(
|
||||
result.get("archive_encryption")
|
||||
),
|
||||
},
|
||||
commit=True,
|
||||
)
|
||||
@@ -891,6 +902,18 @@ def build_version(
|
||||
include_diagnostics=has_scope(principal, "campaigns:diagnostic:read"),
|
||||
)
|
||||
except CampaignPersistenceError as exc:
|
||||
if _is_archive_encryption_denial(exc):
|
||||
_audit_archive_encryption_denial(
|
||||
session, principal, version_id=version_id, error=exc
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=(
|
||||
status.HTTP_403_FORBIDDEN
|
||||
if "Missing scope:" in str(exc)
|
||||
else status.HTTP_422_UNPROCESSABLE_CONTENT
|
||||
),
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
@@ -915,6 +938,38 @@ def build_version(
|
||||
) from exc
|
||||
|
||||
|
||||
def _is_archive_encryption_denial(error: Exception) -> bool:
|
||||
message = str(error).casefold()
|
||||
return any(
|
||||
marker in message
|
||||
for marker in (
|
||||
"archive-encryption",
|
||||
"archive encryption",
|
||||
"zipcrypto",
|
||||
"password-delivery channel",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _audit_archive_encryption_denial(
|
||||
session: Session,
|
||||
principal: ApiPrincipal,
|
||||
*,
|
||||
version_id: str,
|
||||
error: Exception,
|
||||
) -> None:
|
||||
session.rollback()
|
||||
audit_from_principal(
|
||||
session,
|
||||
principal,
|
||||
action="campaign.archive_encryption_denied",
|
||||
object_type="campaign_version",
|
||||
object_id=version_id,
|
||||
details={"reason": str(error)},
|
||||
commit=True,
|
||||
)
|
||||
|
||||
|
||||
def _residual_file_audit_evidence(value: object) -> dict[str, object]:
|
||||
if not isinstance(value, dict):
|
||||
return {}
|
||||
@@ -945,6 +1000,25 @@ def _attachment_reuse_audit_evidence(value: object) -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
def _archive_encryption_audit_evidence(value: object) -> dict[str, object]:
|
||||
if not isinstance(value, dict):
|
||||
return {}
|
||||
policy = value.get("policy")
|
||||
archives = [
|
||||
item for item in (value.get("archives") or []) if isinstance(item, dict)
|
||||
]
|
||||
return {
|
||||
"policy_hash": policy.get("policy_hash")
|
||||
if isinstance(policy, dict)
|
||||
else None,
|
||||
"archive_count": len(archives),
|
||||
"legacy_zipcrypto_count": sum(
|
||||
1 for item in archives if item.get("method") == "zip_standard"
|
||||
),
|
||||
"archive_sha256": [item.get("archive_sha256") for item in archives],
|
||||
}
|
||||
|
||||
|
||||
def _review_decision_audit_evidence(
|
||||
version: CampaignVersion,
|
||||
) -> dict[str, object]:
|
||||
|
||||
Reference in New Issue
Block a user