feat: govern legacy archive encryption

This commit is contained in:
2026-08-20 12:24:36 +02:00
parent d5b874c469
commit 75e8f864a7
19 changed files with 1102 additions and 24 deletions
@@ -76,6 +76,14 @@ class ZipPasswordScope(StrEnum):
GLOBAL = "global"
class ZipPasswordDeliveryChannel(StrEnum):
SEPARATE_MAIL = "separate_mail"
SMS = "sms"
LETTER = "letter"
PHONE = "phone"
IN_PERSON = "in_person"
class ZipPasswordMode(StrEnum):
NONE = "none"
DIRECT = "direct"
@@ -349,6 +357,13 @@ class ZipArchiveConfig(StrictModel):
password_field: str | None = None
password_scope: ZipPasswordScope = ZipPasswordScope.LOCAL
method: ZipMethod = ZipMethod.AES
password_delivery_channel: ZipPasswordDeliveryChannel = (
ZipPasswordDeliveryChannel.SEPARATE_MAIL
)
legacy_zipcrypto_acknowledged: bool = False
legacy_zipcrypto_reason: str | None = Field(default=None, max_length=1000)
legacy_zipcrypto_acknowledged_by: str | None = Field(default=None, max_length=255)
legacy_zipcrypto_acknowledged_at: str | None = Field(default=None, max_length=80)
# Compatibility fields for campaigns created by the first single-archive
# implementation. New WebUI campaigns use password_enabled/field/scope.
@@ -376,6 +391,20 @@ class ZipArchiveConfig(StrictModel):
normalized["password_scope"] = ZipPasswordScope.LOCAL.value
return normalized
@model_validator(mode="after")
def validate_legacy_zipcrypto_acknowledgement(self) -> "ZipArchiveConfig":
if self.method != ZipMethod.ZIP_STANDARD:
return self
if not self.legacy_zipcrypto_acknowledged:
raise ValueError(
"Legacy ZipCrypto requires explicit acknowledgement of its weak encryption"
)
if len((self.legacy_zipcrypto_reason or "").strip()) < 10:
raise ValueError(
"Legacy ZipCrypto requires an acknowledgement reason of at least 10 characters"
)
return self
class ZipCollectionConfig(StrictModel):
enabled: bool = False