diff --git a/spp_grm_cel/__manifest__.py b/spp_grm_cel/__manifest__.py index 5e63a877..5678cb42 100644 --- a/spp_grm_cel/__manifest__.py +++ b/spp_grm_cel/__manifest__.py @@ -2,7 +2,7 @@ { "name": "OpenSPP GRM: CEL Rules", "summary": "CEL-based routing and escalation rules for GRM tickets", - "version": "19.0.2.0.0", + "version": "19.0.2.0.1", "license": "LGPL-3", "development_status": "Production/Stable", "maintainers": ["jeremi", "gonzalesedwin1123", "emjay0921"], diff --git a/spp_grm_cel/security/ir.model.access.csv b/spp_grm_cel/security/ir.model.access.csv index 9f37958e..5520d43c 100644 --- a/spp_grm_cel/security/ir.model.access.csv +++ b/spp_grm_cel/security/ir.model.access.csv @@ -3,9 +3,9 @@ access_spp_grm_routing_rule_viewer,GRM Routing Rule Viewer Access,model_spp_grm_ access_spp_grm_routing_rule_officer,GRM Routing Rule Officer Access,model_spp_grm_routing_rule,spp_grm.group_grm_officer,1,1,1,0 access_spp_grm_routing_rule_manager,GRM Routing Rule Manager Access,model_spp_grm_routing_rule,spp_grm.group_grm_manager,1,1,1,1 access_spp_grm_routing_rule_base_user,GRM Routing Rule Base User Access,model_spp_grm_routing_rule,base.group_user,1,0,0,0 -access_spp_grm_routing_rule_portal_user,GRM Routing Rule Portal User Access,model_spp_grm_routing_rule,base.group_portal,1,1,1,0 +access_spp_grm_routing_rule_portal_user,GRM Routing Rule Portal User Access,model_spp_grm_routing_rule,base.group_portal,1,0,0,0 access_spp_grm_escalation_rule_viewer,GRM Escalation Rule Viewer Access,model_spp_grm_escalation_rule,spp_grm.group_grm_viewer,1,0,0,0 access_spp_grm_escalation_rule_officer,GRM Escalation Rule Officer Access,model_spp_grm_escalation_rule,spp_grm.group_grm_officer,1,1,1,0 access_spp_grm_escalation_rule_manager,GRM Escalation Rule Manager Access,model_spp_grm_escalation_rule,spp_grm.group_grm_manager,1,1,1,1 access_spp_grm_escalation_rule_base_user,GRM Escalation Rule Base User Access,model_spp_grm_escalation_rule,base.group_user,1,0,0,0 -access_spp_grm_escalation_rule_portal_user,GRM Escalation Rule Portal User Access,model_spp_grm_escalation_rule,base.group_portal,1,1,1,0 +access_spp_grm_escalation_rule_portal_user,GRM Escalation Rule Portal User Access,model_spp_grm_escalation_rule,base.group_portal,1,0,0,0 diff --git a/spp_grm_cel/tests/__init__.py b/spp_grm_cel/tests/__init__.py index ad53b861..bb606d64 100644 --- a/spp_grm_cel/tests/__init__.py +++ b/spp_grm_cel/tests/__init__.py @@ -2,3 +2,4 @@ from . import test_routing_rules from . import test_escalation_rules +from . import test_rule_acl diff --git a/spp_grm_cel/tests/test_rule_acl.py b/spp_grm_cel/tests/test_rule_acl.py new file mode 100644 index 00000000..d1b80b42 --- /dev/null +++ b/spp_grm_cel/tests/test_rule_acl.py @@ -0,0 +1,83 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +"""Security: GRM automation rules must not be writable by portal users. + +Regression test for "Portal users can create global GRM automation rules": the +ACL granted ``base.group_portal`` read/write/create on ``spp.grm.routing.rule`` +and ``spp.grm.escalation.rule``. These are global config models (no record +rules), and routing rules run on ticket creation while escalation rules run on +stage changes and via the hourly cron over all open tickets. A portal user could +therefore plant an always-matching rule via RPC and disrupt grievance handling +globally. + +Portal users must keep only READ access (rule evaluation runs as the current +user, mirroring base.group_user) — never write/create/unlink. GRM staff retain +full management. +""" + +from odoo import Command +from odoo.exceptions import AccessError +from odoo.tests.common import TransactionCase, tagged + +ROUTING_MODEL = "spp.grm.routing.rule" +ESCALATION_MODEL = "spp.grm.escalation.rule" + + +@tagged("post_install", "-at_install") +class TestGRMRuleAcl(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.portal_user = cls.env["res.users"].create( + { + "name": "GRM Portal User", + "login": "grm_portal_acl_test", + "group_ids": [Command.link(cls.env.ref("base.group_portal").id)], + } + ) + cls.grm_manager = cls.env["res.users"].create( + { + "name": "GRM Manager", + "login": "grm_manager_acl_test", + "group_ids": [Command.link(cls.env.ref("spp_grm.group_grm_manager").id)], + } + ) + # An existing rule (created as admin) to test write access against. + cls.routing_rule = cls.env[ROUTING_MODEL].create( + {"name": "Test Routing Rule", "condition_cel": "severity == 'critical'"} + ) + cls.escalation_rule = cls.env[ESCALATION_MODEL].create( + {"name": "Test Escalation Rule", "condition_cel": "days_open > 3"} + ) + + def test_portal_user_cannot_create_routing_rule(self): + """A portal user must NOT be able to create routing rules.""" + with self.assertRaises(AccessError): + self.env[ROUTING_MODEL].with_user(self.portal_user).create({"name": "Portal Rule"}) + + def test_portal_user_cannot_create_escalation_rule(self): + """A portal user must NOT be able to create escalation rules.""" + with self.assertRaises(AccessError): + self.env[ESCALATION_MODEL].with_user(self.portal_user).create({"name": "Portal Rule"}) + + def test_portal_user_cannot_write_routing_rule(self): + """A portal user must NOT be able to modify routing rules.""" + with self.assertRaises(AccessError): + self.routing_rule.with_user(self.portal_user).write({"name": "Hijacked"}) + + def test_portal_user_cannot_write_escalation_rule(self): + """A portal user must NOT be able to modify escalation rules.""" + with self.assertRaises(AccessError): + self.escalation_rule.with_user(self.portal_user).write({"name": "Hijacked"}) + + def test_portal_user_can_read_rules(self): + """Read access is retained so rule evaluation works as the current user + (matches base.group_user).""" + self.env[ROUTING_MODEL].with_user(self.portal_user).check_access("read") + self.env[ESCALATION_MODEL].with_user(self.portal_user).check_access("read") + + def test_grm_manager_can_create_rules(self): + """GRM staff must retain full management of both rule models.""" + routing = self.env[ROUTING_MODEL].with_user(self.grm_manager).create({"name": "Manager Routing Rule"}) + escalation = self.env[ESCALATION_MODEL].with_user(self.grm_manager).create({"name": "Manager Escalation Rule"}) + self.assertTrue(routing.id) + self.assertTrue(escalation.id)