feat: protect postbox message content
This commit is contained in:
@@ -2,6 +2,8 @@ from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from datetime import timedelta
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -51,6 +53,7 @@ from govoplan_postbox.backend.db.models import (
|
||||
PostboxTemplate,
|
||||
PostboxTemplateRevision,
|
||||
)
|
||||
from govoplan_postbox.backend.content_protection import PostboxContentProtectionError
|
||||
from govoplan_postbox.backend.service import PostboxError, PostboxService
|
||||
|
||||
|
||||
@@ -1236,6 +1239,85 @@ class PostboxServiceTests(unittest.TestCase):
|
||||
),
|
||||
)
|
||||
|
||||
def test_server_envelope_body_is_not_persisted_in_plaintext_and_fails_closed(
|
||||
self,
|
||||
) -> None:
|
||||
self.idm.assignments.append(self.assignment)
|
||||
protected = SimpleNamespace(
|
||||
ciphertext=b"encrypted-message-body",
|
||||
envelope=SimpleNamespace(
|
||||
envelope_id="envelope-1",
|
||||
ciphertext_ref="postbox-db://message/body",
|
||||
algorithm_suite="AES-256-GCM",
|
||||
wrapped_key_refs=("wrapped-key-1",),
|
||||
),
|
||||
)
|
||||
with Session(self.engine) as session:
|
||||
postbox = self.service.create_exact_postbox(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
name="Protected intake",
|
||||
organization_unit_id="unit-1",
|
||||
function_id="function-1",
|
||||
address_key=None,
|
||||
description=None,
|
||||
classification="internal",
|
||||
actor_id="admin-1",
|
||||
encryption_profile="server_envelope_v1",
|
||||
encryption_vault_id="vault-1",
|
||||
)
|
||||
with patch(
|
||||
"govoplan_postbox.backend.content_protection.protect_message_body",
|
||||
return_value=protected,
|
||||
):
|
||||
delivered = self.service.deliver(
|
||||
session,
|
||||
PostboxDeliveryRequest(
|
||||
tenant_id="tenant-1",
|
||||
target=PostboxTargetRef(postbox_id=postbox.id),
|
||||
producer_module="campaigns",
|
||||
producer_resource_type="campaign_recipient",
|
||||
producer_resource_id="recipient-protected",
|
||||
idempotency_key="protected-message",
|
||||
subject="Protected notice",
|
||||
body_text="clear message body",
|
||||
),
|
||||
)
|
||||
|
||||
stored = session.get(PostboxMessage, delivered.message_id)
|
||||
assert stored is not None
|
||||
self.assertIsNone(stored.body_text)
|
||||
self.assertEqual(b"encrypted-message-body", stored.body_ciphertext)
|
||||
self.assertEqual("envelope-1", stored.encryption_envelope_id)
|
||||
|
||||
with patch(
|
||||
"govoplan_postbox.backend.content_protection.unprotect_message_body",
|
||||
return_value="clear message body",
|
||||
):
|
||||
opened = self.service.get_message(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
message_id=delivered.message_id,
|
||||
actor=self.actor,
|
||||
)
|
||||
assert opened is not None
|
||||
self.assertEqual("clear message body", opened.body_text)
|
||||
|
||||
with patch(
|
||||
"govoplan_postbox.backend.content_protection.unprotect_message_body",
|
||||
side_effect=PostboxContentProtectionError("provider unavailable"),
|
||||
):
|
||||
with self.assertRaisesRegex(
|
||||
PostboxError,
|
||||
"provider unavailable",
|
||||
):
|
||||
self.service.get_message(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
message_id=delivered.message_id,
|
||||
actor=self.actor,
|
||||
)
|
||||
|
||||
def test_hierarchy_linked_copy_snapshots_path_and_independent_state(
|
||||
self,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user