security(grm): evaluate rules as their owner, scope portal tickets, guard entry points (#379, #380, #381) - #415
Open
gonzalesedwin1123 wants to merge 3 commits into
Open
security(grm): evaluate rules as their owner, scope portal tickets, guard entry points (#379, #380, #381)#415gonzalesedwin1123 wants to merge 3 commits into
gonzalesedwin1123 wants to merge 3 commits into
Conversation
…ts (#379, #381) - eval_as_user_id (system-managed, no default) on both rule models; forced to the creator in create() and re-bound to the editor when targeting changes in write(), so it cannot be forged via context or direct write. Re-bind excludes operational toggles (sequence, active) so a manager archiving/reordering an officer's rule cannot silently transfer ownership to the manager's scope. - apply_routing/apply_escalations evaluate and apply each rule with_user(owner): an officer's always-match rule can no longer ride the superuser cron to seize tickets outside the officer's record-rule scope (#379). Owner-unreadable/unwritable tickets are skipped, not applied elevated. - @api.private on apply_routing, apply_escalations, apply_escalation, check_escalations: no longer RPC-dispatchable (#381). Cron/SLA callers are in-process and unaffected. - Drop the portal and internal-user read rows on both rule models — owner identity removes the need, closing the enumeration surface (#380). - Atomic UPDATE for match_count/escalation_count (no sudo, no lost updates). - CEL validation reports any parser error as ValidationError. - Migration backfills eval_as_user_id from create_uid.
spp.grm.ticket granted base.group_portal read/write/create with no ir.rule targeting portal, so any portal user could read and rewrite every grievance in the system over RPC (the controller's partner scoping is presentation-only). - New portal record rule: partner_id == user.partner_id (own tickets only). - Portal ACL row reduced to read-only (1,0,0,0); submission is handled by the sudo'd portal controller, which needs no direct model write/create. - New tests/test_portal_ticket_acl.py: portal cannot read/search/write/create others' tickets; can read own.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## 19.0 #415 +/- ##
==========================================
+ Coverage 72.24% 72.39% +0.15%
==========================================
Files 419 450 +31
Lines 29813 30972 +1159
==========================================
+ Hits 21539 22423 +884
- Misses 8274 8549 +275
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…#415) README.rst / index.html for spp_grm and spp_grm_cel applied verbatim from the pre-commit CI run's printed diff (local RST regen is not byte-reproducible). test_rule_owner_identity.py reformatted per ruff-format.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the GRM security cluster surfaced during the PR #266 staff review and confirmed by the PR #399 review verification. Three interlocking issues, one PR because they share a trust chain: #380 gives portal RPC reach to tickets → #381 gives reach to the rule engine → #379 means the engine acts as superuser.
Closes #379, #380, #381.
What's in it
spp_grm_cel19.0.2.0.1 → 2.0.2 + migrationspp_grm19.0.2.0.1 → 2.0.2@api.private(not RPC-callable)spp_grm_cel#379 — owner-identity evaluation
The hourly
check_escalationscron ran as superuser with record rules bypassed. A GRM officer (who can author rules) could create one always-match escalation rule pointing at themselves; within the hour the cron reassigned every open ticket in the database to them. Same elevated-evaluation shape as spp_alerts #364.Fix (mirrors the #364 owner-identity pattern):
eval_as_user_idon both rule models — no Python default (a default would let_init_columnbackfill the upgrade user and let a client forge it viadefault_eval_as_user_id); forced to the creator increate(), and re-bound to the editor only when a rule's targeting/action fields change inwrite().sequence,active) are deliberately excluded from the re-bind set: archiving/reordering an officer's rule must not silently transfer ownership to the manager doing that routine cleanup (a confused-deputy escalation caught in review).apply_routing/apply_escalationsevaluate and apply each rulewith_user(owner); a ticket the owner can't read/write is skipped, never applied elevated. So an officer's rule can only ever act within the officer's own record-rule scope. The cron and the sudo'd SLA path inherit this automatically (the identity comes from the rule, not the caller).eval_as_user_idfromcreate_uidfor pre-existing rules.#380 — portal ticket isolation
spp.grm.ticketgrantedbase.group_portalread/write/create with noir.ruletargeting portal, so any authenticated portal user could read and rewrite every grievance in the system over RPC (the controller'spartner_idscoping is presentation-only).partner_id == user.partner_id(own tickets only).#381 — entry-point guards
apply_routing,apply_escalations,apply_escalation,check_escalationsare now@api.private— rejected forcall_kwRPC dispatch. The cron (server-sidemodel.check_escalations()), the SLA-breach path, and ticket create/stage-write are all in-process Python calls and unaffected.Also (folded in from the #399 review)
UPDATEformatch_count/escalation_count(drops thesudo()read-modify-write; no lost updates under concurrent cron/UI escalation).ValidationError(wasSyntaxError-only).🔴 Release notes — behavior changes
Verification
spp_grm_cel43 tests,spp_grm30 tests — 0 failed, 0 errors.openspp2-code-reviewer(conventions/principles) + an adversarial pass against Odoo 19 core. Both confirmed the two HIGH holes closed and the owner-identity/guard mechanisms sound; the one Important finding (theactive/sequenceconfused-deputy) is fixed with a regression test. Lint clean (ruff, pylint-odoo, bandit, semgrep).Follow-ups (not in scope)
trigger_after_hoursis not enforced at apply time (_check_time_triggeris unused) — pre-existing; file to wire it in or remove the field.ondelete="restrict"oneval_as_user_idblocks deleting a user who owns rules — consider reassign-on-archive if it bites.(4, id)tuple atgrm_escalation_rule.py— Odoo 19Command.linknit, pre-existing.