From 3d52cc86f912d73da13de1151de0d47df9f1abe4 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Mon, 20 Jul 2026 17:03:58 +0200 Subject: [PATCH] fix(addresses): require group visibility for book creation --- src/govoplan_addresses/backend/service.py | 2 +- tests/test_addresses_service.py | 33 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/govoplan_addresses/backend/service.py b/src/govoplan_addresses/backend/service.py index aa7d806..3109372 100644 --- a/src/govoplan_addresses/backend/service.py +++ b/src/govoplan_addresses/backend/service.py @@ -175,7 +175,7 @@ def _scope_id_for_create(principal: ApiPrincipal, payload: AddressBookCreateRequ group_id = _trim(payload.group_id) if not group_id: raise AddressBookError("Group address books require a group id.") - if principal.group_ids and group_id not in principal.group_ids and not principal.has("addresses:address_book:admin"): + if group_id not in principal.group_ids and not principal.has("addresses:address_book:admin"): raise AddressBookError("The selected group is not visible to the current principal.") return tenant_id, group_id raise AddressBookError("Unsupported address book scope.") diff --git a/tests/test_addresses_service.py b/tests/test_addresses_service.py index ad0dab3..cfe945e 100644 --- a/tests/test_addresses_service.py +++ b/tests/test_addresses_service.py @@ -49,6 +49,7 @@ from govoplan_addresses.backend.schemas import ( ) from govoplan_addresses.backend.manifest import manifest from govoplan_addresses.backend.service import ( + AddressBookError, address_book_contact_counts, address_list_entry_counts, create_address_book, @@ -146,6 +147,15 @@ class ReadOnlyPrincipal(Principal): } +class NoGroupPrincipal(Principal): + group_ids = frozenset() + + +class AddressBookAdminPrincipal(NoGroupPrincipal): + def has(self, scope: str) -> bool: + return scope == "addresses:address_book:admin" or super().has(scope) + + class AddressServiceTest(unittest.TestCase): def setUp(self) -> None: engine = create_engine("sqlite:///:memory:") @@ -169,6 +179,29 @@ class AddressServiceTest(unittest.TestCase): self.session = sessionmaker(bind=engine)() self.principal = Principal() + def test_group_book_creation_requires_membership_or_admin_scope(self) -> None: + with self.assertRaisesRegex(AddressBookError, "selected group is not visible"): + create_address_book( + self.session, + NoGroupPrincipal(), + AddressBookCreateRequest(scope_type="group", group_id="group-2", name="Unassigned"), + ) + + with self.assertRaisesRegex(AddressBookError, "selected group is not visible"): + create_address_book( + self.session, + self.principal, + AddressBookCreateRequest(scope_type="group", group_id="group-2", name="Other group"), + ) + + admin_book = create_address_book( + self.session, + AddressBookAdminPrincipal(), + AddressBookCreateRequest(scope_type="group", group_id="group-2", name="Admin-created"), + ) + + self.assertEqual(admin_book.scope_id, "group-2") + def test_scoped_book_contact_lookup_and_soft_delete(self) -> None: book = create_address_book(self.session, self.principal, AddressBookCreateRequest(scope_type="user", name="Personal")) self.session.commit()