Add parent to group to inherit accesses

issue10032
review320661002
This commit is contained in:
Cédric Krier
2021-03-07 12:43:09 +01:00
parent 04a0aea2ad
commit 3e19c4c5eb
9 changed files with 135 additions and 23 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add parent to group to inherit accesses
* Add __access__ to Model
* Add route wrapper to allow null origin
* Add header grouping on report
+2 -2
View File
@@ -523,7 +523,7 @@ class ModelAccess(DeactivableMixin, ModelSQL, ModelView):
user = Transaction().user
model_access = cls.__table__()
ir_model = Model.__table__()
user_group = UserGroup.__table__()
user_group = UserGroup.user_group_all_table()
group = Group.__table__()
access = {}
@@ -729,7 +729,7 @@ class ModelFieldAccess(DeactivableMixin, ModelSQL, ModelView):
field_access = cls.__table__()
ir_model = Model.__table__()
model_field = ModelField.__table__()
user_group = UserGroup.__table__()
user_group = UserGroup.user_group_all_table()
group = Group.__table__()
accesses = {}
+2 -1
View File
@@ -143,6 +143,7 @@ class Rule(ModelSQL, ModelView):
user = EvalEnvironment(User(user_id), User)
return {
'user': user,
'groups': User.get_groups()
}
@staticmethod
@@ -161,7 +162,7 @@ class Rule(ModelSQL, ModelView):
rule_table = cls.__table__()
rule_group = RuleGroup.__table__()
rule_group_group = RuleGroup_Group.__table__()
user_group = User_Group.__table__()
user_group = User_Group.user_group_all_table()
model = Model.__table__()
transaction = Transaction()
+34 -2
View File
@@ -1,7 +1,12 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from itertools import chain
from trytond.model import ModelView, ModelSQL, DeactivableMixin, fields, Unique
from sql import With
from trytond.model import ModelView, ModelSQL, DeactivableMixin, tree, fields
from trytond.model import Unique
from trytond.pool import Pool
from trytond.tools import grouped_slice
@@ -23,11 +28,14 @@ class MenuMany2Many(fields.Many2Many):
return res
class Group(DeactivableMixin, ModelSQL, ModelView):
class Group(DeactivableMixin, tree(), ModelSQL, ModelView):
"Group"
__name__ = "res.group"
name = fields.Char('Name', required=True, select=True, translate=True)
users = fields.Many2Many('res.user-res.group', 'group', 'user', 'Users')
parent = fields.Many2One(
'res.group', "Parent",
help="The group to inherit accesses from.")
model_access = fields.One2Many('ir.model.access', 'group',
'Access Model')
field_access = fields.One2Many('ir.model.field.access', 'group',
@@ -50,6 +58,19 @@ class Group(DeactivableMixin, ModelSQL, ModelView):
]
cls._order.insert(0, ('name', 'ASC'))
@classmethod
def write(cls, *args):
super().write(*args)
pool = Pool()
# Restart the cache on the domain_get method
pool.get('ir.rule')._domain_get_cache.clear()
# Restart the cache for get_groups
pool.get('res.user')._get_groups_cache.clear()
# Restart the cache for model access and view
pool.get('ir.model.access')._get_access_cache.clear()
pool.get('ir.model.field.access')._get_access_cache.clear()
ModelView._fields_view_get_cache.clear()
@classmethod
def copy(cls, groups, default=None):
if default is None:
@@ -67,3 +88,14 @@ class Group(DeactivableMixin, ModelSQL, ModelView):
default['name'] = name
new_groups.extend(super(Group, cls).copy([group], default=default))
return new_groups
@classmethod
def group_parent_all_cte(cls):
group = cls.__table__()
parents = With('id', 'parent', recursive=True)
parents.query = group.select(group.id, group.parent)
parents.query |= group.select(group.id, group.id)
parents.query |= (group
.join(parents, condition=group.parent == parents.id)
.select(group.id, parents.parent))
return parents
+13 -13
View File
@@ -445,7 +445,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_menu1">
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
eval="[('groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_menu"/>
</record>
@@ -460,7 +460,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_action1">
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
eval="[('groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action"/>
</record>
@@ -475,7 +475,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_action_keyword1">
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
eval="[('groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action_keyword"/>
</record>
@@ -491,7 +491,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_action_report1">
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
eval="[('groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action_report"/>
</record>
@@ -507,7 +507,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_action_act_window1">
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
eval="[('groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action_act_window"/>
</record>
@@ -523,7 +523,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_action_wizard1">
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
eval="[('groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action_wizard"/>
</record>
@@ -539,7 +539,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_action_url1">
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
eval="[('groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_action_url"/>
</record>
@@ -778,7 +778,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_sequence">
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
eval="[('groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_sequence"/>
</record>
@@ -791,7 +791,7 @@ this repository contains the full copyright notices and license terms. -->
</record>
<record model="ir.rule" id="rule_sequence_strict">
<field name="domain"
eval="[('groups', 'in', Eval('user', {}).get('groups', []))]"
eval="[('groups', 'in', Eval('groups', []))]"
pyson="1"/>
<field name="rule_group" ref="rule_group_sequence_strict"/>
</record>
@@ -949,7 +949,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.rule" id="rule_group_export_read1">
<field name="domain" eval="[('groups', 'in', Eval('user.groups', []))]" pyson="1"/>
<field name="domain" eval="[('groups', 'in', Eval('groups', []))]" pyson="1"/>
<field name="rule_group" ref="rule_group_export_read"/>
</record>
<record model="ir.rule" id="rule_group_export_read2">
@@ -968,7 +968,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.rule" id="rule_group_export_write1">
<field name="domain" eval="[('write_groups', 'in', Eval('user.groups', []))]" pyson="1"/>
<field name="domain" eval="[('write_groups', 'in', Eval('groups', []))]" pyson="1"/>
<field name="rule_group" ref="rule_group_export_write"/>
</record>
<record model="ir.rule" id="rule_group_export_write2">
@@ -1006,7 +1006,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.rule" id="rule_group_export_line_read1">
<field name="domain" eval="[('export.groups', 'in', Eval('user.groups', []))]" pyson="1"/>
<field name="domain" eval="[('export.groups', 'in', Eval('groups', []))]" pyson="1"/>
<field name="rule_group" ref="rule_group_export_line_read"/>
</record>
<record model="ir.rule" id="rule_group_export_line_read2">
@@ -1025,7 +1025,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.rule" id="rule_group_export_line_write1">
<field name="domain" eval="[('export.write_groups', 'in', Eval('user.groups', []))]" pyson="1"/>
<field name="domain" eval="[('export.write_groups', 'in', Eval('groups', []))]" pyson="1"/>
<field name="rule_group" ref="rule_group_export_line_write"/>
</record>
<record model="ir.rule" id="rule_group_export_line_write2">
+27 -4
View File
@@ -24,7 +24,7 @@ from itertools import groupby
from operator import attrgetter
from ast import literal_eval
from sql import Literal
from sql import Literal, Null
from sql.functions import CurrentTimestamp
from sql.conditionals import Coalesce, Case
from sql.aggregate import Count
@@ -587,15 +587,22 @@ class User(DeactivableMixin, ModelSQL, ModelView):
return res
@classmethod
def get_groups(cls):
def get_groups(cls, name=None):
'''
Return a list of group ids for the user
Return a list of all group ids for the user
'''
user = Transaction().user
groups = cls._get_groups_cache.get(user)
if groups is not None:
return groups
groups = cls.read([Transaction().user], ['groups'])[0]['groups']
pool = Pool()
UserGroup = pool.get('res.user-res.group')
cursor = Transaction().connection.cursor()
user_group = UserGroup.user_group_all_table()
cursor.execute(*user_group.select(
user_group.group,
where=user_group.user == user))
groups = [g for g, in cursor]
cls._get_groups_cache.set(user, groups)
return groups
@@ -969,6 +976,22 @@ class UserGroup(ModelSQL):
pool.get('ir.model.field.access')._get_access_cache.clear()
ModelView._fields_view_get_cache.clear()
@classmethod
def user_group_all_table(cls):
pool = Pool()
Group = pool.get('res.group')
user_group = cls.__table__()
group_parents = Group.group_parent_all_cte()
return (user_group
.join(group_parents,
condition=user_group.group == group_parents.id)
.select(
user_group.user.as_('user'),
group_parents.parent.as_('group'),
where=group_parents.parent != Null,
with_=group_parents))
class Warning_(ModelSQL, ModelView):
'User Warning'
+3
View File
@@ -6,6 +6,9 @@ this repository contains the full copyright notices and license terms. -->
<field name="name"/>
<label name="active"/>
<field name="active"/>
<label name="parent"/>
<field name="parent"/>
<notebook>
<page string="Members" col="2" id="members">
<field name="users" colspan="2"/>
+2 -1
View File
@@ -2,5 +2,6 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="name" expand="1"/>
<field name="name" expand="2"/>
<field name="parent" expand="1"/>
</tree>
+51
View File
@@ -255,6 +255,31 @@ class _ModelAccessTestCase(unittest.TestCase):
self._assert(record)
@with_transaction(context=_context)
def test_access_inherited_from_parent(self):
"Test access inherited from parent"
pool = Pool()
Group = pool.get('res.group')
ModelAccess = pool.get('ir.model.access')
TestAccess = pool.get('test.access')
record, = TestAccess.create([{}])
group, = Group.create([{'name': 'Test'}])
Group.write([self.group], {
'parent': group.id,
})
ModelAccess.create([{
'model': self.model.id,
'group': self.group.id,
self._perm: False,
}])
ModelAccess.create([{
'model': self.model.id,
'group': group.id,
self._perm: True,
}])
self._assert(record)
class ModelAccessReadTestCase(_ModelAccessTestCase):
_perm = 'perm_read'
@@ -664,6 +689,32 @@ class _ModelFieldAccessTestCase(unittest.TestCase):
self._assert1(record)
self._assert2(record)
@with_transaction(context=_context)
def test_access_inherited_from_parent(self):
"Test no access with other group"
pool = Pool()
Group = pool.get('res.group')
FieldAccess = pool.get('ir.model.field.access')
TestAccess = pool.get('test.access')
record, = TestAccess.create([{}])
group, = Group.create([{'name': 'Test'}])
Group.write([self.group], {
'parent': group.id,
})
FieldAccess.create([{
'field': self.field1.id,
'group': self.group.id,
self._perm: False,
}])
FieldAccess.create([{
'field': self.field1.id,
'group': group.id,
self._perm: True,
}])
self._assert1(record)
self._assert2(record)
@with_transaction(context=_context)
def test_two_access(self):
"Test two access"