[IMP] base_changeset: overhaul security

pull/2380/head
Holger Brunn 2021-11-04 14:07:59 +01:00 committed by Miquel Raïch
parent 8dfc8caba4
commit 47fbaeda11
10 changed files with 87 additions and 13 deletions

View File

@ -88,7 +88,8 @@ class Base(models.AbstractModel):
{
field_name: value
for field_name, value in vals.items()
if self._fields[field_name].required
if field_name in self._fields and
self._fields[field_name].required
and isinstance(
self._fields[field_name], fields.Many2one,
)

View File

@ -43,6 +43,13 @@ class ChangesetFieldRule(models.Model):
help="Use this rule only on records where this is true. "
"Available variables: object, user",
)
validator_group_ids = fields.Many2many(
'res.groups', 'changeset_field_rule_validator_group_rel',
string='Validator Groups', default=lambda self: self.env.ref(
'base_changeset.group_changeset_user',
raise_if_not_found=False,
) or self.env['res.groups'],
)
def init(self):
"""Ensure there is at most one rule with source_model_id NULL."""

View File

@ -397,8 +397,13 @@ class RecordChangesetChange(models.Model):
def _compute_user_can_validate_changeset(self):
is_superuser = self.env.is_superuser()
has_group = self.user_has_groups("base_changeset.group_changeset_user")
user_groups = self.env.user.groups_id
for rec in self:
can_validate = rec._is_change_pending() and (is_superuser or has_group)
can_validate = rec._is_change_pending() and (
is_superuser
or rec.rule_id.validator_group_ids & user_groups
or has_group
)
if rec.rule_id.prevent_self_validation:
can_validate = can_validate and rec.modified_by_id != self.env.user
rec.user_can_validate_changeset = can_validate

View File

@ -64,3 +64,12 @@ same that is passed in ``__changeset_rules_source_model``).
The source is used for the application of the rules, allowing to have a
different rule for a different source. It is also stored on the changeset for
information.
Notes on security
-----------------
Note that by default, changeset users see all changes on all configured
rules. This circumvents read restrictions on the original records, so if you
have restrictions on models with changeset rules, changeset users will still
see all changes of all records, and applying a change on an inaccessible record
will fail.

View File

@ -17,10 +17,7 @@
<data noupdate="1">
<record id="group_changeset_manager" model="res.groups">
<field name="users" eval="[(4, ref('base.user_root'))]" />
</record>
<record id="group_changeset_user" model="res.groups">
<field name="implied_ids" eval="[(4, ref('group_changeset_manager'))]" />
<field name="users" eval="[(4, ref('base.user_root'))]" />
<field name="implied_ids" eval="[(4, ref('group_changeset_user'))]" />
</record>
</data>
</odoo>

View File

@ -16,4 +16,16 @@
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record id="rule_record_changeset_change_base_user" model="ir.rule">
<field name="name">Restrict changeset change access for users</field>
<field name="model_id" ref="model_record_changeset_change" />
<field name="groups" eval="[(4, ref('base.group_user'))]" />
<field name="domain_force">[('create_uid', '=', user.id)]</field>
</record>
<record id="rule_record_changeset_change_user" model="ir.rule">
<field name="name">Allow changeset change access for changeset users</field>
<field name="model_id" ref="model_record_changeset_change" />
<field name="groups" eval="[(4, ref('group_changeset_user'))]" />
<field name="domain_force">[('rule_id.validator_group_ids', 'in', user.groups_id.ids)]</field>
</record>
</odoo>

View File

@ -2,3 +2,4 @@ from . import test_changeset_flow
from . import test_changeset_field_type
from . import test_changeset_origin
from . import test_changeset_field_rule
from . import test_changeset_security

View File

@ -77,13 +77,19 @@ class TestChangesetFlow(ChangesetTestCommon, TransactionCase):
def test_create_new_changeset(self):
""" Create a new partner with a changeset """
new = self.env["res.partner"].with_context(
new = (
self.env["res.partner"]
.with_context(
test_record_changeset=True,
).create({
)
.create(
{
"name": "partner",
"street": "street",
"street2": "street2",
})
}
)
)
self.assertEqual(new.count_pending_changesets, 1)
self.assert_changeset(
new,

View File

@ -0,0 +1,33 @@
# Copyright 2021 Hunki Enterprises BV (<https://hunki-enterprises.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
from .common import ChangesetTestCommon
class TestChangesetFlow(ChangesetTestCommon, TransactionCase):
""" Check that changesets don't leak information """
def setUp(self):
super().setUp()
self.env['changeset.field.rule'].search([]).unlink()
self.rule = self.env['changeset.field.rule'].create({
'model_id': self.env.ref('base.model_ir_config_parameter').id,
'field_id': self.env.ref('base.field_ir_config_parameter__key').id,
'action': 'auto',
})
def test_change_unprivileged_user(self):
"""
Check that unprivileged users can't see changesets they didn't create
"""
user = self.env.ref('base.user_demo')
self.env['ir.config_parameter'].with_context(
test_record_changeset=True,
).set_param('hello', 'world')
changeset = self.env['record.changeset.change'].search([
('rule_id', '=', self.rule.id),
])
self.assertTrue(changeset)
self.assertFalse(changeset.sudo(user).search([('id', '=', changeset.id)]))

View File

@ -8,6 +8,7 @@
<field name="field_id" />
<field name="source_model_id" />
<field name="expression" />
<field name="validator_group_ids" />
<field
name="company_id"
groups="base.group_multi_company"
@ -42,6 +43,7 @@
<field name="source_model_id" widget="selection" />
<field name="prevent_self_validation" />
<field name="expression" placeholder="True" />
<field name="validator_group_ids" widget="many2many_tags" />
</group>
</group>
</sheet>
@ -55,6 +57,7 @@
<field name="field_id" />
<field name="source_model_id" />
<field name="action" />
<field name="validator_group_ids" />
<group string="Group By" name="groupby">
<filter
name="model_groupby"