Skip to content

security(hazard): restrict impact records to hazard/registry roles#262

Open
gonzalesedwin1123 wants to merge 1 commit into
19.0from
security-hazard-impact-acl
Open

security(hazard): restrict impact records to hazard/registry roles#262
gonzalesedwin1123 wants to merge 1 commit into
19.0from
security-hazard-impact-acl

Conversation

@gonzalesedwin1123

Copy link
Copy Markdown
Member

Do not merge yet — held for human review.

Summary

spp_hazard's ACL granted base.group_user (every internal user) read on spp.hazard.impact, which links named registrants to incidents with damage level, dates, verification status, notes and verifier. The menu is gated to hazard groups, but the ACL left this sensitive data open to direct RPC/search_read by any internal user. Dedicated hazard groups (read/viewer/officer/manager) and registry_viewer already grant the intended read, so the base.group_user grant on impact was redundant and over-broad.

Fix (surgical — impact only)

  • ACL: remove the base.group_user read row for spp.hazard.impact only. The four non-PII reference/operational models (category, incident, incident.area, impact.type) keep broad internal read — sibling modules (notably spp_drims, which reads incidents heavily) rely on that, and none of them read impact.
  • spp_hazard_programs: read spp.hazard.impact via sudo() in the two emergency-eligibility computes, so a program user without a hazard group isn't blocked (only aggregate counts / eligible registrants surface, never impact rows).
  • Views: gate every element that renders impact rows to users who actually have impact read (group_hazard_read / registry_viewer / group_spp_admin) — the registrant form's stat button, Emergency Response page (with the impact O2M), list columns and search filters; and the incident form's Impacts page + "Affected" stat button. This prevents AccessError for non-hazard users who reach these forms (e.g. via a CR's registrant link, or DRIMS' incident dashboard).

Why not remove all 5 / add a record rule?

Removing base.group_user from all 5 models broke spp_drims (incident reads) and spp_hazard_programs in an earlier revision — caught in review. Impact is the only model with registrant PII; the rest are non-sensitive and depended upon. No record rule is needed: hazard viewers are meant to see all hazard rows (no per-record ownership).

Tests

  • base.group_user is denied read on impact but retains the 4 non-sensitive models.
  • Hazard viewer and a registry officer (no hazard group) retain full impact access (live O2M read).
  • A program user without a hazard group can still compute emergency eligibility (exercises the sudo path live).
  • The incident form's impact_ids O2M is stripped from the arch for a non-hazard user and present for a hazard user.
  • Green: spp_hazard 88/88 · spp_hazard_programs 25/25 · spp_drims 250/250. Lint/semgrep clean.

Reviewed

Three adversarial staff-review passes. The first two each caught a real regression (all-5 removal breaking DRIMS/hazard_programs; then the ungated incident-form O2M) — both fixed. Final pass: APPROVE, no blockers; an exhaustive repo-wide sweep confirmed no remaining path where a non-hazard user can read spp.hazard.impact.

spp_hazard's ACL granted base.group_user (every internal user) read on
spp.hazard.impact, which links named registrants to incidents with damage
level, dates, verification status, notes and verifier. The menu is gated to
hazard groups, but the ACL left this sensitive data open to direct RPC/
search_read by any internal user. Dedicated hazard groups and registry_viewer
already grant the intended read, so the base.group_user grant on impact was
redundant and over-broad.

Fix (surgical — impact only):
- Remove the base.group_user read row for spp.hazard.impact. The four non-PII
  reference/operational models (category, incident, incident.area, impact.type)
  keep broad internal read, which sibling modules (spp_drims) rely on.
- spp_hazard_programs: read spp.hazard.impact via sudo in the emergency-program
  eligibility computes, so a program user without a hazard group is not blocked
  (only aggregate counts / eligible registrants are surfaced, not impact rows).
- Registrant form: gate the hazard-impact stat button, Emergency Response page,
  list columns and search filters to users with impact read (hazard groups /
  registry_viewer / admin), so non-hazard users reaching the form (e.g. via a
  CR's registrant link) don't render impact data.

Tests: base.group_user is denied read on impact but retains the 4 non-sensitive
models; hazard viewer and registry officer retain full impact access; a program
user without a hazard group can still compute emergency eligibility.
spp_hazard 86/86, spp_hazard_programs 25/25, spp_drims 250/250.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request restricts broad read access to the sensitive spp.hazard.impact model by removing its ACL for base.group_user and adding explicit group restrictions to several views. To prevent access errors for non-hazard program users, the emergency eligibility computations now query the impact model using sudo(). The review feedback highlights performance bottlenecks in these computations, recommending the use of _read_group instead of search() to avoid loading large numbers of impact records into memory.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +91 to +92
impact_sudo = self.env["spp.hazard.impact"].sudo() # nosemgrep: odoo-sudo-without-context
impacts = impact_sudo.search(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Performance Bottleneck: Memory Overhead & N+1 Queries

Using search() followed by mapped('registrant_id') loads all matching spp.hazard.impact records into the ORM cache, and then fetches the related res.partner records. In production environments with a large number of impacts, this will cause significant memory overhead and slow down the compute method.

Since you only need the count of unique registrants, you should use _read_group to perform the aggregation directly at the database level. This avoids loading any records into memory.

Suggested change
impact_sudo = self.env["spp.hazard.impact"].sudo() # nosemgrep: odoo-sudo-without-context
impacts = impact_sudo.search(
impact_sudo = self.env["spp.hazard.impact"].sudo() # nosemgrep: odoo-sudo-without-context
groups = impact_sudo._read_group(
[
("incident_id", "in", rec.target_incident_ids.ids),
("verification_status", "=", "verified"),
]
+ damage_domain,
groupby=["registrant_id"],
)
rec.affected_registrant_count = len(groups)
continue

Comment on lines +133 to +134
impact_sudo = self.env["spp.hazard.impact"].sudo() # nosemgrep: odoo-sudo-without-context
impacts = impact_sudo.search(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Performance Bottleneck: Memory Overhead

Using search() followed by mapped('registrant_id') loads all matching spp.hazard.impact records into memory just to extract the registrant IDs.

You can optimize this by using _read_group to fetch only the unique registrant_ids from the database, and then using browse() to return the partner recordset. This avoids loading any impact records.

Suggested change
impact_sudo = self.env["spp.hazard.impact"].sudo() # nosemgrep: odoo-sudo-without-context
impacts = impact_sudo.search(
impact_sudo = self.env["spp.hazard.impact"].sudo() # nosemgrep: odoo-sudo-without-context
groups = impact_sudo._read_group(
[
("incident_id", "in", self.target_incident_ids.ids),
("verification_status", "=", "verified"),
]
+ damage_domain,
groupby=["registrant_id"],
)
return self.env["res.partner"].browse([r.id for r, in groups if r])

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.29%. Comparing base (9d8a2ba) to head (f6f27d3).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #262      +/-   ##
==========================================
- Coverage   75.29%   75.29%   -0.01%     
==========================================
  Files        1094     1095       +1     
  Lines       64997    65000       +3     
==========================================
  Hits        48940    48940              
- Misses      16057    16060       +3     
Flag Coverage Δ
spp_api_v2_change_request 66.85% <ø> (ø)
spp_api_v2_cycles 71.12% <ø> (ø)
spp_api_v2_entitlements 70.19% <ø> (ø)
spp_api_v2_gis 84.27% <ø> (ø)
spp_api_v2_programs 92.03% <ø> (ø)
spp_api_v2_simulation 71.12% <ø> (ø)
spp_audit_programs 0.00% <ø> (?)
spp_base_common 90.26% <ø> (ø)
spp_case_entitlements 97.61% <ø> (ø)
spp_case_programs 97.14% <ø> (ø)
spp_change_request_v2 75.46% <ø> (ø)
spp_cr_type_assign_program 91.17% <ø> (ø)
spp_cr_types_advanced 0.00% <ø> (ø)
spp_cr_types_base 0.00% <ø> (ø)
spp_dci_compliance 92.76% <ø> (ø)
spp_hazard 97.60% <ø> (ø)
spp_hazard_programs 97.22% <100.00%> (+0.07%) ⬆️
spp_programs 65.09% <ø> (-0.03%) ⬇️
spp_registry 86.83% <ø> (ø)
spp_security 66.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_hazard_programs/models/program.py 98.18% <100.00%> (+0.06%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant