feat(postbox): enforce unified inbox separation
This commit is contained in:
@@ -546,6 +546,7 @@ class PostboxServiceTests(unittest.TestCase):
|
||||
encryption_profile: str = "plaintext_v1",
|
||||
encryption_vault_id: str | None = None,
|
||||
protection_policy: dict[str, object] | None = None,
|
||||
grouping_policy: dict[str, object] | None = None,
|
||||
) -> Postbox:
|
||||
return self.service.create_exact_postbox(
|
||||
session,
|
||||
@@ -560,6 +561,7 @@ class PostboxServiceTests(unittest.TestCase):
|
||||
encryption_profile=encryption_profile,
|
||||
encryption_vault_id=encryption_vault_id,
|
||||
protection_policy=protection_policy,
|
||||
grouping_policy=grouping_policy,
|
||||
)
|
||||
|
||||
def _routing_policy(
|
||||
@@ -2091,6 +2093,211 @@ class PostboxServiceTests(unittest.TestCase):
|
||||
}
|
||||
self.assertEqual({parent.id, child.id}, visible_ids)
|
||||
|
||||
def test_grouping_policy_enforces_function_and_classification_separation(
|
||||
self,
|
||||
) -> None:
|
||||
child_assignment = OrganizationFunctionAssignmentRef(
|
||||
id="assignment-child",
|
||||
tenant_id="tenant-1",
|
||||
identity_id="identity-1",
|
||||
account_id="account-1",
|
||||
function_id="function-child",
|
||||
organization_unit_id="unit-child",
|
||||
source="delegated",
|
||||
delegated_from_assignment_id="assignment-1",
|
||||
)
|
||||
self.idm.assignments.extend((self.assignment, child_assignment))
|
||||
privileged_actor = replace(
|
||||
self.actor,
|
||||
authorized_classifications=frozenset(
|
||||
{"public", "internal", "confidential"}
|
||||
),
|
||||
)
|
||||
with Session(self.engine) as session:
|
||||
parent = self._create_exact(
|
||||
session,
|
||||
grouping_policy={
|
||||
"mode": "same_classification",
|
||||
"reason": "Confidential responsibilities remain partitioned.",
|
||||
},
|
||||
)
|
||||
child = self.service.create_exact_postbox(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
name="Service Desk / Delegated complaints",
|
||||
organization_unit_id="unit-child",
|
||||
function_id="function-child",
|
||||
address_key=None,
|
||||
description=None,
|
||||
classification="confidential",
|
||||
actor_id="admin-1",
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
PostboxError,
|
||||
"classification_separation_required",
|
||||
):
|
||||
self.service.save_grouping(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
actor=privileged_actor,
|
||||
grouping_id=None,
|
||||
name="Mixed classification",
|
||||
is_default=False,
|
||||
postbox_ids=(parent.id, child.id),
|
||||
)
|
||||
|
||||
grouping = self.service.save_grouping(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
actor=privileged_actor,
|
||||
grouping_id=None,
|
||||
name="Same responsibility",
|
||||
is_default=True,
|
||||
postbox_ids=(parent.id,),
|
||||
)
|
||||
session.commit()
|
||||
self.service.update_grouping_policy(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
postbox_id=parent.id,
|
||||
grouping_policy={
|
||||
"mode": "separate",
|
||||
"reason": "This function must remain a dedicated inbox.",
|
||||
},
|
||||
actor_id="admin-1",
|
||||
expected_revision=parent.resource_revision,
|
||||
)
|
||||
session.commit()
|
||||
|
||||
with self.assertRaisesRegex(PostboxError, "source_requires_separation"):
|
||||
self.service.list_messages(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
postbox_ids=(parent.id, child.id),
|
||||
actor=privileged_actor,
|
||||
)
|
||||
self.assertEqual(
|
||||
(parent.id,),
|
||||
tuple(source.postbox_id for source in grouping.sources),
|
||||
)
|
||||
|
||||
def test_grouping_pagination_is_stable_and_delegation_expiry_hides_source(
|
||||
self,
|
||||
) -> None:
|
||||
delegated = OrganizationFunctionAssignmentRef(
|
||||
id="assignment-child",
|
||||
tenant_id="tenant-1",
|
||||
identity_id="identity-1",
|
||||
account_id="account-1",
|
||||
function_id="function-child",
|
||||
organization_unit_id="unit-child",
|
||||
source="delegated",
|
||||
delegated_from_assignment_id="assignment-1",
|
||||
)
|
||||
self.idm.assignments.extend((self.assignment, delegated))
|
||||
with Session(self.engine) as session:
|
||||
parent = self._create_exact(session)
|
||||
child = self.service.create_exact_postbox(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
name="Service Desk / Delegated intake",
|
||||
organization_unit_id="unit-child",
|
||||
function_id="function-child",
|
||||
address_key=None,
|
||||
description=None,
|
||||
classification="internal",
|
||||
actor_id="admin-1",
|
||||
)
|
||||
grouping = self.service.save_grouping(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
actor=self.actor,
|
||||
grouping_id=None,
|
||||
name="Delegated work",
|
||||
is_default=True,
|
||||
postbox_ids=(parent.id, child.id),
|
||||
)
|
||||
focused_grouping = self.service.save_grouping(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
actor=self.actor,
|
||||
grouping_id=None,
|
||||
name="Delegated intake only",
|
||||
is_default=False,
|
||||
postbox_ids=(child.id,),
|
||||
)
|
||||
self.assertEqual(
|
||||
(child.id,),
|
||||
tuple(source.postbox_id for source in focused_grouping.sources),
|
||||
)
|
||||
for index, postbox in enumerate((parent, child, parent, child), start=1):
|
||||
self.service.deliver(
|
||||
session,
|
||||
PostboxDeliveryRequest(
|
||||
tenant_id="tenant-1",
|
||||
target=PostboxTargetRef(postbox_id=postbox.id),
|
||||
producer_module="tests",
|
||||
producer_resource_type="stable_page",
|
||||
producer_resource_id=str(index),
|
||||
idempotency_key=f"stable-page-{index}",
|
||||
subject=f"Message {index}",
|
||||
classification="internal",
|
||||
),
|
||||
)
|
||||
boundary = utc_now()
|
||||
session.query(PostboxMessage).update(
|
||||
{PostboxMessage.delivered_at: boundary}
|
||||
)
|
||||
session.commit()
|
||||
|
||||
first = self.service.list_messages(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
postbox_ids=(parent.id, child.id),
|
||||
actor=self.actor,
|
||||
limit=2,
|
||||
offset=0,
|
||||
)
|
||||
second = self.service.list_messages(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
postbox_ids=(parent.id, child.id),
|
||||
actor=self.actor,
|
||||
limit=2,
|
||||
offset=2,
|
||||
)
|
||||
repeated = self.service.list_messages(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
postbox_ids=(parent.id, child.id),
|
||||
actor=self.actor,
|
||||
limit=2,
|
||||
offset=0,
|
||||
)
|
||||
self.assertEqual(
|
||||
[item.id for item in first], [item.id for item in repeated]
|
||||
)
|
||||
self.assertFalse({item.id for item in first} & {item.id for item in second})
|
||||
|
||||
self.idm.assignments = [
|
||||
self.assignment,
|
||||
replace(delegated, status="expired"),
|
||||
]
|
||||
visible_ids = {
|
||||
item.id
|
||||
for item in self.service.list_visible_postboxes(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
actor=self.actor,
|
||||
)
|
||||
}
|
||||
self.assertEqual({parent.id}, visible_ids)
|
||||
self.assertEqual(
|
||||
(parent.id, child.id),
|
||||
tuple(source.postbox_id for source in grouping.sources),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user