From 0d6721ff6d06217c6651e7d837dd92d44208d5d4 Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Fri, 24 Jul 2026 16:59:26 +0800 Subject: [PATCH 1/4] test(security): program/CR roles must not carry Tier-2 registry viewer Add tests asserting Program Viewer and the CR Requestor/Local/HQ Validator roles do NOT confer spp_registry.group_registry_viewer (which gates the Registry Search portal menu) but DO keep registrant read (res.partner, spp.registry.id, spp.phone.number) via Tier-3 group_registry_read. --- spp_change_request_v2/tests/__init__.py | 1 + .../tests/test_cr_roles_registry_scope.py | 67 ++++++++++++++++++ spp_programs/tests/__init__.py | 1 + .../test_program_viewer_registry_scope.py | 70 +++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 spp_change_request_v2/tests/test_cr_roles_registry_scope.py create mode 100644 spp_programs/tests/test_program_viewer_registry_scope.py diff --git a/spp_change_request_v2/tests/__init__.py b/spp_change_request_v2/tests/__init__.py index bc75e9cc0..b3ac05fba 100644 --- a/spp_change_request_v2/tests/__init__.py +++ b/spp_change_request_v2/tests/__init__.py @@ -25,3 +25,4 @@ from . import test_conflict_dynamic_approval from . import test_html_escaping from . import test_wizard_html_escaping +from . import test_cr_roles_registry_scope diff --git a/spp_change_request_v2/tests/test_cr_roles_registry_scope.py b/spp_change_request_v2/tests/test_cr_roles_registry_scope.py new file mode 100644 index 000000000..45b1eec80 --- /dev/null +++ b/spp_change_request_v2/tests/test_cr_roles_registry_scope.py @@ -0,0 +1,67 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +"""CR roles must read registrant data (a change request is about a registrant) +but must NOT carry the Tier-2 ``group_registry_viewer`` group, which gates the +standalone Registry Search portal menu — an over-broad registrant PII +enumeration surface. Registrant read is preserved via the Tier-3 +``group_registry_read`` group (granted through their ``group_cr_*`` chain and +the explicit role link). +""" + +from odoo.tests import TransactionCase, tagged + +_CR_ROLE_XMLIDS = [ + "spp_change_request_v2.global_role_cr_requestor", + "spp_change_request_v2.local_role_cr_validator", + "spp_change_request_v2.global_role_cr_validator_hq", +] + + +@tagged("post_install", "-at_install") +class TestCRRolesRegistryScope(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.registrant = cls.env["res.partner"].create( + {"name": "CR Registrant", "is_registrant": True, "is_group": False} + ) + cls.reg_id = cls.env["spp.registry.id"].create( + { + "partner_id": cls.registrant.id, + "id_type_id": cls.env.ref("spp_vocabulary.code_id_type_national_id").id, + "value": "CR-123", + } + ) + cls.phone = cls.env["spp.phone.number"].create( + {"partner_id": cls.registrant.id, "phone_no": "09180000000"} + ) + + def _user_with_role(self, role_xmlid, login): + user = self.env["res.users"].create( + {"name": login, "login": login, "email": f"{login}@example.com"} + ) + self.env["res.users.role.line"].create( + {"user_id": user.id, "role_id": self.env.ref(role_xmlid).id} + ) + user.set_groups_from_roles() + return user + + def test_cr_roles_lack_tier2_registry_viewer(self): + for xmlid in _CR_ROLE_XMLIDS: + user = self._user_with_role(xmlid, f"crscope_{xmlid.split('.')[-1]}") + self.assertFalse( + user.has_group("spp_registry.group_registry_viewer"), + f"{xmlid} must not carry the Tier-2 registry viewer group " + "(it gates the registry search portal menu)", + ) + + def test_cr_roles_keep_registrant_read(self): + for xmlid in _CR_ROLE_XMLIDS: + user = self._user_with_role(xmlid, f"crread_{xmlid.split('.')[-1]}") + self.assertTrue( + user.has_group("spp_registry.group_registry_read"), + f"{xmlid} must keep Tier-3 registry read", + ) + # Functional read of the sensitive PII models as the role user. + self.registrant.with_user(user).read(["name"]) + self.reg_id.with_user(user).read(["value"]) + self.phone.with_user(user).read(["phone_no"]) diff --git a/spp_programs/tests/__init__.py b/spp_programs/tests/__init__.py index 15dc1cbe6..9ef1d8e6a 100644 --- a/spp_programs/tests/__init__.py +++ b/spp_programs/tests/__init__.py @@ -43,3 +43,4 @@ from . import test_cycle_null_entitlement_approval from . import test_approve_entitlements_program_isolation from . import test_payment_batch_payment_ids +from . import test_program_viewer_registry_scope diff --git a/spp_programs/tests/test_program_viewer_registry_scope.py b/spp_programs/tests/test_program_viewer_registry_scope.py new file mode 100644 index 000000000..ffa4076c8 --- /dev/null +++ b/spp_programs/tests/test_program_viewer_registry_scope.py @@ -0,0 +1,70 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +"""The Program Viewer role must be able to read registrant data for program +cross-references, but must NOT carry the Tier-2 ``group_registry_viewer`` +group, which gates the standalone Registry Search portal menu +(``spp_registry_search.menu_registry_search``) — an over-broad registrant PII +enumeration surface for a read-only program role. + +The role is switched to the Tier-3 ``group_registry_read`` technical group, +which grants the same registrant read ACLs (defined in ``spp_base_common``) +without the Registry app menu. +""" + +from odoo.tests import TransactionCase, tagged + + +@tagged("post_install", "-at_install") +class TestProgramViewerRegistryScope(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.user = cls.env["res.users"].create( + { + "name": "Program Viewer Test", + "login": "program_viewer_scope_test", + "email": "pv_scope@example.com", + } + ) + cls.env["res.users.role.line"].create( + { + "user_id": cls.user.id, + "role_id": cls.env.ref("spp_programs.global_role_program_viewer").id, + } + ) + cls.user.set_groups_from_roles() + + # A registrant with an ID number and phone (the sensitive PII models). + cls.registrant = cls.env["res.partner"].create( + {"name": "PV Registrant", "is_registrant": True, "is_group": False} + ) + cls.reg_id = cls.env["spp.registry.id"].create( + { + "partner_id": cls.registrant.id, + "id_type_id": cls.env.ref("spp_vocabulary.code_id_type_national_id").id, + "value": "PV-123", + } + ) + cls.phone = cls.env["spp.phone.number"].create( + {"partner_id": cls.registrant.id, "phone_no": "09170000000"} + ) + + def test_program_viewer_lacks_tier2_registry_viewer(self): + """The role must not carry group_registry_viewer (gates the Registry + Search portal menu).""" + self.assertFalse( + self.user.has_group("spp_registry.group_registry_viewer"), + "Program Viewer must not have the Tier-2 registry viewer group " + "(it gates the registry search portal menu)", + ) + + def test_program_viewer_keeps_registrant_read(self): + """Registrant read must be preserved via Tier-3 group_registry_read.""" + self.assertTrue(self.user.has_group("spp_registry.group_registry_read")) + # Functional read of the sensitive PII models as the role user. + self.registrant.with_user(self.user).read(["name"]) + self.reg_id.with_user(self.user).read(["value"]) + self.phone.with_user(self.user).read(["phone_no"]) + + def test_program_viewer_keeps_program_data_read(self): + """The role must still read program/cycle data (from group_programs_viewer).""" + self.assertTrue(self.user.has_group("spp_programs.group_programs_viewer")) From 266f1dca4b517f5e36be34a8de1c9fea2eaf3453 Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Fri, 24 Jul 2026 16:59:26 +0800 Subject: [PATCH 2/4] security(roles): scope program/CR roles to Tier-3 registry read, drop viewer menu Program Viewer, CR Requestor, CR Local Validator and CR HQ Validator implied spp_registry.group_registry_viewer (Tier-2), which gates the standalone Registry Search portal menu and exposed a broad registrant-PII enumeration surface to these program/CR-scoped roles. Switch them to Tier-3 group_registry_read, which grants the same registrant read ACLs (defined in spp_base_common; also provided to CR roles via the group_cr_* chain) without the Registry app menu. user_roles.xml is noupdate=1 and the roles ship in the 2026.07 release, so add post-migrations (spp_programs 19.0.2.2.2, spp_change_request_v2 19.0.3.0.3) that unlink the viewer group, link the read group, and re-sync already-assigned users via action_update_users. Reduces the registry-search UI surface; underlying RPC read scope is unchanged (governed by the area filter rule_res_partner_area_filter). --- spp_change_request_v2/data/user_roles.xml | 16 ++++-- .../migrations/19.0.3.0.3/post-migration.py | 50 +++++++++++++++++++ spp_programs/data/user_roles.xml | 11 +++- .../migrations/19.0.2.2.2/post-migration.py | 45 +++++++++++++++++ 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 spp_change_request_v2/migrations/19.0.3.0.3/post-migration.py create mode 100644 spp_programs/migrations/19.0.2.2.2/post-migration.py diff --git a/spp_change_request_v2/data/user_roles.xml b/spp_change_request_v2/data/user_roles.xml index c8e4e7364..75bc23d54 100644 --- a/spp_change_request_v2/data/user_roles.xml +++ b/spp_change_request_v2/data/user_roles.xml @@ -5,6 +5,16 @@ Part of OpenSPP. See LICENSE file for full copyright and licensing details. User roles for Change Request module. --> + + @@ -18,7 +28,7 @@ User roles for Change Request module. eval="[ Command.link(ref('base.group_user')), Command.link(ref('group_cr_manager')), - Command.link(ref('spp_registry.group_registry_viewer')), + Command.link(ref('spp_registry.group_registry_read')), Command.link(ref('spp_hazard.group_hazard_viewer')), ]" /> @@ -35,7 +45,7 @@ User roles for Change Request module. eval="[ Command.link(ref('base.group_user')), Command.link(ref('group_cr_validator')), - Command.link(ref('spp_registry.group_registry_viewer')), + Command.link(ref('spp_registry.group_registry_read')), Command.link(ref('spp_hazard.group_hazard_viewer')), ]" /> @@ -52,7 +62,7 @@ User roles for Change Request module. eval="[ Command.link(ref('base.group_user')), Command.link(ref('group_cr_validator_hq')), - Command.link(ref('spp_registry.group_registry_viewer')), + Command.link(ref('spp_registry.group_registry_read')), Command.link(ref('spp_hazard.group_hazard_viewer')), ]" /> diff --git a/spp_change_request_v2/migrations/19.0.3.0.3/post-migration.py b/spp_change_request_v2/migrations/19.0.3.0.3/post-migration.py new file mode 100644 index 000000000..af0cbace6 --- /dev/null +++ b/spp_change_request_v2/migrations/19.0.3.0.3/post-migration.py @@ -0,0 +1,50 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +"""Swap the CR roles from Tier-2 ``group_registry_viewer`` to Tier-3 +``group_registry_read``. + +The roles' ``implied_ids`` are seeded from ``data/user_roles.xml`` with +``noupdate="1"``, so a released database (2026.07) keeps the old +``group_registry_viewer`` link on upgrade and would retain the Registry Search +portal menu. This migration unlinks the Tier-2 viewer group, links the Tier-3 +read group (same registrant read ACLs, no menu; read is also provided through +the group_cr_* chain), and re-materializes the group membership of users +already assigned each role. +""" + +import logging + +from odoo import SUPERUSER_ID, Command, api + +_logger = logging.getLogger(__name__) + +_ROLE_XMLIDS = [ + "spp_change_request_v2.global_role_cr_requestor", + "spp_change_request_v2.local_role_cr_validator", + "spp_change_request_v2.global_role_cr_validator_hq", +] + + +def migrate(cr, version): + if not version: + return + env = api.Environment(cr, SUPERUSER_ID, {}) + viewer = env.ref("spp_registry.group_registry_viewer", raise_if_not_found=False) + read = env.ref("spp_registry.group_registry_read", raise_if_not_found=False) + if not viewer or not read: + return + for xmlid in _ROLE_XMLIDS: + role = env.ref(xmlid, raise_if_not_found=False) + if not role: + continue + commands = [] + if viewer in role.implied_ids: + commands.append(Command.unlink(viewer.id)) + if read not in role.implied_ids: + commands.append(Command.link(read.id)) + if commands: + role.implied_ids = commands + role.action_update_users() + _logger.info( + "Migrated role %s: registry viewer -> registry read (re-synced users)", + xmlid, + ) diff --git a/spp_programs/data/user_roles.xml b/spp_programs/data/user_roles.xml index 62b2275f6..95b6d2642 100644 --- a/spp_programs/data/user_roles.xml +++ b/spp_programs/data/user_roles.xml @@ -7,12 +7,21 @@ Read-only access to program, cycle, and entitlement records. + registry read (re-synced users)", + xmlid, + ) From 8a9f1fa0b30e722e48321f0ba45ceef0948acb2b Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Fri, 24 Jul 2026 16:59:26 +0800 Subject: [PATCH 3/4] docs(security): version bump + HISTORY for registry-viewer role scoping spp_programs 19.0.2.2.0 -> 19.0.2.2.2 (dodges #336's .2.1); spp_change_request_v2 19.0.3.0.0 -> 19.0.3.0.3 (dodges #264 .0.1 / #261 .0.2). README.rst / index.html to be regenerated from CI's pinned generator. --- spp_change_request_v2/__manifest__.py | 2 +- spp_change_request_v2/readme/HISTORY.md | 4 ++++ spp_programs/__manifest__.py | 2 +- spp_programs/readme/HISTORY.md | 4 ++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/spp_change_request_v2/__manifest__.py b/spp_change_request_v2/__manifest__.py index 1e32f323f..516bfd937 100644 --- a/spp_change_request_v2/__manifest__.py +++ b/spp_change_request_v2/__manifest__.py @@ -1,6 +1,6 @@ { "name": "OpenSPP Change Request V2", - "version": "19.0.3.0.0", + "version": "19.0.3.0.3", "sequence": 50, "category": "OpenSPP", "summary": "Configuration-driven change request system with UX improvements, conflict detection and duplicate prevention", diff --git a/spp_change_request_v2/readme/HISTORY.md b/spp_change_request_v2/readme/HISTORY.md index 0f9d181e5..19f5a8a49 100644 --- a/spp_change_request_v2/readme/HISTORY.md +++ b/spp_change_request_v2/readme/HISTORY.md @@ -1,3 +1,7 @@ +### 19.0.3.0.3 + +- fix(security): the CR Requestor, CR Local Validator, and CR HQ Validator roles no longer carry the Tier-2 `spp_registry.group_registry_viewer` group, which gates the standalone Registry Search portal menu and exposed a broad registrant-PII enumeration surface. They now use the Tier-3 `spp_registry.group_registry_read` group instead, preserving the registrant read a change request needs (same read ACLs, also provided through the `group_cr_*` chain) without the Registry app menu. Includes a migration that re-points the roles and re-syncs already-assigned users on upgrade. + ### 19.0.3.0.0 - feat(change_request): redesign the group/membership CR flows (#242) — Create Group (#876), Add Member now searches an existing member (#871), Remove Member first-page/review cleanup (#872), Change Head of Household via a per-member role table (#873), and Split Household as a relational member move with single-head validation (#877). Review pages render the real data as tables / detail sections. diff --git a/spp_programs/__manifest__.py b/spp_programs/__manifest__.py index 8dac1cba2..f7c9e330b 100644 --- a/spp_programs/__manifest__.py +++ b/spp_programs/__manifest__.py @@ -4,7 +4,7 @@ "name": "OpenSPP Programs", "summary": "Manage programs, cycles, beneficiary enrollment, entitlements (cash and in-kind), payments, and fund tracking for social protection.", "category": "OpenSPP/Core", - "version": "19.0.2.2.0", + "version": "19.0.2.2.2", "sequence": 1, "author": "OpenSPP.org", "website": "https://github.com/OpenSPP/OpenSPP2", diff --git a/spp_programs/readme/HISTORY.md b/spp_programs/readme/HISTORY.md index 826b3233a..8e7cf9c8c 100644 --- a/spp_programs/readme/HISTORY.md +++ b/spp_programs/readme/HISTORY.md @@ -1,3 +1,7 @@ +### 19.0.2.2.2 + +- fix(security): the Program Viewer role no longer carries the Tier-2 `spp_registry.group_registry_viewer` group, which gates the standalone Registry Search portal menu and exposed a broad registrant-PII enumeration surface to a read-only program role. It now uses the Tier-3 `spp_registry.group_registry_read` group instead, preserving the registrant read needed for program cross-references (same read ACLs, defined in `spp_base_common`) without the Registry app menu. Includes a migration that re-points the role and re-syncs already-assigned users on upgrade. + ### 19.0.2.1.3 - fix(security): align Program Viewer / Validator / Cycle Approver roles with the OP#951 menu audit — Program Viewer additionally gets `group_registry_viewer` + `group_approval_viewer` (read-only Registry + Approvals access); all three program roles get `group_hazard_viewer` + `group_gis_report_user` so they retain Hazard / GIS Reports visibility once those menu roots are gated. Adds `spp_hazard` and `spp_gis_report` to module dependencies. From 5f743ea8ec6ba8f0031e74d7cf38dc298f04d40b Mon Sep 17 00:00:00 2001 From: Edwin Gonzales Date: Fri, 24 Jul 2026 17:11:44 +0800 Subject: [PATCH 4/4] docs(security): regenerate READMEs + apply CI ruff-format for registry-viewer scoping README.rst / index.html regenerated from CI's pinned oca-gen output for spp_programs (19.0.2.2.2) and spp_change_request_v2 (19.0.3.0.3); test files reformatted to CI's ruff-format line length (local hook env differed). --- spp_change_request_v2/README.rst | 14 +++++++ .../static/description/index.html | 31 ++++++++++---- .../tests/test_cr_roles_registry_scope.py | 15 ++----- spp_programs/README.rst | 13 ++++++ spp_programs/static/description/index.html | 42 ++++++++++++------- .../test_program_viewer_registry_scope.py | 7 +--- 6 files changed, 84 insertions(+), 38 deletions(-) diff --git a/spp_change_request_v2/README.rst b/spp_change_request_v2/README.rst index 914b1a0e5..4de47720a 100644 --- a/spp_change_request_v2/README.rst +++ b/spp_change_request_v2/README.rst @@ -853,6 +853,20 @@ Before declaring a new CR type complete: Changelog ========= +19.0.3.0.3 +~~~~~~~~~~ + +- fix(security): the CR Requestor, CR Local Validator, and CR HQ + Validator roles no longer carry the Tier-2 + ``spp_registry.group_registry_viewer`` group, which gates the + standalone Registry Search portal menu and exposed a broad + registrant-PII enumeration surface. They now use the Tier-3 + ``spp_registry.group_registry_read`` group instead, preserving the + registrant read a change request needs (same read ACLs, also provided + through the ``group_cr_*`` chain) without the Registry app menu. + Includes a migration that re-points the roles and re-syncs + already-assigned users on upgrade. + 19.0.3.0.0 ~~~~~~~~~~ diff --git a/spp_change_request_v2/static/description/index.html b/spp_change_request_v2/static/description/index.html index 58f8d5751..34f7c4148 100644 --- a/spp_change_request_v2/static/description/index.html +++ b/spp_change_request_v2/static/description/index.html @@ -1339,6 +1339,21 @@

Changelog

+

19.0.3.0.3

+
    +
  • fix(security): the CR Requestor, CR Local Validator, and CR HQ +Validator roles no longer carry the Tier-2 +spp_registry.group_registry_viewer group, which gates the +standalone Registry Search portal menu and exposed a broad +registrant-PII enumeration surface. They now use the Tier-3 +spp_registry.group_registry_read group instead, preserving the +registrant read a change request needs (same read ACLs, also provided +through the group_cr_* chain) without the Registry app menu. +Includes a migration that re-points the roles and re-syncs +already-assigned users on upgrade.
  • +
+
+

19.0.3.0.0

  • feat(change_request): redesign the group/membership CR flows (#242) — @@ -1360,7 +1375,7 @@

    19.0.3.0.0

    must adapt (see #1133).
-
+

19.0.2.0.8

  • fix(views): disable inline creation of CR document types on the Change @@ -1371,7 +1386,7 @@

    19.0.2.0.8

    Documents” modal (missing Name field) that blocked saving (#1125)
-
+

19.0.2.0.7

  • fix(security): align CR Requestor / CR Local Validator / CR HQ @@ -1383,7 +1398,7 @@

    19.0.2.0.7

    dependencies.
-
+

19.0.2.0.6

  • fix(views): route post-submit CRs (pending / approved / applied / @@ -1398,7 +1413,7 @@

    19.0.2.0.6

    list so row-click goes through the stage router.
-
+

19.0.2.0.5

  • fix(security): add a global ir.rule on spp.change.request that @@ -1411,27 +1426,27 @@

    19.0.2.0.5

    roles).
-
+

19.0.2.0.3

  • fix: add HTML escaping to all computed Html fields with sanitize=False to prevent stored XSS (#50)
-
+

19.0.2.0.2

  • fix: fix batch approval wizard line deletion (#130)
-
+

19.0.2.0.1

  • fix: skip field types before getattr and isolate detail prefetch (#129)
-
+

19.0.2.0.0

  • Initial migration to OpenSPP2
  • diff --git a/spp_change_request_v2/tests/test_cr_roles_registry_scope.py b/spp_change_request_v2/tests/test_cr_roles_registry_scope.py index 45b1eec80..43761489e 100644 --- a/spp_change_request_v2/tests/test_cr_roles_registry_scope.py +++ b/spp_change_request_v2/tests/test_cr_roles_registry_scope.py @@ -31,17 +31,11 @@ def setUpClass(cls): "value": "CR-123", } ) - cls.phone = cls.env["spp.phone.number"].create( - {"partner_id": cls.registrant.id, "phone_no": "09180000000"} - ) + cls.phone = cls.env["spp.phone.number"].create({"partner_id": cls.registrant.id, "phone_no": "09180000000"}) def _user_with_role(self, role_xmlid, login): - user = self.env["res.users"].create( - {"name": login, "login": login, "email": f"{login}@example.com"} - ) - self.env["res.users.role.line"].create( - {"user_id": user.id, "role_id": self.env.ref(role_xmlid).id} - ) + user = self.env["res.users"].create({"name": login, "login": login, "email": f"{login}@example.com"}) + self.env["res.users.role.line"].create({"user_id": user.id, "role_id": self.env.ref(role_xmlid).id}) user.set_groups_from_roles() return user @@ -50,8 +44,7 @@ def test_cr_roles_lack_tier2_registry_viewer(self): user = self._user_with_role(xmlid, f"crscope_{xmlid.split('.')[-1]}") self.assertFalse( user.has_group("spp_registry.group_registry_viewer"), - f"{xmlid} must not carry the Tier-2 registry viewer group " - "(it gates the registry search portal menu)", + f"{xmlid} must not carry the Tier-2 registry viewer group (it gates the registry search portal menu)", ) def test_cr_roles_keep_registrant_read(self): diff --git a/spp_programs/README.rst b/spp_programs/README.rst index c05dac423..116bfa98d 100644 --- a/spp_programs/README.rst +++ b/spp_programs/README.rst @@ -254,6 +254,19 @@ Dependencies Changelog ========= +19.0.2.2.2 +~~~~~~~~~~ + +- fix(security): the Program Viewer role no longer carries the Tier-2 + ``spp_registry.group_registry_viewer`` group, which gates the + standalone Registry Search portal menu and exposed a broad + registrant-PII enumeration surface to a read-only program role. It now + uses the Tier-3 ``spp_registry.group_registry_read`` group instead, + preserving the registrant read needed for program cross-references + (same read ACLs, defined in ``spp_base_common``) without the Registry + app menu. Includes a migration that re-points the role and re-syncs + already-assigned users on upgrade. + 19.0.2.1.3 ~~~~~~~~~~ diff --git a/spp_programs/static/description/index.html b/spp_programs/static/description/index.html index 50e35cbb3..dc5dd2352 100644 --- a/spp_programs/static/description/index.html +++ b/spp_programs/static/description/index.html @@ -658,6 +658,20 @@

    Changelog

+

19.0.2.2.2

+
    +
  • fix(security): the Program Viewer role no longer carries the Tier-2 +spp_registry.group_registry_viewer group, which gates the +standalone Registry Search portal menu and exposed a broad +registrant-PII enumeration surface to a read-only program role. It now +uses the Tier-3 spp_registry.group_registry_read group instead, +preserving the registrant read needed for program cross-references +(same read ACLs, defined in spp_base_common) without the Registry +app menu. Includes a migration that re-points the role and re-syncs +already-assigned users on upgrade.
  • +
+
+

19.0.2.1.3

  • fix(security): align Program Viewer / Validator / Cycle Approver roles @@ -676,7 +690,7 @@

    19.0.2.1.3

    cross-references — only the dedicated top-level menu disappears.
-
+

19.0.2.1.2

  • fix(security): add global ir.rule records on @@ -690,7 +704,7 @@

    19.0.2.1.2

    no-op for users with no center areas (global roles).
-
+

19.0.2.1.1

  • fix(views): apply spp_registry.x2many_no_padding widget to the @@ -699,7 +713,7 @@

    19.0.2.1.1

    19 inserts on inline list-in-form views (#943).
-
+

19.0.2.0.11

  • Fix TypeError: 'NoneType' object is not iterable when clicking @@ -710,7 +724,7 @@

    19.0.2.0.11

    omit the state filter instead of crashing on tuple(None)
-
+

19.0.2.0.10

  • Increase parallel-safe channel limits (cycle, eligibility_manager, @@ -723,7 +737,7 @@

    19.0.2.0.10

    submission on double-click
-
+

19.0.2.0.9

  • Add context flags (skip_registrant_statistics, @@ -736,7 +750,7 @@

    19.0.2.0.9

    _compute_has_members
-
+

19.0.2.0.8

  • Replace OFFSET pagination with NTILE-based ID-range batching in all @@ -747,7 +761,7 @@

    19.0.2.0.8

    program and cycle
-
+

19.0.2.0.7

  • Bulk membership creation using raw SQL INSERT ON CONFLICT DO NOTHING @@ -756,7 +770,7 @@

    19.0.2.0.7

    _add_beneficiaries with bulk SQL path
-
+

19.0.2.0.6

  • Remove unused entitlement_base_model.py (dead code, never imported)
  • @@ -765,34 +779,34 @@

    19.0.2.0.6

    payment, and fund tests (172 → 492 tests)
-
+

19.0.2.0.5

  • Batch create entitlements and payments instead of one-by-one ORM creates
-
+

19.0.2.0.4

  • Fetch fund balance once per approval batch instead of per entitlement
-
+

19.0.2.0.3

  • Replace cycle computed fields (total_amount, entitlements_count, approval flags) with SQL aggregation queries
-
+

19.0.2.0.2

  • Add composite indexes for frequent query patterns on entitlements and program memberships
-
+

19.0.2.0.1

  • Replace Python-level uniqueness checks with SQL UNIQUE constraints for @@ -801,7 +815,7 @@

    19.0.2.0.1

    constraint creation
-
+

19.0.2.0.0

  • Initial migration to OpenSPP2
  • diff --git a/spp_programs/tests/test_program_viewer_registry_scope.py b/spp_programs/tests/test_program_viewer_registry_scope.py index ffa4076c8..708eb9f08 100644 --- a/spp_programs/tests/test_program_viewer_registry_scope.py +++ b/spp_programs/tests/test_program_viewer_registry_scope.py @@ -44,17 +44,14 @@ def setUpClass(cls): "value": "PV-123", } ) - cls.phone = cls.env["spp.phone.number"].create( - {"partner_id": cls.registrant.id, "phone_no": "09170000000"} - ) + cls.phone = cls.env["spp.phone.number"].create({"partner_id": cls.registrant.id, "phone_no": "09170000000"}) def test_program_viewer_lacks_tier2_registry_viewer(self): """The role must not carry group_registry_viewer (gates the Registry Search portal menu).""" self.assertFalse( self.user.has_group("spp_registry.group_registry_viewer"), - "Program Viewer must not have the Tier-2 registry viewer group " - "(it gates the registry search portal menu)", + "Program Viewer must not have the Tier-2 registry viewer group (it gates the registry search portal menu)", ) def test_program_viewer_keeps_registrant_read(self):