Browse Source

QMS Release

pull/6/head
Mathias Markl 6 years ago
parent
commit
366d559718
  1. 2
      muk_autovacuum/__manifest__.py
  2. 6
      muk_autovacuum/models/ir_autovacuum.py
  3. 7
      muk_autovacuum/models/rules.py
  4. 5
      muk_security/__init__.py
  5. 6
      muk_security/__manifest__.py
  6. 23
      muk_security/base/__init__.py
  7. 36
      muk_security/base/api.py
  8. 40
      muk_security/base/models.py
  9. 35
      muk_security/data/autovacuum.xml
  10. 5
      muk_security/doc/changelog.rst
  11. 4
      muk_security/doc/index.rst
  12. 5
      muk_security/models/__init__.py
  13. 41
      muk_security/models/access.py
  14. 171
      muk_security/models/access_groups.py
  15. 178
      muk_security/models/groups.py
  16. 39
      muk_security/models/ir_model_access.py
  17. 39
      muk_security/models/ir_rule.py
  18. 31
      muk_security/models/lock.py
  19. 82
      muk_security/models/locking.py
  20. 31
      muk_security/models/res_groups.py
  21. 57
      muk_security/models/res_users.py
  22. 43
      muk_security/models/security_groups.py
  23. 5
      muk_security/security/ir.model.access.csv
  24. 45
      muk_security/security/security.xml
  25. 7
      muk_security/static/description/index.html
  26. 22
      muk_security/tests/__init__.py
  27. 50
      muk_security/tests/test_suspend_security.py
  28. 22
      muk_security/tools/__init__.py
  29. 36
      muk_security/tools/helper.py
  30. 6
      muk_security/views/groups.xml
  31. 5
      muk_utils/__manifest__.py
  32. 41
      muk_utils/data/ir_cron.xml
  33. 3
      muk_utils/models/__init__.py
  34. 180
      muk_utils/models/groups.py
  35. 61
      muk_utils/models/model.py
  36. 68
      muk_utils/models/res_groups.py
  37. 85
      muk_utils/models/res_users.py

2
muk_autovacuum/__manifest__.py

@ -20,7 +20,7 @@
{
"name": "MuK Autovacuum",
"summary": """Configure automatic garbage collection""",
"version": "11.0.2.1.0",
"version": "11.0.2.1.1",
"category": "Extra Tools",
"license": "AGPL-3",
"website": "https://www.mukit.at",

6
muk_autovacuum/models/ir_autovacuum.py

@ -62,8 +62,10 @@ class AutoVacuum(models.AbstractModel):
if rule.state == 'time':
computed_time = datetime.datetime.utcnow() - _types[rule.time_type](rule.time)
domain = [(rule.time_field.name, '<', computed_time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
if rule.protect_starred and "starred" in rule.model.field_id.mapped("name"):
domain.append(('starred', '=', False))
if rule.protect_starred:
for field in rule.model.field_id:
if field.name in ['starred', 'favorite', 'is_starred', 'is_favorite']:
domain.append((field.name, '=', False))
if rule.only_inactive and "active" in rule.model.field_id.mapped("name"):
domain.append(('active', '=', False))
_logger.info(_("GC domain: %s"), domain)

7
muk_autovacuum/models/rules.py

@ -205,7 +205,12 @@ class AutoVacuumRules(models.Model):
'size': [('invisible', True)],
'domain': [('invisible', True)],
'code': [('invisible', True)]},
help="Do not delete starred records.")
help="""Do not delete starred records.
Checks for the following fields:
- starred
- favorite
- is_starred
- is_favorite""")
only_inactive = fields.Boolean(
string='Only Archived',

5
muk_security/__init__.py

@ -17,4 +17,7 @@
#
###################################################################################
from . import models
from . import models
def _patch_system():
from . import base

6
muk_security/__manifest__.py

@ -20,7 +20,7 @@
{
"name": "MuK Security",
"summary": """Security Features""",
"version": "11.0.1.0.0",
"version": "11.0.1.1.0",
"category": "Extra Tools",
"license": "AGPL-3",
"website": "http://www.mukit.at",
@ -31,11 +31,14 @@
],
"depends": [
"muk_utils",
"muk_autovacuum",
],
"data": [
"security/security.xml",
"security/ir.model.access.csv",
"views/lock.xml",
"views/groups.xml",
"data/autovacuum.xml",
],
"qweb": [
"static/src/xml/*.xml",
@ -50,4 +53,5 @@
"auto_install": True,
"application": False,
"installable": True,
"post_load": "_patch_system",
}

23
muk_security/base/__init__.py

@ -0,0 +1,23 @@
###################################################################################
#
# MuK Document Management System
#
# Copyright (C) 2018 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
from . import api
from . import models

36
muk_security/base/api.py

@ -0,0 +1,36 @@
###################################################################################
#
# Copyright (C) 2018 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
import logging
from odoo import models, api, SUPERUSER_ID
from odoo.addons.muk_utils.tools import patch
from odoo.addons.muk_security.tools import helper
_logger = logging.getLogger(__name__)
@api.model
@patch.monkey_patch(api.Environment)
def __call__(self, cr=None, user=None, context=None):
env = __call__.super(self, cr, user, context)
if user and isinstance(user, helper.NoSecurityUid):
env.uid = user
return env
return env

40
muk_security/base/models.py

@ -0,0 +1,40 @@
###################################################################################
#
# Copyright (C) 2018 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
import logging
from odoo import models, api, SUPERUSER_ID
from odoo.addons.muk_utils.tools import patch
from odoo.addons.muk_security.tools import helper
_logger = logging.getLogger(__name__)
@api.model
def suspend_security(self, user=None):
return self.sudo(user=helper.NoSecurityUid(user or self.env.uid))
models.BaseModel.suspend_security = suspend_security
@api.model
@patch.monkey_patch_model(models.BaseModel)
def check_field_access_rights(self, operation, fields):
if isinstance(self.env.uid, helper.NoSecurityUid):
return fields or list(self._fields)
return check_field_access_rights.super(self, operation, fields)

35
muk_security/data/autovacuum.xml

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2018 MuK IT GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<odoo noupdate="1">
<record id="security_lock_autovacuum_rule" model="muk_autovacuum.rules">
<field name="name">Cleans up locks that have not been removed correctly</field>
<field name="state">code</field>
<field name="model" ref="model_muk_security_lock"/>
<field name="code">
locks = env['muk_security.lock']
for lock in model.search([]):
if not lock.lock_ref:
locks |= lock
locks.unlink()
</field>
</record>
</odoo>

5
muk_security/doc/changelog.rst

@ -1,3 +1,8 @@
`1.1.0`
-------
- Updated dependencies
`1.0.0`
-------

4
muk_security/doc/index.rst

@ -2,7 +2,7 @@
MuK Security
============
Technical module to provide some utility features and libraries that can be used
Technical module to provide some utility and security features that can be used
in other applications. This module has no direct effect on the running system.
Installation
@ -23,7 +23,7 @@ No additional configuration is needed to use this module.
Usage
=============
This module has no direct visible effect on the system. It provide utility features.
This module has no direct visible effect on the system. It provide security features.
Credits
=======

5
muk_security/models/__init__.py

@ -20,6 +20,9 @@
from . import lock
from . import locking
from . import access
from . import groups
from . import access_groups
from . import security_groups
from . import res_groups
from . import res_users
from . import ir_rule
from . import ir_model_access

41
muk_security/models/access.py

@ -37,18 +37,22 @@ class BaseModelAccess(models.AbstractModel):
permission_read = fields.Boolean(
compute='_compute_permissions',
search='_search_permission_read',
string="Read Access")
permission_create = fields.Boolean(
compute='_compute_permissions',
search='_search_permission_create',
string="Create Access")
permission_write = fields.Boolean(
compute='_compute_permissions',
search='_search_permission_write',
string="Write Access")
permission_unlink = fields.Boolean(
compute='_compute_permissions',
search='_search_permission_unlink',
string="Delete Access")
#----------------------------------------------------------
@ -76,12 +80,47 @@ class BaseModelAccess(models.AbstractModel):
try:
access_right = self.check_access_rights(operation, raise_exception)
access_rule = self.check_access_rule(operation) == None
return access_right and access_rule
access = access_right and access_rule
if not access and raise_exception:
raise AccessError(_("This operation is forbidden!"))
return access
except AccessError:
if raise_exception:
raise AccessError(_("This operation is forbidden!"))
return False
#----------------------------------------------------------
# Search
#----------------------------------------------------------
@api.model
def _search_permission_read(self, operator, operand):
records = self.search([]).filtered(lambda r: r.check_access('read') == True)
if operator == '=' and operand:
return [('id', 'in', records.mapped('id'))]
return [('id', 'not in', records.mapped('id'))]
@api.model
def _search_permission_create(self, operator, operand):
records = self.search([]).filtered(lambda r: r.check_access('create') == True)
if operator == '=' and operand:
return [('id', 'in', records.mapped('id'))]
return [('id', 'not in', records.mapped('id'))]
@api.model
def _search_permission_write(self, operator, operand):
records = self.search([]).filtered(lambda r: r.check_access('write') == True)
if operator == '=' and operand:
return [('id', 'in', records.mapped('id'))]
return [('id', 'not in', records.mapped('id'))]
@api.model
def _search_permission_unlink(self, operator, operand):
records = self.search([]).filtered(lambda r: r.check_access('unlink') == True)
if operator == '=' and operand:
return [('id', 'in', records.mapped('id'))]
return [('id', 'not in', records.mapped('id'))]
#----------------------------------------------------------
# Read, View
#----------------------------------------------------------

171
muk_security/models/access_groups.py

@ -23,6 +23,8 @@ from odoo import _, SUPERUSER_ID
from odoo import models, api, fields
from odoo.exceptions import AccessError
from odoo.addons.muk_security.tools import helper
_logger = logging.getLogger(__name__)
class BaseModelAccessGroups(models.AbstractModel):
@ -32,12 +34,18 @@ class BaseModelAccessGroups(models.AbstractModel):
_inherit = 'muk_security.access'
# Set it to True to enforced security even if no group has been set
_strict_security = False
_strict_security = False
# If set the group fields are restricted by the access group
_field_groups = None
# If set the suspend fields are restricted by the access group
_suspend_groups = None
#----------------------------------------------------------
# Function
# Datebase
#----------------------------------------------------------
@api.model
def _add_magic_fields(self):
super(BaseModelAccessGroups, self)._add_magic_fields()
@ -45,6 +53,30 @@ class BaseModelAccessGroups(models.AbstractModel):
if name not in self._fields:
self._add_field(name, field)
base, model = self._name.split(".")
add('suspend_security_read', fields.Boolean(
_module=base,
string="Suspend Security for Read",
automatic=True,
default=False,
groups=self._suspend_groups))
add('suspend_security_create', fields.Boolean(
_module=base,
string="Suspend Security for Create",
automatic=True,
default=False,
groups=self._suspend_groups))
add('suspend_security_write', fields.Boolean(
_module=base,
string="Suspend Security for Write",
automatic=True,
default=False,
groups=self._suspend_groups))
add('suspend_security_unlink', fields.Boolean(
_module=base,
string="Suspend Security for Unlink",
automatic=True,
default=False,
groups=self._suspend_groups))
add('groups', fields.Many2many(
_module=base,
comodel_name='muk_security.groups',
@ -52,7 +84,8 @@ class BaseModelAccessGroups(models.AbstractModel):
column1='aid',
column2='gid',
string="Groups",
automatic=True))
automatic=True,
groups=self._field_groups))
add('complete_groups', fields.Many2many(
_module=base,
comodel_name='muk_security.groups',
@ -62,7 +95,32 @@ class BaseModelAccessGroups(models.AbstractModel):
string="Complete Groups",
compute='_compute_groups',
store=True,
automatic=True))
automatic=True,
groups=self._field_groups))
#----------------------------------------------------------
# Function
#----------------------------------------------------------
@api.multi
def trigger_computation(self, fields, *largs, **kwargs):
super(BaseModelAccessGroups, self).trigger_computation(fields, *largs, **kwargs)
if "complete_groups" in fields:
self.suspend_security()._compute_groups()
@api.model
def check_group_values(self, values):
if any(field in values for field in ['groups']):
return True
return False
@api.multi
@api.returns('muk_security.groups')
def get_groups(self):
self.ensure_one()
groups = self.env['muk_security.groups']
groups |= self.groups
return groups
@api.model
def _get_no_access_ids(self):
@ -83,6 +141,18 @@ class BaseModelAccessGroups(models.AbstractModel):
else:
return []
@api.model
def _get_suspended_access_ids(self, operation):
base, model = self._name.split(".")
sql = '''
SELECT id
FROM %s a
WHERE a.suspend_security_%s = true
''' % (self._table, operation)
self.env.cr.execute(sql)
fetch = self.env.cr.fetchall()
return len(fetch) > 0 and list(map(lambda x: x[0], fetch)) or []
@api.model
def _get_access_ids(self):
base, model = self._name.split(".")
@ -90,7 +160,7 @@ class BaseModelAccessGroups(models.AbstractModel):
SELECT r.aid
FROM muk_groups_complete_%s_rel r
JOIN muk_security_groups g ON r.gid = g.id
JOIN muk_groups_users_rel u ON r.gid = u.gid
JOIN muk_security_groups_users_rel u ON r.gid = u.gid
WHERE u.uid = %s AND g.perm_read = true
''' % (model, self.env.user.id)
self.env.cr.execute(sql)
@ -99,42 +169,67 @@ class BaseModelAccessGroups(models.AbstractModel):
return access_ids
@api.model
def _get_complete_access_ids(self):
return self._get_no_access_ids() + self._get_access_ids()
def _get_ids_without_security(self, operation):
no_access_ids = self._get_no_access_ids()
suspended_access_ids = self._get_suspended_access_ids(operation)
return list(set(no_access_ids).union(suspended_access_ids))
@api.model
def _get_complete_access_ids(self, operation):
access_ids = self._get_access_ids()
no_access_ids = self._get_no_access_ids()
suspended_access_ids = self._get_suspended_access_ids(operation)
return list(set(access_ids).union(no_access_ids, suspended_access_ids))
@api.multi
def _eval_access_skip(self):
def _eval_access_skip(self, operation):
if isinstance(self.env.uid, helper.NoSecurityUid):
return True
return False
@api.multi
def check_access_rule(self, operation):
super(BaseModelAccessGroups, self).check_access_rule(operation)
if self.env.user.id == SUPERUSER_ID or self._eval_access_skip():
def check_access_groups(self, operation):
if self.env.user.id == SUPERUSER_ID or self._eval_access_skip(operation):
return None
base, model = self._name.split(".")
no_access_ids = self._get_no_access_ids()
for record in self.filtered(lambda rec: rec.id not in no_access_ids):
filter_ids = self._get_ids_without_security(operation)
for record in self.filtered(lambda rec: rec.id not in filter_ids):
sql = '''
SELECT perm_%s
FROM muk_groups_complete_%s_rel r
JOIN muk_security_groups g ON g.id = r.gid
JOIN muk_groups_users_rel u ON u.gid = g.id
JOIN muk_security_groups_users_rel u ON u.gid = g.id
WHERE r.aid = %s AND u.uid = %s
''' % (operation, model, record.id, self.env.user.id)
self.env.cr.execute(sql)
fetch = self.env.cr.fetchall()
if not any(list(map(lambda x: x[0], fetch))):
raise AccessError(_("This operation is forbidden!"))
@api.multi
def check_access(self, operation, raise_exception=False):
res = super(BaseModelAccessGroups, self).check_access(operation, raise_exception)
try:
access_groups = self.check_access_groups(operation) == None
access = res and access_groups
if not access and raise_exception:
raise AccessError(_("This operation is forbidden!"))
return access
except AccessError:
if raise_exception:
raise AccessError(_("This operation is forbidden!"))
return False
#----------------------------------------------------------
# Read
#----------------------------------------------------------
@api.multi
def _after_read(self, result, *largs, **kwargs):
result = super(BaseModelAccessGroups, self)._after_read(result)
if self.env.user.id == SUPERUSER_ID or self._eval_access_skip():
if self.env.user.id == SUPERUSER_ID or self._eval_access_skip("read"):
return result
access_ids = self._get_complete_access_ids()
access_ids = self._get_complete_access_ids("read")
result = [result] if not isinstance(result, list) else result
if len(access_ids) > 0:
access_result = []
@ -144,11 +239,12 @@ class BaseModelAccessGroups(models.AbstractModel):
return access_result
return []
@api.model
def _after_search(self, result, *largs, **kwargs):
result = super(BaseModelAccessGroups, self)._after_search(result)
if self.env.user.id == SUPERUSER_ID or self._eval_access_skip():
if self.env.user.id == SUPERUSER_ID or self._eval_access_skip("read"):
return result
access_ids = self._get_complete_access_ids()
access_ids = self._get_complete_access_ids("read")
if len(access_ids) > 0:
access_result = self.env[self._name]
if isinstance(result, int):
@ -161,11 +257,12 @@ class BaseModelAccessGroups(models.AbstractModel):
return access_result
return self.env[self._name]
@api.model
def _after_name_search(self, result, *largs, **kwargs):
result = super(BaseModelAccessGroups, self)._after_name_search(result)
if self.env.user.id == SUPERUSER_ID or self._eval_access_skip():
if self.env.user.id == SUPERUSER_ID or self._eval_access_skip("read"):
return result
access_ids = self._get_complete_access_ids()
access_ids = self._get_complete_access_ids("read")
if len(access_ids) > 0:
access_result = []
for tuple in result:
@ -177,11 +274,35 @@ class BaseModelAccessGroups(models.AbstractModel):
#----------------------------------------------------------
# Read, View
#----------------------------------------------------------
@api.multi
def _compute_groups(self, write=True):
if write:
for record in self:
record.complete_groups = record.groups
record.complete_groups = record.get_groups()
else:
self.ensure_one()
return {'complete_groups': [(6, 0, self.groups.mapped('id'))]}
return {'complete_groups': [(6, 0, self.get_groups().mapped('id'))]}
#----------------------------------------------------------
# Create, Update, Delete
#----------------------------------------------------------
@api.multi
def _before_write(self, vals, *largs, **kwargs):
self.check_access_groups('write')
return super(BaseModelAccessGroups, self)._before_write(vals, *largs, **kwargs)
@api.multi
def _before_unlink(self, *largs, **kwargs):
self.check_access_groups('unlink')
return super(BaseModelAccessGroups, self)._before_unlink(*largs, **kwargs)
@api.multi
def _check_recomputation(self, vals, olds, *largs, **kwargs):
super(BaseModelAccessGroups, self)._check_recomputation(vals, olds, *largs, **kwargs)
fields = []
if self.check_group_values(vals):
fields.extend(['complete_groups'])
if fields:
self.trigger_computation(fields)

178
muk_security/models/groups.py

@ -1,178 +0,0 @@
###################################################################################
#
# Copyright (C) 2017 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
from odoo import models, fields, api
class AccessGroups(models.Model):
_name = 'muk_security.groups'
_description = "Access Groups"
_inherit = 'muk_utils.model'
_parent_store = True
_parent_name = "parent_group"
_parent_order = 'parent_left'
_order = 'parent_left'
#----------------------------------------------------------
# Database
#----------------------------------------------------------
name = fields.Char(
string="Group Name",
required=True)
parent_group = fields.Many2one(
comodel_name='muk_security.groups',
string='Parent Group',
ondelete='cascade',
auto_join=True,
index=True)
child_groups = fields.One2many(
comodel_name='muk_security.groups',
inverse_name='parent_group',
string='Child Groups')
parent_left = fields.Integer(
string='Left Parent',
index=True)
parent_right = fields.Integer(
string='Right Parent',
index=True)
perm_read = fields.Boolean(
string='Read Access')
perm_create = fields.Boolean(
string='Create Access')
perm_write = fields.Boolean(
string='Write Access')
perm_unlink = fields.Boolean(
string='Unlink Access')
groups = fields.Many2many(
comodel_name='res.groups',
relation='muk_groups_groups_rel',
column1='gid',
column2='rid',
string='Groups')
explicit_users = fields.Many2many(
comodel_name='res.users',
relation='muk_groups_explicit_users_rel',
column1='gid',
column2='uid',
string='Explicit Users')
users = fields.Many2many(
comodel_name='res.users',
relation='muk_groups_users_rel',
column1='gid',
column2='uid',
string='Users',
compute='_compute_users',
store=True)
count_users = fields.Integer(
compute='_compute_count_users',
string="Users")
_sql_constraints = [
('name_uniq', 'unique (name)', 'The name of the group must be unique!')
]
#----------------------------------------------------------
# Functions
#----------------------------------------------------------
def trigger_computation_up(self, fields):
parent_group = self.parent_group
if parent_group:
parent_group.trigger_computation(fields)
def trigger_computation_down(self, fields):
for child in self.child_groups:
child.with_context(is_subnode=True).trigger_computation(fields)
def trigger_computation(self, fields):
values = {}
if "users" in fields:
values.update(self._compute_users(write=False))
if values:
self.write(values);
if "users" in fields:
self.trigger_computation_down(fields)
#----------------------------------------------------------
# Read, View
#----------------------------------------------------------
@api.model
def check_user_values(self, values):
if any(field in values for field in ['parent_group', 'groups', 'explicit_users']):
return True
return False
@api.multi
def get_users(self):
self.ensure_one()
users = self.env['res.users']
if self.parent_group:
users |= self.parent_group.users
users |= self.groups.mapped('users')
users |= self.explicit_users
return users
def _compute_users(self, write=True):
if write:
for record in self:
record.users = record.get_users()
else:
self.ensure_one()
return {'users': [(6, 0, self.get_users().mapped('id'))]}
@api.depends('users')
def _compute_count_users(self):
for record in self:
record.count_users = len(record.users)
#----------------------------------------------------------
# Create, Write, Delete
#----------------------------------------------------------
def _after_create(self, vals):
record = super(AccessGroups, self)._after_create(vals)
record._check_recomputation(vals)
return record
def _after_write_record(self, vals):
vals = super(AccessGroups, self)._after_write_record(vals)
self._check_recomputation(vals)
return vals
def _check_recomputation(self, values):
fields = []
if self.check_user_values(values):
fields.extend(['users'])
if fields:
self.trigger_computation(fields)

39
muk_security/models/ir_model_access.py

@ -0,0 +1,39 @@
###################################################################################
#
# Copyright (C) 2017 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
import logging
from odoo import api, fields, models
from odoo import tools, _
from odoo.exceptions import ValidationError
from odoo.addons.muk_security.tools import helper
_logger = logging.getLogger(__name__)
class ExtendedIrModelAccess(models.Model):
_inherit = 'ir.model.access'
@api.model
@tools.ormcache_context('self._uid', 'model', 'mode', 'raise_exception', keys=('lang',))
def check(self, model, mode='read', raise_exception=True):
if isinstance(self.env.uid, helper.NoSecurityUid):
return True
return super(ExtendedIrModelAccess, self).check(model, mode=mode, raise_exception=raise_exception)

39
muk_security/models/ir_rule.py

@ -0,0 +1,39 @@
###################################################################################
#
# Copyright (C) 2017 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
import logging
from odoo import api, fields, models
from odoo import tools, _
from odoo.exceptions import ValidationError
from odoo.addons.muk_security.tools import helper
_logger = logging.getLogger(__name__)
class ExtendedIrRule(models.Model):
_inherit = 'ir.rule'
@api.model
@tools.ormcache('self._uid', 'model_name', 'mode')
def _compute_domain(self, model_name, mode="read"):
if isinstance(self.env.uid, helper.NoSecurityUid):
return None
return super(ExtendedIrRule, self)._compute_domain(model_name, mode=mode)

31
muk_security/models/lock.py

@ -29,22 +29,33 @@ class Lock(models.Model):
_description = "Lock"
name = fields.Char(
compute='_compute_name',
string="Name")
compute='_compute_lock_ref_data',
string="Name",
store=True)
locked_by = fields.Char(
string="Locked by",
required=True)
locked_by_ref = fields.Reference(
selection=[('res.users', 'User')],
string="User Reference")
locked_by_ref = fields.Many2one(
comodel_name='res.users',
string="Locked by")
lock_ref = fields.Reference(
selection=[],
string="Object Reference",
string="Reference",
required=True)
lock_ref_model = fields.Char(
compute='_compute_lock_ref_data',
string="Reference Model",
store=True)
lock_ref_id = fields.Char(
compute='_compute_lock_ref_data',
string="Reference ID",
store=True)
token = fields.Char(
string="Token")
@ -52,6 +63,10 @@ class Lock(models.Model):
string="Operation")
@api.depends('lock_ref')
def _compute_name(self):
def _compute_lock_ref_data(self):
for record in self:
record.name = "Lock for " + str(record.lock_ref.name)
record.update({
'name': "Lock for " + str(record.lock_ref.display_name),
'lock_ref_model': record.lock_ref._name,
'lock_ref_id': record.lock_ref.id})

82
muk_security/models/locking.py

@ -20,6 +20,7 @@
import os
import hashlib
import logging
import itertools
from odoo import _
from odoo import models, api, fields
@ -40,12 +41,29 @@ class BaseModelLocking(models.AbstractModel):
locked = fields.Many2one(
comodel_name='muk_security.lock',
compute='_compute_lock',
string="Locked",)
string="Locked")
locked_state = fields.Boolean(
compute='_compute_lock',
string="Locked")
locked_by = fields.Many2one(
related='locked.locked_by_ref',
comodel_name='res.users',
string="Locked by")
editor = fields.Boolean(
compute='_compute_editor',
string="Editor")
#----------------------------------------------------------
# Functions
#----------------------------------------------------------
@api.model
def generate_operation_key(self):
return hashlib.sha1(os.urandom(128)).hexdigest()
#----------------------------------------------------------
# Locking
#----------------------------------------------------------
@ -67,7 +85,7 @@ class BaseModelLocking(models.AbstractModel):
token = hashlib.sha1(os.urandom(128)).hexdigest()
lock = self.env['muk_security.lock'].sudo().create({
'locked_by': user and user.name or "System",
'locked_by_ref': user and user._name + ',' + str(user.id) or None,
'locked_by_ref': user and user.id or None,
'lock_ref': record._name + ',' + str(record.id),
'token': token,
'operation': operation})
@ -88,8 +106,13 @@ class BaseModelLocking(models.AbstractModel):
@api.model
def unlock_operation(self, operation, *largs, **kwargs):
locks = self.env['muk_security.lock'].sudo().search([('operation', '=', operation)])
references = [
list((k, list(map(lambda rec: rec.lock_ref_id, v))))
for k, v in itertools.groupby(
locks.sorted(key=lambda rec: rec.lock_ref_model),
lambda rec: rec.lock_ref_model)]
locks.sudo().unlink()
return True
return references
@api.multi
def is_locked(self, *largs, **kwargs):
@ -113,7 +136,7 @@ class BaseModelLocking(models.AbstractModel):
def _checking_lock_user(self, *largs, **kwargs):
for record in self:
lock = record.is_locked()
if lock and lock.locked_by_ref and not lock.locked_by_ref != self.env.user:
if lock and lock.locked_by_ref and not lock.locked_by_ref.id != self.env.user.id:
raise AccessError(_("The record (%s[%s]) is locked by a user, so it can't be changes or deleted.") %
(self._name, self.id))
@ -123,6 +146,8 @@ class BaseModelLocking(models.AbstractModel):
for record in self:
lock = record.is_locked()
if lock and lock.operation and lock.operation != operation:
print(operation, lock.operation)
raise IOError
raise AccessError(_("The record (%s[%s]) is locked, so it can't be changes or deleted.") %
(self._name, self.id))
@ -149,14 +174,19 @@ class BaseModelLocking(models.AbstractModel):
raise AccessError(_("The record is already locked by another user. (%s)") % lock.locked_by_ref.name)
else:
raise AccessError(_("The record is already locked."))
return True
#----------------------------------------------------------
# Read, View
#----------------------------------------------------------
@api.multi
def _compute_lock(self):
for record in self:
record.locked = record.is_locked()
locked = record.is_locked()
record.update({
'locked_state': bool(locked),
'locked': locked})
@api.depends('locked')
def _compute_editor(self):
@ -169,51 +199,53 @@ class BaseModelLocking(models.AbstractModel):
@api.multi
def write(self, vals):
operation = hashlib.sha1(os.urandom(128)).hexdigest()
operation = self.generate_operation_key()
vals = self._before_write_operation(vals, operation)
result = super(BaseModelLocking, self).write(vals)
process_operation = self.env.context['operation'] if 'operation' in self.env.context else operation
result = super(BaseModelLocking, self.with_context(operation=process_operation)).write(vals)
for record in self:
record._after_write_record_operation(vals, operation)
result = self._after_write_operation(result, vals, operation)
return result
@api.multi
def _before_write_operation(self, vals, operation, *largs, **kwargs):
if 'operation' in self.env.context:
self._checking_lock(self.env.context['operation'])
elif operation:
self._checking_lock(operation)
else:
self._checking_lock_user()
self._checking_lock(operation)
return vals
@api.multi
def _after_write_record_operation(self, vals, operation, *largs, **kwargs):
return vals
@api.multi
def _after_write_operation(self, result, vals, operation, *largs, **kwargs):
return result
@api.multi
def unlink(self):
operation = hashlib.sha1(os.urandom(128)).hexdigest()
operation_info = self._before_unlink_operation(operation)
operation_infos = []
operation = self.generate_operation_key()
self._before_unlink_operation(operation)
for record in self:
operation_infos.append(record._before_unlink_record_operation(operation))
result = super(BaseModelLocking, self).unlink()
self._after_unlink_operation(result, operation_info, operation_infos, operation)
record._before_unlink_record_operation(operation)
process_operation = self.env.context['operation'] if 'operation' in self.env.context else operation
result = super(BaseModelLocking, self.with_context(operation=process_operation)).unlink()
self._after_unlink_operation(result, operation)
return result
@api.multi
def _before_unlink_operation(self, operation, *largs, **kwargs):
if 'operation' in self.env.context:
self._checking_lock(self.env.context['operation'])
elif operation:
self._checking_lock(operation)
else:
self._checking_lock_user()
return {}
self._checking_lock(operation)
@api.multi
def _before_unlink_record_operation(self, operation, *largs, **kwargs):
return {}
def _after_unlink_operation(self, result, operation_info, operation_infos, operation, *largs, **kwargs):
pass
@api.multi
def _after_unlink_operation(self, result, operation, *largs, **kwargs):
pass

31
muk_security/models/res_groups.py

@ -29,36 +29,13 @@ class AccessGroups(models.Model):
_inherit = "res.groups"
#----------------------------------------------------------
#----------------------------------------------------------
# Database
#----------------------------------------------------------
groups = fields.Many2many(
security_groups = fields.Many2many(
comodel_name='muk_security.groups',
relation='muk_groups_groups_rel',
relation='muk_security_groups_groups_rel',
column1='rid',
column2='gid',
string='Groups')
#----------------------------------------------------------
# Create, Update, Delete
#----------------------------------------------------------
@api.multi
def write(self, vals):
result = super(AccessGroups, self).write(vals)
if any(field in vals for field in ['users']):
for record in self:
for group in record.groups:
group.trigger_computation(['users'])
return result
@api.multi
def unlink(self):
groups = self.env['muk_security.groups']
for record in self:
groups |= record.groups
result = super(AccessGroups, self).unlink()
for group in groups:
group.trigger_computation(['users'])
return result
string='Groups')

57
muk_security/models/res_users.py

@ -0,0 +1,57 @@
###################################################################################
#
# Copyright (C) 2017 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
import logging
from odoo import api, fields, models
from odoo import tools, _
from odoo.exceptions import ValidationError
from odoo.addons.base.res import res_users
from odoo.addons.muk_security.tools import helper
_logger = logging.getLogger(__name__)
class AccessUser(models.Model):
_inherit = 'res.users'
#----------------------------------------------------------
# Database
#----------------------------------------------------------
security_groups = fields.Many2many(
comodel_name='muk_security.groups',
relation='muk_security_groups_explicit_users_rel',
column1='uid',
column2='gid',
string='Groups',
readonly=True)
#----------------------------------------------------------
# Functions
#----------------------------------------------------------
@classmethod
def _browse(cls, ids, env, prefetch=None):
return super(AccessUser, cls)._browse([
id if not isinstance(id, helper.NoSecurityUid)
else super(helper.NoSecurityUid, id).__int__()
for id in ids], env, prefetch=prefetch)

43
muk_security/models/security_groups.py

@ -0,0 +1,43 @@
###################################################################################
#
# Copyright (C) 2017 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
from odoo import models, fields, api
class AccessGroups(models.Model):
_name = 'muk_security.groups'
_description = "Access Groups"
_inherit = 'muk_utils.groups'
#----------------------------------------------------------
# Database
#----------------------------------------------------------
perm_read = fields.Boolean(
string='Read Access')
perm_create = fields.Boolean(
string='Create Access')
perm_write = fields.Boolean(
string='Write Access')
perm_unlink = fields.Boolean(
string='Unlink Access')

5
muk_security/security/ir.model.access.csv

@ -1,4 +1,5 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_security_lock_admin,security_lock_admin,model_muk_security_lock,base.group_erp_manager,1,0,0,1
access_security_groups_admin,security_groups_admin,model_muk_security_groups,base.group_erp_manager,1,1,1,1
access_security_groups_user,security_groups_user,model_muk_security_groups,base.group_user,1,1,1,1
access_security_lock_admin,security_lock_admin,model_muk_security_lock,base.group_erp_manager,1,0,0,1

45
muk_security/security/security.xml

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2017 MuK IT GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<odoo>
<record id="rule_security_groups_user" model="ir.rule">
<field name="name">User can only edit and delete their own groups.</field>
<field name="model_id" ref="model_muk_security_groups"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_write" eval="1"/>
<field name="perm_unlink" eval="1" />
<field name="domain_force">[('create_uid','=',user.id)]</field>
</record>
<record id="rule_security_groups_manager" model="ir.rule">
<field name="name">Admins can edit and delete all groups.</field>
<field name="model_id" ref="model_muk_security_groups"/>
<field name="groups" eval="[(4, ref('base.group_erp_manager'))]"/>
<field name="perm_read" eval="0"/>
<field name="perm_create" eval="0"/>
<field name="perm_write" eval="1"/>
<field name="perm_unlink" eval="1" />
<field name="domain_force">[(1 ,'=', 1)]</field>
</record>
</odoo>

7
muk_security/static/description/index.html

@ -11,10 +11,9 @@
<div class="oe_row oe_spaced">
<div style="max-width: 84%; margin: 16px 8%;">
<h3 class="oe_slogan">Overview</h3>
<p class="oe_mt32">Technical module to provide some utility
features. The module is mainly used as a dependency by other modules
and to provide a collection of common libraries. It has no direct
visible effect on the system.</p>
<p class="oe_mt32">Technical module to provide some utility and
security features. The module is mainly used as a dependency by
other modules and has no direct visible effect on the system.</p>
</div>
</div>
</section>

22
muk_security/tests/__init__.py

@ -0,0 +1,22 @@
###################################################################################
#
# MuK Document Management System
#
# Copyright (C) 2018 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
from . import test_suspend_security

50
muk_security/tests/test_suspend_security.py

@ -0,0 +1,50 @@
###################################################################################
#
# Copyright (C) 2017 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
import os
import base64
import logging
from odoo import exceptions
from odoo.tests import common
_path = os.path.dirname(os.path.dirname(__file__))
_logger = logging.getLogger(__name__)
class SuspendSecurityTestCase(common.TransactionCase):
at_install = False
post_install = True
def setUp(self):
super(SuspendSecurityTestCase, self).setUp()
def tearDown(self):
super(SuspendSecurityTestCase, self).tearDown()
def test_suspend_security(self):
user_id = self.env.ref('base.user_demo').id
with self.assertRaises(exceptions.AccessError):
self.env.ref('base.user_root').sudo(user_id).name = 'test'
self.env.ref('base.user_root').sudo(user_id).suspend_security().name = 'test'
self.assertEqual(self.env.ref('base.user_root').name, 'test')
self.assertEqual(self.env.ref('base.user_root').write_uid.id, user_id)
def test_normalize(self):
self.env['res.users'].browse(self.env['res.users'].suspend_security().env.uid)

22
muk_security/tools/__init__.py

@ -0,0 +1,22 @@
###################################################################################
#
# MuK Document Management System
#
# Copyright (C) 2018 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
from . import helper

36
muk_security/tools/helper.py

@ -0,0 +1,36 @@
###################################################################################
#
# MuK Document Management System
#
# Copyright (C) 2018 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
class NoSecurityUid(int):
def __int__(self):
return self
def __eq__(self, other):
if isinstance(other, int):
return False
return super(NoSecurityUid, self).__int__() == other
def __iter__(self):
yield super(NoSecurityUid, self).__int__()
def __hash__(self):
return super(NoSecurityUid, self).__hash__()

6
muk_security/views/groups.xml

@ -65,7 +65,7 @@
</group>
</group>
<notebook>
<page string="Users">
<page name="users" string="Users">
<field name="users">
<tree string="Users">
<field name="name" />
@ -75,7 +75,7 @@
</tree>
</field>
</page>
<page string="Groups">
<page name="groups" string="Groups">
<field name="groups">
<tree string="Groups">
<field name="name" />
@ -83,7 +83,7 @@
</tree>
</field>
</page>
<page string="Explicit Users">
<page name="extra_users" string="Explicit Users">
<field name="explicit_users">
<tree string="Explicit Users">
<field name="name" />

5
muk_utils/__manifest__.py

@ -20,7 +20,7 @@
{
"name": "MuK Utils",
"summary": """Utility Features""",
"version": '11.0.1.0.6',
"version": '11.0.1.0.8',
"category": 'Extra Tools',
"license": "AGPL-3",
"website": "https://www.mukit.at",
@ -32,6 +32,9 @@
"depends": [
"base",
],
"data": [
"data/ir_cron.xml",
],
"qweb": [
"static/src/xml/*.xml",
],

41
muk_utils/data/ir_cron.xml

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2017 MuK IT GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<odoo noupdate="1">
<record id="cron_utils_update_groups" model="ir.cron">
<field name="name">Cron job to update the Groups</field>
<field name="active" eval="True" />
<field name="user_id" ref="base.user_root" />
<field name="model_id" ref="muk_utils.model_muk_utils_groups" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="priority" eval="5" />
<field name="state">code</field>
<field name="code">
model_names = model.pool.descendants(['muk_utils.groups'], '_inherit', '_inherits')
for model_name in model_names:
group = model.env[model_name].sudo()
if not group._abstract:
group.update_groups()
</field>
</record>
</odoo>

3
muk_utils/models/__init__.py

@ -18,3 +18,6 @@
###################################################################################
from . import model
from . import groups
from . import res_groups
from . import res_users

180
muk_utils/models/groups.py

@ -0,0 +1,180 @@
###################################################################################
#
# Copyright (C) 2017 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
from odoo import models, fields, api
class Groups(models.AbstractModel):
_name = 'muk_utils.groups'
_inherit = 'muk_utils.model'
_parent_store = True
_parent_name = "parent_group"
_parent_order = 'parent_left'
_order = 'parent_left'
#----------------------------------------------------------
# Database
#----------------------------------------------------------
name = fields.Char(
string="Group Name",
required=True,
translate=True)
parent_left = fields.Integer(
string='Left Parent',
index=True)
parent_right = fields.Integer(
string='Right Parent',
index=True)
count_users = fields.Integer(
compute='_compute_count_users',
string="Users")
@api.model
def _add_magic_fields(self):
super(Groups, self)._add_magic_fields()
def add(name, field):
if name not in self._fields:
self._add_field(name, field)
base, model = self._name.split(".")
add('parent_group', fields.Many2one(
_module=base,
comodel_name=self._name,
string='Parent Group',
ondelete='cascade',
auto_join=True,
index=True,
automatic=True))
add('child_groups', fields.One2many(
_module=base,
comodel_name=self._name,
inverse_name='parent_group',
string='Child Groups',
automatic=True))
add('groups', fields.Many2many(
_module=base,
comodel_name='res.groups',
relation='%s_groups_rel' % (self._table),
column1='gid',
column2='rid',
string='Groups',
automatic=True))
add('explicit_users', fields.Many2many(
_module=base,
comodel_name='res.users',
relation='%s_explicit_users_rel' % (self._table),
column1='gid',
column2='uid',
string='Explicit Users',
automatic=True))
add('users', fields.Many2many(
_module=base,
comodel_name='res.users',
relation='%s_users_rel' % (self._table),
column1='gid',
column2='uid',
string='Users',
compute='_compute_users',
store=True,
automatic=True))
_sql_constraints = [
('name_uniq', 'unique (name)', 'The name of the group must be unique!')
]
#----------------------------------------------------------
# Functions
#----------------------------------------------------------
@api.multi
def trigger_computation_up(self, fields, *largs, **kwargs):
parent_groups = self.mapped('parent_group')
if parent_groups.exists():
parent_groups.with_context(is_parent=True).trigger_computation(fields)
@api.multi
def trigger_computation_down(self, fields, *largs, **kwargs):
child_groups = self.mapped('child_groups')
if child_groups.exists():
child_groups.with_context(is_child=True).trigger_computation(fields)
@api.multi
def trigger_computation(self, fields, *largs, **kwargs):
super(Groups, self).trigger_computation(fields, *largs, **kwargs)
if "users" in fields:
self.suspend_security()._compute_users()
self.suspend_security().trigger_computation_down(fields)
@api.model
def check_user_values(self, values):
if any(field in values for field in [
'parent_group', 'groups', 'explicit_users']):
return True
return False
@api.multi
@api.returns('res.users')
def get_users(self):
self.ensure_one()
users = self.env['res.users']
if self.parent_group:
users |= self.parent_group.users
users |= self.groups.mapped('users')
users |= self.explicit_users
return users
#----------------------------------------------------------
# Read, View
#----------------------------------------------------------
@api.multi
def _compute_users(self):
for record in self:
record.users = record.get_users()
@api.depends('users')
def _compute_count_users(self):
for record in self:
record.count_users = len(record.users)
#----------------------------------------------------------
# Create, Write, Delete
#----------------------------------------------------------
@api.multi
def _check_recomputation(self, vals, olds, *largs, **kwargs):
super(Groups, self)._check_recomputation(vals, olds, *largs, **kwargs)
fields = []
if self.check_user_values(vals):
fields.extend(['users'])
if fields:
self.trigger_computation(fields)
#----------------------------------------------------------
# Cron Job Functions
#----------------------------------------------------------
@api.model
def update_groups(self, *args, **kwargs):
self.search([]).trigger_computation(['users'])

61
muk_utils/models/model.py

@ -32,6 +32,14 @@ class BaseModelExtension(models.AbstractModel):
# Function
#----------------------------------------------------------
@api.multi
def notify_change(self, values, *largs, **kwargs):
pass
@api.multi
def trigger_computation(self, fields, *largs, **kwargs):
pass
@api.multi
def check_existence(self):
records = self.exists()
@ -44,15 +52,18 @@ class BaseModelExtension(models.AbstractModel):
# Read
#----------------------------------------------------------
@api.model
def browse(self, arg=None, prefetch=None):
arg = self._before_browse(arg)
result = super(BaseModelExtension, self).browse(arg, prefetch)
result = self._after_browse(result)
return result
@api.model
def _before_browse(self, arg, *largs, **kwargs):
return arg
@api.model
def _after_browse(self, result, *largs, **kwargs):
return result
@ -61,16 +72,22 @@ class BaseModelExtension(models.AbstractModel):
fields = self._before_read(fields)
result = super(BaseModelExtension, self).read(fields, load)
for index, record in enumerate(self.exists()):
result[index] = record._after_read_record(result[index])
try:
result[index] = record._after_read_record(result[index])
except IndexError:
_logger.exception("Something went wrong!")
result = self._after_read(result)
return result
@api.multi
def _before_read(self, fields, *largs, **kwargs):
return fields
@api.multi
def _after_read_record(self, values, *largs, **kwargs):
return values
@api.multi
def _after_read(self, result, *largs, **kwargs):
return result
@ -84,9 +101,11 @@ class BaseModelExtension(models.AbstractModel):
result = self._after_search(result)
return result
@api.model
def _before_search(self, args, offset, limit, order, count, *largs, **kwargs):
return args, offset, limit, order, count
@api.model
def _after_search(self, result, *largs, **kwargs):
return result
@ -97,9 +116,11 @@ class BaseModelExtension(models.AbstractModel):
result = self._after_name_search(result)
return result
@api.model
def _before_name_search(self, name, args, operator, limit, *largs, **kwargs):
return name, args, operator, limit
@api.model
def _after_name_search(self, result, *largs, **kwargs):
return result
@ -110,9 +131,11 @@ class BaseModelExtension(models.AbstractModel):
result = self._after_read_group(result)
return result
@api.model
def _before_read_group(self, domain, fields, groupby, offset, limit, orderby, lazy, *largs, **kwargs):
return domain, fields, groupby, offset, limit, orderby, lazy
@api.model
def _after_read_group(self, result, *largs, **kwargs):
return result
@ -127,28 +150,39 @@ class BaseModelExtension(models.AbstractModel):
result = result._after_create(vals)
return result
@api.model
def _before_create(self, vals, *largs, **kwargs):
return vals
@api.model
def _after_create(self, vals, *largs, **kwargs):
self._check_recomputation(vals, [])
return self
@api.multi
def write(self, vals):
olds = []
vals = self._before_write(vals)
if 'track_old_values' in self.env.context:
olds = [{key: record[key] for key in vals} for record in self]
result = super(BaseModelExtension, self).write(vals)
for record in self:
record._after_write_record(vals)
result = self._after_write(result, vals)
result = self._after_write(result, vals, olds)
return result
@api.multi
def _before_write(self, vals, *largs, **kwargs):
return vals
@api.multi
def _after_write_record(self, vals, *largs, **kwargs):
return vals
def _after_write(self, result, vals, *largs, **kwargs):
@api.multi
def _after_write(self, result, vals, olds, *largs, **kwargs):
self._check_recomputation(vals, olds)
self._check_notification(vals)
return result
@api.multi
@ -161,11 +195,28 @@ class BaseModelExtension(models.AbstractModel):
self._after_unlink(result, info, infos)
return result
@api.multi
def _before_unlink(self, *largs, **kwargs):
return {}
@api.multi
def _before_unlink_record(self, *largs, **kwargs):
return {}
@api.multi
def _after_unlink(self, result, info, infos, *largs, **kwargs):
pass
#----------------------------------------------------------
# Helper
#----------------------------------------------------------
@api.multi
def _check_recomputation(self, vals, olds, *largs, **kwargs):
# self.trigger_computation(fields)
pass
@api.multi
def _check_notification(self, vals, *largs, **kwargs):
# self.notify_change(change)
pass

68
muk_utils/models/res_groups.py

@ -0,0 +1,68 @@
###################################################################################
#
# Copyright (C) 2017 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
import logging
from collections import defaultdict
from odoo import api, fields, models
from odoo import tools, _
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class ResGroups(models.Model):
_inherit = "res.groups"
#----------------------------------------------------------
# Create, Update, Delete
#----------------------------------------------------------
@api.multi
def write(self, vals):
model_recs = defaultdict(set)
model_names = self.pool.descendants(['muk_utils.groups'], '_inherit', '_inherits')
if any(field in vals for field in ['users']):
for model_name in model_names:
model = self.env[model_name].sudo()
if not model._abstract:
model_recs[model_name] = model.search([['groups', 'in', self.mapped('id')]])
result = super(ResGroups, self).write(vals)
if any(field in vals for field in ['users']):
for model_name in model_names:
model = self.env[model_name].sudo()
if not model._abstract:
model_recs[model_name] = model_recs[model_name] | model.search([['groups', 'in', self.mapped('id')]])
for tuple in model_recs.items():
tuple[1].trigger_computation(['users'])
return result
@api.multi
def unlink(self):
model_recs = defaultdict(set)
model_names = self.pool.descendants(['muk_utils.groups'], '_inherit', '_inherits')
for model_name in model_names:
model = self.env[model_name].sudo()
if not model._abstract:
model_recs[model_name] = model.search([['groups', 'in', self.mapped('id')]])
result = super(ResGroups, self).unlink(vals)
for tuple in model_recs.items():
tuple[1].trigger_computation(['users'])
return result

85
muk_utils/models/res_users.py

@ -0,0 +1,85 @@
###################################################################################
#
# Copyright (C) 2017 MuK IT GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###################################################################################
import logging
from collections import defaultdict
from odoo import api, fields, models
from odoo import tools, _
from odoo.exceptions import ValidationError
from odoo.addons.base.res import res_users
from odoo.addons.muk_security.tools import helper
_logger = logging.getLogger(__name__)
class ResUser(models.Model):
_inherit = 'res.users'
#----------------------------------------------------------
# Create, Update, Delete
#----------------------------------------------------------
@api.model
def create(self, values):
result = super(ResUser, self).create(values)
model_recs = defaultdict(set)
model_names = self.pool.descendants(['muk_utils.groups'], '_inherit', '_inherits')
for model_name in model_names:
model = self.env[model_name].sudo()
if not model._abstract:
model_recs[model_name] = model.search([['groups', 'in', self.mapped('groups_id.id')]])
for tuple in model_recs.items():
tuple[1].trigger_computation(['users'])
return result
@api.multi
def write(self, vals):
group_ids = self.mapped('groups_id.id')
result = super(ResUser, self).write(vals)
group_ids += [vals[k] for k in vals if res_users.is_selection_groups(k) and vals[k]]
group_ids += [vals[k] for k in vals if res_users.is_boolean_group(k) and vals[k]]
if any(field in vals for field in ['groups_id']):
group_ids += self.mapped('groups_id.id')
if group_ids:
model_recs = defaultdict(set)
model_names = self.pool.descendants(['muk_utils.groups'], '_inherit', '_inherits')
for model_name in model_names:
model = self.env[model_name].sudo()
if not model._abstract:
model_recs[model_name] = model.search([['groups', 'in', group_ids]])
for tuple in model_recs.items():
tuple[1].trigger_computation(['users'])
return result
@api.multi
def unlink(self):
model_recs = defaultdict(set)
model_names = self.pool.descendants(['muk_utils.groups'], '_inherit', '_inherits')
for model_name in model_names:
model = self.env[model_name].sudo()
if not model._abstract:
model_recs[model_name] = model.search([['groups', 'in', self.mapped('groups_id.id')]])
result = super(ResUser, self).unlink()
for tuple in model_recs.items():
tuple[1].trigger_computation(['users'])
return result
Loading…
Cancel
Save