[FIX] project: sharing, restrict child_ids an tag_ids for portals

Oversight in 06919a232251cbd68a28500757aef5cd2e3363a0

Need to handle `default_child_ids` and `default_tag_ids` as well.

closes odoo/odoo#153397

X-original-commit: 7d521b129c82e38afe50cd8b81cb2081c157aceb
Signed-off-by: Denis Ledoux (dle) <dle@odoo.com>
This commit is contained in:
Denis Ledoux
2024-02-09 11:57:35 +00:00
parent b93ba8f631
commit 81389be677
2 changed files with 42 additions and 16 deletions
+22 -16
View File
@@ -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()}
@@ -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)