diff options
| author | Lucas Lefèvre <lul@odoo.com> | 2019-11-19 09:25:24 +0000 |
|---|---|---|
| committer | Lucas Lefèvre <lul@odoo.com> | 2020-01-14 16:26:32 +0000 |
| commit | 7059b177ee3108afe951dc279d3db4054eba4115 (patch) | |
| tree | 810e7b33f66fa230910e61fb8b83c4431870984a /addons/project/tests | |
| parent | 1227e407767da810aa75a4b64ef2a335c575c00a (diff) | |
[IMP] project: Following a private project no longer required
=======
Purpose
=======
Currently for a user to be allowed to see a private project they must follow the
project. This means that they will be added as a follower of all new tasks in the
project which causes them to receive 1000000 notifications, especially if there are a
lot of tasks in a project.
==============
Specifications
==============
1) Add an Employee m2m field on project next to 'Invited Employees'
- only visible if the 'Invited Employees' option is selected
- the project should only be visible to employees selected there (regardless if they
are followers or not)
2) Add an Employee m2m field on tasks below the 'Email cc' one in debug mode
- only visible if the 'Invited Employees' option is selected on the related project
- should be pre-filled with what is set on the project
- the task should be visible to employees selected there (regardless if they are
followers or not)
3) Add a Portal users m2m field on project next to 'Portal users and all employees'
- only visible if the 'Portal users and all employees' option is selected
- the project should only be visible to portal users selected there (regardless if
they are followers or not)
- rename the option into 'Invited portal users and all employees'
4) Add a Portal users m2m field on tasks below the 'Email cc' one in debug mode
- only visible if the 'Portal users and all employees' option is selected
- should be pre-filled with what is set on the project
- the task should be visible to portal users selected there (regardless if they are
followers or not)
5) Following a project/task should not grant the user/employee the ability to see the
project/task
Task 2031527
closes odoo/odoo#40505
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
Diffstat (limited to 'addons/project/tests')
| -rw-r--r-- | addons/project/tests/__init__.py | 2 | ||||
| -rw-r--r-- | addons/project/tests/test_access_rights.py | 311 | ||||
| -rw-r--r-- | addons/project/tests/test_portal.py | 39 |
3 files changed, 218 insertions, 134 deletions
diff --git a/addons/project/tests/__init__.py b/addons/project/tests/__init__.py index a65ef6d88ce..4b37120321d 100644 --- a/addons/project/tests/__init__.py +++ b/addons/project/tests/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -# from . import test_project_base, test_project_flow, test_access_rights, test_project_ui, test_portal +from . import test_project_base, test_project_flow, test_access_rights, test_project_ui from . import test_multicompany diff --git a/addons/project/tests/test_access_rights.py b/addons/project/tests/test_access_rights.py index a6e0ac0494f..2af3a2a2647 100644 --- a/addons/project/tests/test_access_rights.py +++ b/addons/project/tests/test_access_rights.py @@ -2,101 +2,224 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.addons.project.tests.test_project_base import TestProjectCommon -from odoo.exceptions import AccessError -from odoo.tools import mute_logger +from odoo.exceptions import AccessError, ValidationError +from odoo.tests.common import new_test_user, users -class TestProjectPortalCommon(TestProjectCommon): +class TestAccessRights(TestProjectCommon): def setUp(self): - super(TestProjectPortalCommon, self).setUp() - self.user_noone = self.env['res.users'].with_context({'no_reset_password': True, 'mail_create_nosubscribe': True}).create({ - 'name': 'Noemie NoOne', - 'login': 'noemie', - 'email': 'n.n@example.com', - 'signature': '--\nNoemie', - 'notification_type': 'email', - 'groups_id': [(6, 0, [])]}) - - self.task_3 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({ - 'name': 'Test3', 'user_id': self.user_portal.id, 'project_id': self.project_pigs.id}) - self.task_4 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({ - 'name': 'Test4', 'user_id': self.user_public.id, 'project_id': self.project_pigs.id}) - self.task_5 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({ - 'name': 'Test5', 'user_id': False, 'project_id': self.project_pigs.id}) - self.task_6 = self.env['project.task'].with_context({'mail_create_nolog': True}).create({ - 'name': 'Test5', 'user_id': False, 'project_id': self.project_pigs.id}) - - -class TestPortalProject(TestProjectPortalCommon): - - @mute_logger('odoo.addons.base.models.ir_model') - def test_employee_project_access_rights(self): - pigs = self.project_pigs - - pigs.write({'privacy_visibility': 'employees'}) - # Do: Alfred reads project -> ok (employee ok employee) - pigs.with_user(self.user_projectuser).read(['user_id']) - # Test: all project tasks visible - tasks = self.env['project.task'].with_user(self.user_projectuser).search([('project_id', '=', pigs.id)]) - test_task_ids = set([self.task_1.id, self.task_2.id, self.task_3.id, self.task_4.id, self.task_5.id, self.task_6.id]) - self.assertEqual(set(tasks.ids), test_task_ids, - 'access rights: project user cannot see all tasks of an employees project') - # Do: Bert reads project -> crash, no group - self.assertRaises(AccessError, pigs.with_user(self.user_noone).read, ['user_id']) - # Do: Donovan reads project -> ko (public ko employee) - self.assertRaises(AccessError, pigs.with_user(self.user_public).read, ['user_id']) - # Do: project user is employee and can create a task - tmp_task = self.env['project.task'].with_user(self.user_projectuser).with_context({'mail_create_nolog': True}).create({ - 'name': 'Pigs task', - 'project_id': pigs.id}) - tmp_task.with_user(self.user_projectuser).unlink() - - @mute_logger('odoo.addons.base.models.ir_model') - def test_favorite_project_access_rights(self): - pigs = self.project_pigs.with_user(self.user_projectuser) - - # we can't write on project name - self.assertRaises(AccessError, pigs.write, {'name': 'False Pigs'}) - # we can write on is_favorite - pigs.write({'is_favorite': True}) - - @mute_logger('odoo.addons.base.ir.ir_model') - def test_followers_project_access_rights(self): - pigs = self.project_pigs - pigs.write({'privacy_visibility': 'followers'}) - pigs.flush(['privacy_visibility']) - # Do: Alfred reads project -> ko (employee ko followers) - self.assertRaises(AccessError, pigs.with_user(self.user_projectuser).read, ['user_id']) - # Test: no project task visible - tasks = self.env['project.task'].with_user(self.user_projectuser).search([('project_id', '=', pigs.id)]) - self.assertEqual(tasks, self.task_1, - 'access rights: employee user should not see tasks of a not-followed followers project, only assigned') - - # Do: Bert reads project -> crash, no group - self.assertRaises(AccessError, pigs.with_user(self.user_noone).read, ['user_id']) - - # Do: Donovan reads project -> ko (public ko employee) - self.assertRaises(AccessError, pigs.with_user(self.user_public).read, ['user_id']) - - pigs.message_subscribe(partner_ids=[self.user_projectuser.partner_id.id]) - - # Do: Alfred reads project -> ok (follower ok followers) - donkey = pigs.with_user(self.user_projectuser) - donkey.invalidate_cache() - donkey.read(['user_id']) - - # Do: Donovan reads project -> ko (public ko follower even if follower) - self.assertRaises(AccessError, pigs.with_user(self.user_public).read, ['user_id']) - # Do: project user is follower of the project and can create a task - self.env['project.task'].with_user(self.user_projectuser).with_context({'mail_create_nolog': True}).create({ - 'name': 'Pigs task', 'project_id': pigs.id - }) - # not follower user should not be able to create a task - pigs.with_user(self.user_projectuser).message_unsubscribe(partner_ids=[self.user_projectuser.partner_id.id]) - self.assertRaises(AccessError, self.env['project.task'].with_user(self.user_projectuser).with_context({ - 'mail_create_nolog': True}).create, {'name': 'Pigs task', 'project_id': pigs.id}) - - # Do: project user can create a task without project - self.assertRaises(AccessError, self.env['project.task'].with_user(self.user_projectuser).with_context({ - 'mail_create_nolog': True}).create, {'name': 'Pigs task', 'project_id': pigs.id}) + super().setUp() + self.task = self.create_task('Make the world a better place') + self.user = new_test_user(self.env, 'Internal user', groups='base.group_user') + self.portal = new_test_user(self.env, 'Portal user', groups='base.group_portal') + + def create_task(self, name, *, with_user=None, **kwargs): + values = dict(name=name, project_id=self.project_pigs.id, **kwargs) + return self.env['project.task'].with_user(with_user or self.env.user).create(values) + + +class TestCRUDVisibilityFollowers(TestAccessRights): + + def setUp(self): + super().setUp() + self.project_pigs.privacy_visibility = 'followers' + + @users('Internal user', 'Portal user') + def test_project_no_write(self): + with self.assertRaises(AccessError, msg="%s should not be able to write on the project" % self.env.user.name): + self.project_pigs.with_user(self.env.user).name = "Take over the world" + + self.project_pigs.allowed_user_ids = self.env.user + with self.assertRaises(AccessError, msg="%s should not be able to write on the project" % self.env.user.name): + self.project_pigs.with_user(self.env.user).name = "Take over the world" + + @users('Internal user', 'Portal user') + def test_project_no_unlink(self): + self.project_pigs.task_ids.unlink() + with self.assertRaises(AccessError, msg="%s should not be able to unlink the project" % self.env.user.name): + self.project_pigs.with_user(self.env.user).unlink() + + self.project_pigs.allowed_user_ids = self.env.user + self.project_pigs.task_ids.unlink() + with self.assertRaises(AccessError, msg="%s should not be able to unlink the project" % self.env.user.name): + self.project_pigs.with_user(self.env.user).unlink() + + @users('Internal user', 'Portal user') + def test_project_no_read(self): + self.project_pigs.invalidate_cache() + with self.assertRaises(AccessError, msg="%s should not be able to read the project" % self.env.user.name): + self.project_pigs.with_user(self.env.user).name + + @users('Portal user') + def test_project_allowed_portal_no_read(self): + self.project_pigs.allowed_user_ids = self.env.user + self.project_pigs.invalidate_cache() + with self.assertRaises(AccessError, msg="%s should not be able to read the project" % self.env.user.name): + self.project_pigs.with_user(self.env.user).name + + @users('Internal user') + def test_project_allowed_internal_read(self): + self.project_pigs.allowed_user_ids = self.env.user + self.project_pigs.invalidate_cache() + self.project_pigs.with_user(self.env.user).name + + @users('Internal user', 'Portal user') + def test_task_no_read(self): + with self.assertRaises(AccessError, msg="%s should not be able to read the task" % self.env.user.name): + self.task.with_user(self.env.user).name + + @users('Portal user') + def test_task_allowed_portal_no_read(self): + self.project_pigs.allowed_user_ids = self.env.user + self.task.invalidate_cache() + with self.assertRaises(AccessError, msg="%s should not be able to read the task" % self.env.user.name): + self.task.with_user(self.env.user).name + + @users('Internal user') + def test_task_allowed_internal_read(self): + self.project_pigs.allowed_user_ids = self.env.user + self.task.invalidate_cache() + self.task.with_user(self.env.user).name + + @users('Internal user', 'Portal user') + def test_task_no_write(self): + with self.assertRaises(AccessError, msg="%s should not be able to write on the task" % self.env.user.name): + self.task.with_user(self.env.user).name = "Paint the world in black & white" + + self.project_pigs.allowed_user_ids = self.env.user + with self.assertRaises(AccessError, msg="%s should not be able to write on the task" % self.env.user.name): + self.task.with_user(self.env.user).name = "Paint the world in black & white" + + @users('Internal user', 'Portal user') + def test_task_no_create(self): + with self.assertRaises(AccessError, msg="%s should not be able to create a task" % self.env.user.name): + self.create_task("Archive the world, it's not needed anymore") + + self.project_pigs.allowed_user_ids = self.env.user + with self.assertRaises(AccessError, msg="%s should not be able to create a task" % self.env.user.name): + self.create_task("Archive the world, it's not needed anymore") + + @users('Internal user', 'Portal user') + def test_task_no_unlink(self): + with self.assertRaises(AccessError, msg="%s should not be able to unlink the task" % self.env.user.name): + self.task.with_user(self.env.user).unlink() + + self.project_pigs.allowed_user_ids = self.env.user + with self.assertRaises(AccessError, msg="%s should not be able to unlink the task" % self.env.user.name): + self.task.with_user(self.env.user).unlink() + + +class TestCRUDVisibilityPortal(TestAccessRights): + + def setUp(self): + super().setUp() + self.project_pigs.privacy_visibility = 'portal' + + @users('Portal user') + def test_task_portal_no_read(self): + self.task.invalidate_cache() + with self.assertRaises(AccessError, msg="%s should not be able to read the task" % self.env.user.name): + self.task.with_user(self.env.user).name + + @users('Portal user') + def test_task_allowed_portal_read(self): + self.project_pigs.allowed_user_ids = self.env.user + self.task.invalidate_cache() + self.task.with_user(self.env.user).name + + @users('Internal user') + def test_task_internal_read(self): + self.task.with_user(self.env.user).name + + +class TestCRUDVisibilityEmployees(TestAccessRights): + + def setUp(self): + super().setUp() + self.project_pigs.privacy_visibility = 'employees' + + @users('Portal user') + def test_task_portal_no_read(self): + with self.assertRaises(AccessError, msg="%s should not be able to read the task" % self.env.user.name): + self.task.with_user(self.env.user).name + + self.project_pigs.allowed_user_ids = self.env.user + self.task.invalidate_cache() + with self.assertRaises(AccessError, msg="%s should not be able to read the task" % self.env.user.name): + self.task.with_user(self.env.user).name + + @users('Internal user') + def test_task_allowed_portal_read(self): + self.task.invalidate_cache() + self.task.with_user(self.env.user).name + + +class TestAllowedUsers(TestAccessRights): + + def setUp(self): + super().setUp() + self.project_pigs.privacy_visibility = 'followers' + + def test_project_permission_added(self): + self.project_pigs.allowed_user_ids = self.user + self.assertIn(self.user, self.task.allowed_user_ids) + + def test_project_default_permission(self): + self.project_pigs.allowed_user_ids = self.user + task = self.create_task("Review the end of the world") + self.assertIn(self.user, task.allowed_user_ids) + + def test_project_default_customer_permission(self): + self.project_pigs.privacy_visibility = 'portal' + self.project_pigs.partner_id = self.portal.partner_id + self.assertIn(self.portal, self.task.allowed_user_ids) + self.assertIn(self.portal, self.project_pigs.allowed_user_ids) + + def test_project_permission_removed(self): + self.project_pigs.allowed_user_ids = self.user + self.project_pigs.allowed_user_ids -= self.user + self.assertNotIn(self.user, self.task.allowed_user_ids) + + def test_project_specific_permission(self): + self.project_pigs.allowed_user_ids = self.user + john = new_test_user(self.env, 'John') + self.task.allowed_user_ids |= john + self.project_pigs.allowed_user_ids -= self.user + self.assertIn(john, self.task.allowed_user_ids, "John should still be allowed to read the task") + + def test_project_specific_remove_mutliple_tasks(self): + self.project_pigs.allowed_user_ids = self.user + john = new_test_user(self.env, 'John') + task = self.create_task('task') + self.task.allowed_user_ids |= john + self.project_pigs.allowed_user_ids -= self.user + self.assertIn(john, self.task.allowed_user_ids) + self.assertNotIn(john, task.allowed_user_ids) + self.assertNotIn(self.user, task.allowed_user_ids) + self.assertNotIn(self.user, self.task.allowed_user_ids) + + def test_no_portal_allowed(self): + with self.assertRaises(ValidationError, msg="It should not allow to add portal users"): + self.task.allowed_user_ids = self.portal + + def test_visibility_changed(self): + self.project_pigs.privacy_visibility = 'portal' + self.task.allowed_user_ids |= self.portal + self.assertNotIn(self.user, self.task.allowed_user_ids, "Internal user should have been removed from allowed users") + self.project_pigs.privacy_visibility = 'employees' + self.assertNotIn(self.portal, self.task.allowed_user_ids, "Portal user should have been removed from allowed users") + + def test_write_task(self): + self.user.groups_id |= self.env.ref('project.group_project_user') + self.assertNotIn(self.user, self.project_pigs.allowed_user_ids) + self.task.allowed_user_ids = self.user + self.project_pigs.invalidate_cache() + self.task.invalidate_cache() + self.task.with_user(self.user).name = "I can edit a task!" + + def test_no_write_project(self): + self.user.groups_id |= self.env.ref('project.group_project_user') + self.assertNotIn(self.user, self.project_pigs.allowed_user_ids) + with self.assertRaises(AccessError, msg="User should not be able to edit project"): + self.project_pigs.with_user(self.user).name = "I can't edit a task!" diff --git a/addons/project/tests/test_portal.py b/addons/project/tests/test_portal.py deleted file mode 100644 index 5ae9bda4bb3..00000000000 --- a/addons/project/tests/test_portal.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo.addons.project.tests.test_access_rights import TestProjectPortalCommon -from odoo.exceptions import AccessError -from odoo.tools import mute_logger - - -class TestPortalProject(TestProjectPortalCommon): - @mute_logger('odoo.addons.base.models.ir_model') - def test_portal_project_access_rights(self): - pigs = self.project_pigs - pigs.write({'privacy_visibility': 'portal'}) - - # Do: Alfred reads project -> ok (employee ok public) - pigs.with_user(self.user_projectuser).read(['user_id']) - # Test: all project tasks visible - tasks = self.env['project.task'].with_user(self.user_projectuser).search([('project_id', '=', pigs.id)]) - self.assertEqual(tasks, self.task_1 | self.task_2 | self.task_3 | self.task_4 | self.task_5 | self.task_6, - 'access rights: project user should see all tasks of a portal project') - - # Do: Bert reads project -> crash, no group - self.assertRaises(AccessError, pigs.with_user(self.user_noone).read, ['user_id']) - # Test: no project task searchable - self.assertRaises(AccessError, self.env['project.task'].with_user(self.user_noone).search, [('project_id', '=', pigs.id)]) - - # Data: task follower - pigs.with_user(self.user_projectmanager).message_subscribe(partner_ids=[self.user_portal.partner_id.id]) - self.task_1.with_user(self.user_projectuser).message_subscribe(partner_ids=[self.user_portal.partner_id.id]) - self.task_3.with_user(self.user_projectuser).message_subscribe(partner_ids=[self.user_portal.partner_id.id]) - # Do: Chell reads project -> ok (portal ok public) - pigs.with_user(self.user_portal).read(['user_id']) - # Do: Donovan reads project -> ko (public ko portal) - self.assertRaises(AccessError, pigs.with_user(self.user_public).read, ['user_id']) - # Test: no access right to project.task - self.assertRaises(AccessError, self.env['project.task'].with_user(self.user_public).search, []) - # Data: task follower cleaning - self.task_1.with_user(self.user_projectuser).message_unsubscribe(partner_ids=[self.user_portal.partner_id.id]) - self.task_3.with_user(self.user_projectuser).message_unsubscribe(partner_ids=[self.user_portal.partner_id.id]) |
