diff --git a/addons/project/models/project_task.py b/addons/project/models/project_task.py index 4651c8c10dd..54394d6b4a3 100644 --- a/addons/project/models/project_task.py +++ b/addons/project/models/project_task.py @@ -796,7 +796,7 @@ class Task(models.Model): error_message = _('You cannot write on %s fields in task.', ', '.join(unauthorized_fields)) raise AccessError(error_message) - def _get_sudo_portal_vals(self, vals): + def _get_portal_sudo_vals(self, vals, defaults=False): """ returns the values which must be written without and with sudo when a portal user creates / writes a task. :param vals: dict of {field: value}, the values to create/write :return: a tuple with 2 dicts: @@ -804,9 +804,25 @@ class Task(models.Model): - the second with the values to write with sudo """ vals_no_sudo = {key: val for key, val in vals.items() if self._fields[key].type in ('one2many', 'many2many')} + if defaults: + vals_no_sudo.update({ + key[8:]: value + for key, value in self.env.context.items() + if key.startswith('default_') and key[8:] in self.SELF_WRITABLE_FIELDS and self._fields[key[8:]].type in ('one2many', 'many2many') + }) vals_sudo = {key: val for key, val in vals.items() if key not in vals_no_sudo} return vals_no_sudo, vals_sudo + @api.model + def _get_portal_sudo_context(self): + return { + key: value for key, value in self.env.context.items() + if key == 'default_project_id' + or key == 'default_user_ids' and value is False + or not key.startswith('default_') + or key[8:] in (field for field in self.SELF_WRITABLE_FIELDS if self._fields[field].type not in ('one2many', 'many2many')) + } + def read(self, fields=None, load='_classic_read'): self._ensure_fields_are_accessible(fields) return super(Task, self).read(fields=fields, load=load) @@ -868,16 +884,6 @@ class Task(models.Model): return tasks - @api.model - def _get_portal_sudo_context(self): - return { - key: value for key, value in self.env.context.items() - if key == 'default_project_id' - or key == 'default_user_ids' and value is False - or not key.startswith('default_') - or key[8:] in self.SELF_WRITABLE_FIELDS - } - @api.model_create_multi def create(self, vals_list): new_context = dict(self.env.context) @@ -951,12 +957,12 @@ class Task(models.Model): # in order to compute the field tracking was_in_sudo = self.env.su if is_portal_user: - self = self.sudo().with_context(self._get_portal_sudo_context()) - vals_list_no_sudo, vals_list = zip(*(self._get_sudo_portal_vals(vals) for vals in vals_list)) + vals_list_no_sudo, vals_list = zip(*(self._get_portal_sudo_vals(vals, defaults=True) for vals in vals_list)) + self_no_sudo, self = self, self.sudo().with_context(self._get_portal_sudo_context()) tasks = super(Task, self.with_context(mail_create_nosubscribe=True)).create(vals_list) if is_portal_user: - for task, vals in zip(tasks, vals_list_no_sudo): - task.sudo(was_in_sudo).write(vals) + for task, vals in zip(tasks.with_env(self_no_sudo.env), vals_list_no_sudo): + task.write(vals) tasks._populate_missing_personal_stages() self._task_message_auto_subscribe_notify({task: task.user_ids - self.env.user for task in tasks}) @@ -1103,7 +1109,7 @@ class Task(models.Model): # in order to keep the same name than the task. if portal_can_write: self_no_sudo, self = self, self.sudo().with_context(self._get_portal_sudo_context()) - vals_no_sudo, vals = self._get_sudo_portal_vals(vals) + vals_no_sudo, vals = self._get_portal_sudo_vals(vals) # Track user_ids to send assignment notifications old_user_ids = {t: t.user_ids for t in self.sudo()} diff --git a/addons/project/tests/test_project_sharing.py b/addons/project/tests/test_project_sharing.py index 6103c8b8978..1bbd102518e 100644 --- a/addons/project/tests/test_project_sharing.py +++ b/addons/project/tests/test_project_sharing.py @@ -172,6 +172,18 @@ class TestProjectSharing(TestProjectSharingCommon): with self.assertRaisesRegex(AccessError, "top-secret records"): Task.create({'name': 'foo', 'child_ids': [Command.set([self.task_no_collabo.id])]}) + # Same thing but using context defaults + with self.assertRaisesRegex(AccessError, "top-secret records"): + Task.with_context(default_child_ids=[Command.update(self.task_no_collabo.id, {'name': 'Foo'})]).create({'name': 'foo'}) + with self.assertRaisesRegex(AccessError, "top-secret records"): + Task.with_context(default_child_ids=[Command.delete(self.task_no_collabo.id)]).create({'name': 'foo'}) + with self.assertRaisesRegex(AccessError, "top-secret records"): + Task.with_context(default_child_ids=[Command.unlink(self.task_no_collabo.id)]).create({'name': 'foo'}) + with self.assertRaisesRegex(AccessError, "top-secret records"): + Task.with_context(default_child_ids=[Command.link(self.task_no_collabo.id)]).create({'name': 'foo'}) + with self.assertRaisesRegex(AccessError, "top-secret records"): + Task.with_context(default_child_ids=[Command.set([self.task_no_collabo.id])]).create({'name': 'foo'}) + # Create/update a tag through tag_ids with self.assertRaisesRegex(AccessError, "not allowed to create 'Project Tags'"): Task.create({'name': 'foo', 'tag_ids': [Command.create({'name': 'Bar'})]}) @@ -180,6 +192,14 @@ class TestProjectSharing(TestProjectSharingCommon): with self.assertRaisesRegex(AccessError, "not allowed to delete 'Project Tags'"): Task.create({'name': 'foo', 'tag_ids': [Command.delete(self.task_tag.id)]}) + # Same thing but using context defaults + with self.assertRaisesRegex(AccessError, "not allowed to create 'Project Tags'"): + Task.with_context(default_tag_ids=[Command.create({'name': 'Bar'})]).create({'name': 'foo'}) + with self.assertRaisesRegex(AccessError, "not allowed to modify 'Project Tags'"): + Task.with_context(default_tag_ids=[Command.update(self.task_tag.id, {'name': 'Bar'})]).create({'name': 'foo'}) + with self.assertRaisesRegex(AccessError, "not allowed to delete 'Project Tags'"): + Task.with_context(default_tag_ids=[Command.delete(self.task_tag.id)]).create({'name': 'foo'}) + task = Task.create({'name': 'foo', 'tag_ids': [Command.link(self.task_tag.id)]}) self.assertEqual(task.tag_ids, self.task_tag)