Files
public/directive_layer/tests/test_directive_security.py

146 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from odoo.exceptions import AccessError
from odoo.tests.common import TransactionCase, tagged
@tagged('standard', 'at_install', 'directive_logic')
class TestDirectiveSecurity(TransactionCase):
"""Поведенческая проверка ролевой модели безопасности (design 5.1/5.2).
Назначаем бизнес-роль через role_line_ids и проверяем, что:
- пользователь попадает в техническую группу _res_groups (implied_ids);
- исполнитель читает/двигает свою директиву, но не может её удалить;
- исполнитель не видит чужие директивы (record rule rule_directive_own);
- диспетчер видит все директивы и может удалять.
"""
def setUp(self):
super().setUp()
self.role_executor = self.env.ref('directive_layer.role_directive_executor')
self.role_dispatcher = self.env.ref('directive_layer.role_directive_dispatcher')
self.grp_executor = self.env.ref('directive_layer.role_directive_executor_res_groups')
self.grp_dispatcher = self.env.ref('directive_layer.role_directive_dispatcher_res_groups')
self.work_type = self.env['directive.work.type'].create({
'name': 'Development',
'code': 'dev',
})
self.stage_draft = self.env['directive.stage'].search([('code', '=', 'draft')], limit=1)
if not self.stage_draft:
self.stage_draft = self.env['directive.stage'].create({'name': 'Draft', 'code': 'draft'})
self.executor_user = self._make_user('exec_user', self.role_executor)
self.dispatcher_user = self._make_user('disp_user', self.role_dispatcher)
# Рядовой internal user без единой роли директив (fail-closed).
self.roleless_user = self.env['res.users'].create({
'name': 'roleless_user',
'login': 'roleless_user',
'email': 'roleless_user@example.com',
'group_ids': [(4, self.env.ref('base.group_user').id)],
})
# Своя директива исполнителя + чужая директива.
self.own_directive = self._make_directive(
'Own directive', executor_user=self.executor_user)
self.other_directive = self._make_directive(
'Other directive', executor_user=self.dispatcher_user)
def _make_user(self, login, role):
# На create res.users вызывается set_groups_from_roles(): группы
# пользователя заменяются на role.group_id + implied_ids (т.е. _res_groups).
return self.env['res.users'].create({
'name': login,
'login': login,
'email': f'{login}@example.com',
'role_line_ids': [(0, 0, {'role_id': role.id})],
})
def _make_directive(self, name, executor_user):
origin = self.env['directive.origin'].create({
'res_model': 'res.partner',
'res_id': self.env.user.partner_id.id,
})
return self.env['directive.directive'].create({
'name': name,
'deadline_at': '2099-01-01 00:00:00',
'planned_hours': 1.0,
'work_type_id': self.work_type.id,
'stage_id': self.stage_draft.id,
'origin_id': origin.id,
'executor_user_id': executor_user.id,
})
def test_01_role_grants_technical_group(self):
"""Назначение бизнес-роли переводит пользователя в её _res_groups."""
self.assertTrue(
self.executor_user.has_group('directive_layer.role_directive_executor_res_groups'),
"Исполнитель не попал в role_directive_executor_res_groups после назначения роли.",
)
self.assertTrue(
self.dispatcher_user.has_group('directive_layer.role_directive_dispatcher_res_groups'),
"Диспетчер не попал в role_directive_dispatcher_res_groups после назначения роли.",
)
def test_02_executor_read_write_own(self):
"""Исполнитель читает и двигает свою директиву."""
directive = self.own_directive.with_user(self.executor_user)
self.assertEqual(directive.name, 'Own directive')
directive.write({'load_percent': 50})
self.assertEqual(directive.load_percent, 50)
def test_03_executor_cannot_unlink(self):
"""Исполнитель не может удалить директиву (нет perm_unlink)."""
directive = self.own_directive.with_user(self.executor_user)
with self.assertRaises(AccessError):
directive.unlink()
def test_04_executor_cannot_create(self):
"""Исполнитель не может создавать директивы напрямую (нет perm_create)."""
origin = self.env['directive.origin'].create({
'res_model': 'res.partner',
'res_id': self.env.user.partner_id.id,
})
with self.assertRaises(AccessError):
self.env['directive.directive'].with_user(self.executor_user).create({
'name': 'Illegal',
'deadline_at': '2099-01-01 00:00:00',
'planned_hours': 1.0,
'work_type_id': self.work_type.id,
'stage_id': self.stage_draft.id,
'origin_id': origin.id,
})
def test_05_executor_sees_only_own(self):
"""Record rule: исполнитель видит свою директиву, но не чужую."""
Directive = self.env['directive.directive'].with_user(self.executor_user)
visible_ids = Directive.search([]).ids
self.assertIn(self.own_directive.id, visible_ids,
"Исполнитель не видит собственную директиву.")
self.assertNotIn(self.other_directive.id, visible_ids,
"Исполнитель видит чужую директиву — record rule не работает.")
def test_06_dispatcher_sees_all_and_can_unlink(self):
"""Диспетчер видит все директивы и может удалять."""
Directive = self.env['directive.directive'].with_user(self.dispatcher_user)
visible_ids = Directive.search([]).ids
self.assertIn(self.own_directive.id, visible_ids)
self.assertIn(self.other_directive.id, visible_ids)
Directive.browse(self.own_directive.id).unlink()
self.assertFalse(self.own_directive.exists())
def test_07_roleless_user_sees_zero(self):
"""Fail-closed: internal user без ролей директив видит ноль директив.
base.group_user даёт только bounded read (чтобы related-поля на формах-
источниках грузились), а global record rule ограничивает выборку нулём
строк для пользователя без совпадений own/role. Эта проверка фиксирует
инвариант, чтобы будущий рефактор не ослабил fail-closed молча.
"""
Directive = self.env['directive.directive'].with_user(self.roleless_user)
self.assertFalse(self.roleless_user.has_group(
'directive_layer.role_directive_dispatcher_res_groups'))
self.assertEqual(
Directive.search([]).ids, [],
"Пользователь без ролей директив видит директивы — нарушен fail-closed.",
)