mirror of https://github.com/muk-it/muk_base
MuK IT GmbH
6 years ago
9 changed files with 513 additions and 482 deletions
-
2muk_security/__init__.py
-
4muk_security/__manifest__.py
-
272muk_security/models/access.py
-
78muk_security/models/ir_rule.py
-
502muk_security/models/locking.py
-
2muk_security/models/res_groups.py
-
2muk_security/models/res_users.py
-
2muk_security/tests/__init__.py
-
131muk_security/tests/test_suspend_security.py
@ -1,136 +1,136 @@ |
|||||
################################################################################### |
|
||||
# |
|
||||
# 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 _ |
|
||||
from odoo import models, api, fields |
|
||||
from odoo.exceptions import AccessError |
|
||||
|
|
||||
_logger = logging.getLogger(__name__) |
|
||||
|
|
||||
class BaseModelAccess(models.AbstractModel): |
|
||||
|
|
||||
_name = 'muk_security.access' |
|
||||
_description = "MuK Access Model" |
|
||||
_inherit = 'muk_utils.model' |
|
||||
|
|
||||
#---------------------------------------------------------- |
|
||||
# Database |
|
||||
#---------------------------------------------------------- |
|
||||
|
|
||||
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") |
|
||||
|
|
||||
#---------------------------------------------------------- |
|
||||
# Function |
|
||||
#---------------------------------------------------------- |
|
||||
|
|
||||
@api.model |
|
||||
def check_access_rights(self, operation, raise_exception=True): |
|
||||
return super(BaseModelAccess, self).check_access_rights(operation, raise_exception) |
|
||||
|
|
||||
@api.multi |
|
||||
def check_access_rule(self, operation): |
|
||||
return super(BaseModelAccess, self).check_access_rule(operation) |
|
||||
|
|
||||
@api.model |
|
||||
def _apply_ir_rules(self, query, mode='read'): |
|
||||
return super(BaseModelAccess, self)._apply_ir_rules(query, mode) |
|
||||
|
|
||||
@api.model |
|
||||
def check_field_access_rights(self, operation, fields): |
|
||||
return super(BaseModelAccess, self).check_field_access_rights(operation, fields) |
|
||||
|
|
||||
@api.multi |
|
||||
def check_access(self, operation, raise_exception=False): |
|
||||
try: |
|
||||
access_right = self.check_access_rights(operation, raise_exception) |
|
||||
access_rule = self.check_access_rule(operation) == None |
|
||||
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 |
|
||||
#---------------------------------------------------------- |
|
||||
|
|
||||
@api.multi |
|
||||
def _compute_permissions(self): |
|
||||
for record in self: |
|
||||
record.update({ |
|
||||
'permission_read': record.check_access('read'), |
|
||||
'permission_create': record.check_access('create'), |
|
||||
'permission_write': record.check_access('write'), |
|
||||
'permission_unlink': record.check_access('unlink'), |
|
||||
}) |
|
||||
|
################################################################################### |
||||
|
# |
||||
|
# 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 _ |
||||
|
from odoo import models, api, fields |
||||
|
from odoo.exceptions import AccessError |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
class BaseModelAccess(models.AbstractModel): |
||||
|
|
||||
|
_name = 'muk_security.access' |
||||
|
_description = "MuK Access Model" |
||||
|
_inherit = 'muk_utils.model' |
||||
|
|
||||
|
#---------------------------------------------------------- |
||||
|
# Database |
||||
|
#---------------------------------------------------------- |
||||
|
|
||||
|
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") |
||||
|
|
||||
|
#---------------------------------------------------------- |
||||
|
# Function |
||||
|
#---------------------------------------------------------- |
||||
|
|
||||
|
@api.model |
||||
|
def check_access_rights(self, operation, raise_exception=True): |
||||
|
return super(BaseModelAccess, self).check_access_rights(operation, raise_exception) |
||||
|
|
||||
|
@api.multi |
||||
|
def check_access_rule(self, operation): |
||||
|
return super(BaseModelAccess, self).check_access_rule(operation) |
||||
|
|
||||
|
@api.model |
||||
|
def _apply_ir_rules(self, query, mode='read'): |
||||
|
return super(BaseModelAccess, self)._apply_ir_rules(query, mode) |
||||
|
|
||||
|
@api.model |
||||
|
def check_field_access_rights(self, operation, fields): |
||||
|
return super(BaseModelAccess, self).check_field_access_rights(operation, fields) |
||||
|
|
||||
|
@api.multi |
||||
|
def check_access(self, operation, raise_exception=False): |
||||
|
try: |
||||
|
access_right = self.check_access_rights(operation, raise_exception) |
||||
|
access_rule = self.check_access_rule(operation) == None |
||||
|
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 |
||||
|
#---------------------------------------------------------- |
||||
|
|
||||
|
@api.multi |
||||
|
def _compute_permissions(self): |
||||
|
for record in self: |
||||
|
record.update({ |
||||
|
'permission_read': record.check_access('read'), |
||||
|
'permission_create': record.check_access('create'), |
||||
|
'permission_write': record.check_access('write'), |
||||
|
'permission_unlink': record.check_access('unlink'), |
||||
|
}) |
@ -1,39 +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) |
|
||||
|
################################################################################### |
||||
|
# |
||||
|
# 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) |
@ -1,251 +1,251 @@ |
|||||
################################################################################### |
|
||||
# |
|
||||
# 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 hashlib |
|
||||
import logging |
|
||||
import itertools |
|
||||
|
|
||||
from odoo import _ |
|
||||
from odoo import models, api, fields |
|
||||
from odoo.exceptions import AccessError |
|
||||
|
|
||||
_logger = logging.getLogger(__name__) |
|
||||
|
|
||||
class BaseModelLocking(models.AbstractModel): |
|
||||
|
|
||||
_name = 'muk_security.locking' |
|
||||
_description = 'MuK Locking Model' |
|
||||
_inherit = 'muk_utils.model' |
|
||||
|
|
||||
#---------------------------------------------------------- |
|
||||
# Database |
|
||||
#---------------------------------------------------------- |
|
||||
|
|
||||
locked = fields.Many2one( |
|
||||
comodel_name='muk_security.lock', |
|
||||
compute='_compute_lock', |
|
||||
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 |
|
||||
#---------------------------------------------------------- |
|
||||
|
|
||||
@api.multi |
|
||||
def lock(self, user=None, operation=None, *largs, **kwargs): |
|
||||
result = [] |
|
||||
for record in self: |
|
||||
lock = record.is_locked() |
|
||||
if lock and lock.operation and lock.operation == operation: |
|
||||
result.append({ |
|
||||
'record': record, |
|
||||
'lock': lock, |
|
||||
'token': lock.token}) |
|
||||
elif lock and ((lock.operation and lock.operation != operation) or not lock.operation): |
|
||||
raise AccessError(_("The record (%s[%s]) is locked, so it can't be locked again.") % |
|
||||
(record._name, record.id)) |
|
||||
else: |
|
||||
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.id or None, |
|
||||
'lock_ref': record._name + ',' + str(record.id), |
|
||||
'token': token, |
|
||||
'operation': operation}) |
|
||||
result.append({ |
|
||||
'record': record, |
|
||||
'lock': lock, |
|
||||
'token': token}) |
|
||||
return result |
|
||||
|
|
||||
@api.multi |
|
||||
def unlock(self, *largs, **kwargs): |
|
||||
for record in self: |
|
||||
locks = self.env['muk_security.lock'].sudo().search( |
|
||||
[('lock_ref', '=', "%s,%s" % (record._name, str(record.id)))]) |
|
||||
locks.sudo().unlink() |
|
||||
return True |
|
||||
|
|
||||
@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 references |
|
||||
|
|
||||
@api.multi |
|
||||
def is_locked(self, *largs, **kwargs): |
|
||||
self.ensure_one() |
|
||||
lock = self.env['muk_security.lock'].sudo().search( |
|
||||
[('lock_ref', '=', self._name + ',' + str(self.id))], limit=1) |
|
||||
if lock.id: |
|
||||
return lock |
|
||||
return False |
|
||||
|
|
||||
@api.multi |
|
||||
def is_locked_by(self, *largs, **kwargs): |
|
||||
self.ensure_one() |
|
||||
lock = self.env['muk_security.lock'].sudo().search( |
|
||||
[('lock_ref', '=', self._name + ',' + str(self.id))], limit=1) |
|
||||
if lock.id: |
|
||||
return lock.locked_by_ref |
|
||||
return False |
|
||||
|
|
||||
@api.multi |
|
||||
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.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)) |
|
||||
|
|
||||
@api.multi |
|
||||
def _checking_lock(self, operation=None, *largs, **kwargs): |
|
||||
self._checking_lock_user() |
|
||||
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)) |
|
||||
|
|
||||
@api.multi |
|
||||
def user_lock(self, *largs, **kwargs): |
|
||||
self.ensure_one() |
|
||||
lock = self.is_locked() |
|
||||
if lock: |
|
||||
if lock.locked_by_ref: |
|
||||
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 self.lock(user=self.env.user) |
|
||||
|
|
||||
@api.multi |
|
||||
def user_unlock(self, *largs, **kwargs): |
|
||||
self.ensure_one() |
|
||||
lock = self.is_locked() |
|
||||
if lock: |
|
||||
if lock.locked_by_ref and lock.locked_by_ref.id == self.env.user.id: |
|
||||
self.unlock() |
|
||||
else: |
|
||||
if lock.locked_by_ref: |
|
||||
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: |
|
||||
locked = record.is_locked() |
|
||||
record.update({ |
|
||||
'locked_state': bool(locked), |
|
||||
'locked': locked}) |
|
||||
|
|
||||
@api.depends('locked') |
|
||||
def _compute_editor(self): |
|
||||
for record in self: |
|
||||
record.editor = record.is_locked_by() == record.env.user |
|
||||
|
|
||||
#---------------------------------------------------------- |
|
||||
# Create, Update, Delete |
|
||||
#---------------------------------------------------------- |
|
||||
|
|
||||
@api.multi |
|
||||
def write(self, vals): |
|
||||
operation = self.generate_operation_key() |
|
||||
vals = self._before_write_operation(vals, operation) |
|
||||
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']) |
|
||||
else: |
|
||||
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 = self.generate_operation_key() |
|
||||
self._before_unlink_operation(operation) |
|
||||
for record in self: |
|
||||
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']) |
|
||||
else: |
|
||||
self._checking_lock(operation) |
|
||||
|
|
||||
@api.multi |
|
||||
def _before_unlink_record_operation(self, operation, *largs, **kwargs): |
|
||||
pass |
|
||||
|
|
||||
@api.multi |
|
||||
def _after_unlink_operation(self, result, operation, *largs, **kwargs): |
|
||||
pass |
|
||||
|
################################################################################### |
||||
|
# |
||||
|
# 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 hashlib |
||||
|
import logging |
||||
|
import itertools |
||||
|
|
||||
|
from odoo import _ |
||||
|
from odoo import models, api, fields |
||||
|
from odoo.exceptions import AccessError |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
class BaseModelLocking(models.AbstractModel): |
||||
|
|
||||
|
_name = 'muk_security.locking' |
||||
|
_description = 'MuK Locking Model' |
||||
|
_inherit = 'muk_utils.model' |
||||
|
|
||||
|
#---------------------------------------------------------- |
||||
|
# Database |
||||
|
#---------------------------------------------------------- |
||||
|
|
||||
|
locked = fields.Many2one( |
||||
|
comodel_name='muk_security.lock', |
||||
|
compute='_compute_lock', |
||||
|
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 |
||||
|
#---------------------------------------------------------- |
||||
|
|
||||
|
@api.multi |
||||
|
def lock(self, user=None, operation=None, *largs, **kwargs): |
||||
|
result = [] |
||||
|
for record in self: |
||||
|
lock = record.is_locked() |
||||
|
if lock and lock.operation and lock.operation == operation: |
||||
|
result.append({ |
||||
|
'record': record, |
||||
|
'lock': lock, |
||||
|
'token': lock.token}) |
||||
|
elif lock and ((lock.operation and lock.operation != operation) or not lock.operation): |
||||
|
raise AccessError(_("The record (%s[%s]) is locked, so it can't be locked again.") % |
||||
|
(record._name, record.id)) |
||||
|
else: |
||||
|
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.id or None, |
||||
|
'lock_ref': record._name + ',' + str(record.id), |
||||
|
'token': token, |
||||
|
'operation': operation}) |
||||
|
result.append({ |
||||
|
'record': record, |
||||
|
'lock': lock, |
||||
|
'token': token}) |
||||
|
return result |
||||
|
|
||||
|
@api.multi |
||||
|
def unlock(self, *largs, **kwargs): |
||||
|
for record in self: |
||||
|
locks = self.env['muk_security.lock'].sudo().search( |
||||
|
[('lock_ref', '=', "%s,%s" % (record._name, str(record.id)))]) |
||||
|
locks.sudo().unlink() |
||||
|
return True |
||||
|
|
||||
|
@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 references |
||||
|
|
||||
|
@api.multi |
||||
|
def is_locked(self, *largs, **kwargs): |
||||
|
self.ensure_one() |
||||
|
lock = self.env['muk_security.lock'].sudo().search( |
||||
|
[('lock_ref', '=', self._name + ',' + str(self.id))], limit=1) |
||||
|
if lock.id: |
||||
|
return lock |
||||
|
return False |
||||
|
|
||||
|
@api.multi |
||||
|
def is_locked_by(self, *largs, **kwargs): |
||||
|
self.ensure_one() |
||||
|
lock = self.env['muk_security.lock'].sudo().search( |
||||
|
[('lock_ref', '=', self._name + ',' + str(self.id))], limit=1) |
||||
|
if lock.id: |
||||
|
return lock.locked_by_ref |
||||
|
return False |
||||
|
|
||||
|
@api.multi |
||||
|
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.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)) |
||||
|
|
||||
|
@api.multi |
||||
|
def _checking_lock(self, operation=None, *largs, **kwargs): |
||||
|
self._checking_lock_user() |
||||
|
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)) |
||||
|
|
||||
|
@api.multi |
||||
|
def user_lock(self, *largs, **kwargs): |
||||
|
self.ensure_one() |
||||
|
lock = self.is_locked() |
||||
|
if lock: |
||||
|
if lock.locked_by_ref: |
||||
|
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 self.lock(user=self.env.user) |
||||
|
|
||||
|
@api.multi |
||||
|
def user_unlock(self, *largs, **kwargs): |
||||
|
self.ensure_one() |
||||
|
lock = self.is_locked() |
||||
|
if lock: |
||||
|
if lock.locked_by_ref and lock.locked_by_ref.id == self.env.user.id: |
||||
|
self.unlock() |
||||
|
else: |
||||
|
if lock.locked_by_ref: |
||||
|
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: |
||||
|
locked = record.is_locked() |
||||
|
record.update({ |
||||
|
'locked_state': bool(locked), |
||||
|
'locked': locked}) |
||||
|
|
||||
|
@api.depends('locked') |
||||
|
def _compute_editor(self): |
||||
|
for record in self: |
||||
|
record.editor = record.is_locked_by() == record.env.user |
||||
|
|
||||
|
#---------------------------------------------------------- |
||||
|
# Create, Update, Delete |
||||
|
#---------------------------------------------------------- |
||||
|
|
||||
|
@api.multi |
||||
|
def write(self, vals): |
||||
|
operation = self.generate_operation_key() |
||||
|
vals = self._before_write_operation(vals, operation) |
||||
|
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']) |
||||
|
else: |
||||
|
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 = self.generate_operation_key() |
||||
|
self._before_unlink_operation(operation) |
||||
|
for record in self: |
||||
|
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']) |
||||
|
else: |
||||
|
self._checking_lock(operation) |
||||
|
|
||||
|
@api.multi |
||||
|
def _before_unlink_record_operation(self, operation, *largs, **kwargs): |
||||
|
pass |
||||
|
|
||||
|
@api.multi |
||||
|
def _after_unlink_operation(self, result, operation, *largs, **kwargs): |
||||
|
pass |
@ -1,50 +1,81 @@ |
|||||
################################################################################### |
|
||||
# |
|
||||
# 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) |
|
||||
|
################################################################################### |
||||
|
# |
||||
|
# 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 |
||||
|
other_company = self.env['res.company'].create({ |
||||
|
'name': 'other company', |
||||
|
# without this, a partner is created and mail's constraint on |
||||
|
# notify_email kicks in |
||||
|
'partner_id': self.env.ref('base.partner_demo').id, |
||||
|
}) |
||||
|
with self.assertRaises(exceptions.AccessError): |
||||
|
self.env.ref('base.user_root').sudo(user_id).name = 'test' |
||||
|
with self.assertRaises(exceptions.AccessError): |
||||
|
other_company.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) |
||||
|
# this tests ir.rule |
||||
|
other_company.sudo(user_id).suspend_security().write({'name': 'test'}) |
||||
|
self.assertEqual(other_company.name, 'test') |
||||
|
self.assertEqual(other_company.write_uid.id, user_id) |
||||
|
# this tests if _normalize_args conversion works |
||||
|
self.env['res.users'].browse( |
||||
|
self.env['res.users'].suspend_security().env.uid) |
||||
|
# Test normal search on One2many |
||||
|
partner = self.env['res.partner'].search( |
||||
|
[('user_ids.id', '=', self.env.user.suspend_security().env.uid)], |
||||
|
limit=1) |
||||
|
self.assertEqual(partner, self.env.user.partner_id) |
||||
|
# Test search on One2many without specifing ID (standard Odoo) |
||||
|
partner = self.env['res.partner'].search( |
||||
|
[('user_ids', '=', self.env.uid)], |
||||
|
limit=1) |
||||
|
self.assertEqual(partner, self.env.user.partner_id) |
||||
|
# Test search on One2many without specifing ID (suspend_security) |
||||
|
partner = self.env['res.partner'].search( |
||||
|
[('user_ids', '=', self.env.user.suspend_security().env.uid)], |
||||
|
limit=1) |
||||
|
self.assertEqual(partner, self.env.user.partner_id) |
||||
|
# Test search on One2many without ID with IN (standard Odoo) |
||||
|
partner = self.env['res.partner'].search( |
||||
|
[('user_ids', 'in', self.env.uid)], |
||||
|
limit=1) |
||||
|
self.assertEqual(partner, self.env.user.partner_id) |
||||
|
# Test search on One2many without ID with IN (suspend_security) |
||||
|
partner = self.env['res.partner'].search( |
||||
|
[('user_ids', 'in', self.env.user.suspend_security().env.uid)], |
||||
|
limit=1) |
||||
|
self.assertEqual(partner, self.env.user.partner_id) |
||||
|
def test_normalize(self): |
||||
|
self.env['res.users'].browse(self.env['res.users'].suspend_security().env.uid) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue