Skip to content
11 changes: 11 additions & 0 deletions spp_area/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,17 @@ Dependencies
Changelog
=========

19.0.2.0.1
~~~~~~~~~~

- fix(security): add a global ``ir.rule`` on ``res.partner`` that
filters registrants by ``area_id`` for users with ``center_area_ids``
set (OP#989). Replaces the limited ``search_read`` /
``web_search_read`` override in ``models/registrant.py`` which missed
``name_search`` (Many2one dropdowns), ``search_count``,
``read_group``, and related-field traversal. The rule's conditional
domain is a no-op for users without center areas (global roles).

19.0.2.0.0
~~~~~~~~~~

Expand Down
3 changes: 2 additions & 1 deletion spp_area/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"name": "OpenSPP Area Management",
"summary": "Establishes direct associations between OpenSPP registrants, beneficiary groups, and their corresponding geographical administrative areas. It validates registrant-area linkages against official area types, ensuring data integrity and enabling targeted program delivery and analysis.",
"category": "OpenSPP/Core",
"version": "19.0.2.0.0",
"version": "19.0.2.0.1",
"sequence": 1,
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
Expand All @@ -33,6 +33,7 @@
"security/privileges.xml",
"security/groups.xml",
"security/ir.model.access.csv",
"security/rules.xml",
"wizard/area_import_language_wizard_views.xml",
"views/area_base.xml",
"views/area_tag.xml",
Expand Down
4 changes: 4 additions & 0 deletions spp_area/readme/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 19.0.2.0.1

- fix(security): add a global `ir.rule` on `res.partner` that filters registrants by `area_id` for users with `center_area_ids` set (OP#989). Replaces the limited `search_read` / `web_search_read` override in `models/registrant.py` which missed `name_search` (Many2one dropdowns), `search_count`, `read_group`, and related-field traversal. The rule's conditional domain is a no-op for users without center areas (global roles).

### 19.0.2.0.0

- Initial migration to OpenSPP2
33 changes: 33 additions & 0 deletions spp_area/security/rules.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Part of OpenSPP. See LICENSE file for full copyright and licensing details.

Area-based row-level filtering for registrants (OP#989).

Replaces / strengthens the `_prepare_domain` override in models/registrant.py
which only catches `search_read` / `web_search_read`. An ir.rule applies to
every ORM read path automatically: `search`, `search_count`, `read_group`,
`name_search` (Many2one dropdowns), `read`, and related-field traversal.

The rule is scoped to `is_registrant = True` only — non-registrant contacts
(users' own partners, admins, companies, system bots, mail followers) must
remain readable, otherwise every record using `message_partner_ids` /
`message_follower_ids` blows up for local users with `center_area_ids`.

The conditional domain makes the rule a no-op for users without
`center_area_ids` (global roles).
-->
<odoo noupdate="1">
<record id="rule_res_partner_area_filter" model="ir.rule">
<field name="name">Registrants: visible only within user's center areas</field>
<field name="model_id" ref="base.model_res_partner" />
<field
name="domain_force"
>['|', ('is_registrant', '=', False), ('area_id', 'child_of', user.center_area_ids.ids)] if user.center_area_ids else []</field>
<field name="global" eval="True" />
<field name="perm_read" eval="True" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="True" />
<field name="perm_unlink" eval="True" />
</record>
</odoo>
12 changes: 12 additions & 0 deletions spp_area/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,18 @@ <h2><a class="toc-backref" href="#toc-entry-1">Changelog</a></h2>
</div>
</div>
<div class="section" id="section-1">
<h1>19.0.2.0.1</h1>
<ul class="simple">
<li>fix(security): add a global <tt class="docutils literal">ir.rule</tt> on <tt class="docutils literal">res.partner</tt> that
filters registrants by <tt class="docutils literal">area_id</tt> for users with <tt class="docutils literal">center_area_ids</tt>
set (OP#989). Replaces the limited <tt class="docutils literal">search_read</tt> /
<tt class="docutils literal">web_search_read</tt> override in <tt class="docutils literal">models/registrant.py</tt> which missed
<tt class="docutils literal">name_search</tt> (Many2one dropdowns), <tt class="docutils literal">search_count</tt>,
<tt class="docutils literal">read_group</tt>, and related-field traversal. The rule’s conditional
domain is a no-op for users without center areas (global roles).</li>
</ul>
</div>
<div class="section" id="section-2">
<h1>19.0.2.0.0</h1>
<ul class="simple">
<li>Initial migration to OpenSPP2</li>
Expand Down
12 changes: 12 additions & 0 deletions spp_change_request_v2/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,18 @@ Before declaring a new CR type complete:
Changelog
=========

19.0.2.0.5
~~~~~~~~~~

- fix(security): add a global ``ir.rule`` on ``spp.change.request`` that
filters by ``registrant_id.area_id`` against the user's
``center_area_ids`` (OP#989 round-2). The earlier ``_prepare_domain``
override only caught ``search_read`` / ``web_search_read`` and missed
the registrant Many2one picker (which uses ``name_search`` →
``_search``), so users could still select out-of-area registrants. The
conditional domain is a no-op for users with no center areas (global
roles).

19.0.2.0.3
~~~~~~~~~~

Expand Down
4 changes: 3 additions & 1 deletion spp_change_request_v2/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "OpenSPP Change Request V2",
"version": "19.0.2.0.3",
"version": "19.0.2.0.5",
"sequence": 50,
"category": "OpenSPP",
"summary": "Configuration-driven change request system with UX improvements, conflict detection and duplicate prevention",
Expand All @@ -13,6 +13,7 @@
"mail",
"spp_base_common",
"spp_registry",
"spp_area",
"spp_security",
"spp_approval",
"spp_event_data",
Expand All @@ -24,6 +25,7 @@
"security/privileges.xml",
"security/groups.xml",
"security/rules.xml",
"security/area_filter_rules.xml",
"security/ir.model.access.csv",
# Views (loaded before data that references them)
"views/dms_file_views.xml",
Expand Down
4 changes: 4 additions & 0 deletions spp_change_request_v2/readme/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 19.0.2.0.5

- fix(security): add a global `ir.rule` on `spp.change.request` that filters by `registrant_id.area_id` against the user's `center_area_ids` (OP#989 round-2). The earlier `_prepare_domain` override only caught `search_read` / `web_search_read` and missed the registrant Many2one picker (which uses `name_search` → `_search`), so users could still select out-of-area registrants. The conditional domain is a no-op for users with no center areas (global roles).

### 19.0.2.0.3

- fix: add HTML escaping to all computed Html fields with `sanitize=False` to prevent stored XSS (#50)
Expand Down
34 changes: 34 additions & 0 deletions spp_change_request_v2/security/area_filter_rules.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Part of OpenSPP. See LICENSE file for full copyright and licensing details.

Area-based row-level filtering for change requests (OP#989).

QA round 1 surfaced that the original `_prepare_domain` override on
`spp.change.request` only caught `search_read` / `web_search_read`. The
registrant picker on the CR form (a Many2one widget) calls `name_search` ->
`_search` and bypasses the override. An ir.rule applies to every ORM read
path automatically, including `name_search`, `search_count`, `read_group`,
and related-field traversal.

Mirrors the conditional domain pattern from `spp_area/security/rules.xml`
so users without `center_area_ids` (global roles) are unaffected. The
module now depends on `spp_area` directly so `user.center_area_ids`
can be referenced without defensive guards.
-->
<odoo noupdate="1">
<record id="rule_spp_change_request_area_filter" model="ir.rule">
<field
name="name"
>Change Request: visible only within user's center areas</field>
<field name="model_id" ref="model_spp_change_request" />
<field
name="domain_force"
>[('registrant_id.area_id', 'child_of', user.center_area_ids.ids)] if user.center_area_ids else []</field>
<field name="global" eval="True" />
<field name="perm_read" eval="True" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="True" />
<field name="perm_unlink" eval="True" />
</record>
</odoo>
19 changes: 16 additions & 3 deletions spp_change_request_v2/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1339,26 +1339,39 @@ <h2>Changelog</h2>
</div>
</div>
<div class="section" id="section-1">
<h1>19.0.2.0.5</h1>
<ul class="simple">
<li>fix(security): add a global <tt class="docutils literal">ir.rule</tt> on <tt class="docutils literal">spp.change.request</tt> that
filters by <tt class="docutils literal">registrant_id.area_id</tt> against the user’s
<tt class="docutils literal">center_area_ids</tt> (OP#989 round-2). The earlier <tt class="docutils literal">_prepare_domain</tt>
override only caught <tt class="docutils literal">search_read</tt> / <tt class="docutils literal">web_search_read</tt> and missed
the registrant Many2one picker (which uses <tt class="docutils literal">name_search</tt> →
<tt class="docutils literal">_search</tt>), so users could still select out-of-area registrants. The
conditional domain is a no-op for users with no center areas (global
roles).</li>
</ul>
</div>
<div class="section" id="section-2">
<h1>19.0.2.0.3</h1>
<ul class="simple">
<li>fix: add HTML escaping to all computed Html fields with
<tt class="docutils literal">sanitize=False</tt> to prevent stored XSS (#50)</li>
</ul>
</div>
<div class="section" id="section-2">
<div class="section" id="section-3">
<h1>19.0.2.0.2</h1>
<ul class="simple">
<li>fix: fix batch approval wizard line deletion (#130)</li>
</ul>
</div>
<div class="section" id="section-3">
<div class="section" id="section-4">
<h1>19.0.2.0.1</h1>
<ul class="simple">
<li>fix: skip field types before getattr and isolate detail prefetch
(#129)</li>
</ul>
</div>
<div class="section" id="section-4">
<div class="section" id="section-5">
<h1>19.0.2.0.0</h1>
<ul class="simple">
<li>Initial migration to OpenSPP2</li>
Expand Down
13 changes: 13 additions & 0 deletions spp_programs/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,19 @@ Dependencies
Changelog
=========

19.0.2.1.2
~~~~~~~~~~

- fix(security): add global ``ir.rule`` records on
``spp.program.membership`` and ``spp.cycle.membership`` that filter by
``partner_id.area_id`` against the user's ``center_area_ids`` (OP#989
round-2). The earlier Python ``_prepare_domain`` override on program
memberships only caught ``search_read`` / ``web_search_read`` and
missed counts (``search_count``, ``read_group``), dropdowns
(``name_search``), and related-field traversal — and cycle memberships
had no filter at all. Both rules use a conditional domain that's a
no-op for users with no center areas (global roles).

19.0.2.1.1
~~~~~~~~~~

Expand Down
3 changes: 2 additions & 1 deletion spp_programs/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.1.1",
"version": "19.0.2.1.2",
"sequence": 1,
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
Expand Down Expand Up @@ -39,6 +39,7 @@
"security/program_security.xml",
"security/ir.model.access.csv",
"security/registrant_rule.xml",
"security/area_filter_rules.xml",
# Data files
"data/sequences.xml",
"data/queue_data.xml",
Expand Down
4 changes: 4 additions & 0 deletions spp_programs/readme/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 19.0.2.1.2

- fix(security): add global `ir.rule` records on `spp.program.membership` and `spp.cycle.membership` that filter by `partner_id.area_id` against the user's `center_area_ids` (OP#989 round-2). The earlier Python `_prepare_domain` override on program memberships only caught `search_read` / `web_search_read` and missed counts (`search_count`, `read_group`), dropdowns (`name_search`), and related-field traversal — and cycle memberships had no filter at all. Both rules use a conditional domain that's a 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 Programs and Entitlements lists on registrant forms and to Program Membership inline lines — removes the four empty placeholder rows Odoo 19 inserts on inline list-in-form views (#943).
Expand Down
46 changes: 46 additions & 0 deletions spp_programs/security/area_filter_rules.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Part of OpenSPP. See LICENSE file for full copyright and licensing details.

Area-based row-level filtering for program / cycle memberships (OP#989).

QA round 1 surfaced gaps in the original Python `_prepare_domain` override:
counts (`search_count`, `read_group`), Many2one dropdowns (`name_search`),
related-field traversal, and `spp.cycle.membership` (untouched) all bypassed
the area filter. An ir.rule fixes all of those at once because it applies to
every ORM read path automatically.

Mirrors the conditional domain pattern from `spp_area/security/rules.xml`
so users without `center_area_ids` (global roles) are unaffected.
-->
<odoo noupdate="1">
<record id="rule_spp_program_membership_area_filter" model="ir.rule">
<field
name="name"
>Program Membership: visible only within user's center areas</field>
<field name="model_id" ref="model_spp_program_membership" />
<field
name="domain_force"
>[('partner_id.area_id', 'child_of', user.center_area_ids.ids)] if user.center_area_ids else []</field>
<field name="global" eval="True" />
<field name="perm_read" eval="True" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="True" />
<field name="perm_unlink" eval="True" />
</record>

<record id="rule_spp_cycle_membership_area_filter" model="ir.rule">
<field
name="name"
>Cycle Membership: visible only within user's center areas</field>
<field name="model_id" ref="model_spp_cycle_membership" />
<field
name="domain_force"
>[('partner_id.area_id', 'child_of', user.center_area_ids.ids)] if user.center_area_ids else []</field>
<field name="global" eval="True" />
<field name="perm_read" eval="True" />
<field name="perm_write" eval="True" />
<field name="perm_create" eval="True" />
<field name="perm_unlink" eval="True" />
</record>
</odoo>
Loading
Loading