Skip to content

Commit 963f229

Browse files
committed
[FIX] mail: check access on translation too
This commit checks that users without the access right 'Template Editor' are not allowed to add dynamic qweb in the translations of a mail template. Old tests have been updated to keep the assertRaises and all the code that handle the invalidation cache, ... But now we ensure that the Access Error raised is the expected one. Original tests was testing that a user without the group 'Mail Editor' got an access error due to the missing group, but since the commit 1a3e713 the Access Error is because you cannot edit a template that is not created by yourself. closes odoo#94585 X-original-commit: 127a598 Signed-off-by: Thibault Delavallee (tde) <[email protected]>
1 parent 876aa76 commit 963f229

File tree

3 files changed

+141
-1
lines changed

3 files changed

+141
-1
lines changed

addons/mail/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from . import ir_http
4747
from . import ir_model
4848
from . import ir_model_fields
49+
from . import ir_translation
4950
from . import ir_ui_view
5051
from . import ir_qweb
5152
from . import res_company
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# -*- coding: utf-8 -*-
2+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
3+
4+
from odoo import models, _
5+
from odoo.exceptions import AccessError
6+
7+
8+
class IrTranslation(models.Model):
9+
_inherit = 'ir.translation'
10+
11+
def create(self, vals_list):
12+
translations = super().create(vals_list)
13+
translations._check_is_dynamic()
14+
return translations
15+
16+
def write(self, vals):
17+
res = super().write(vals)
18+
self._check_is_dynamic()
19+
return res
20+
21+
def _check_is_dynamic(self):
22+
# if we don't modify translation of at least a model that inherits from mail.render.mixin, we ignore it
23+
# translation.name can be a path, and so not in the pool, so type(None) will exclude these translations.
24+
translations_for_mail_render_mixin = self.filtered(
25+
lambda translation: issubclass(type(self.env.get(translation.name.split(',')[0])), self.pool['mail.render.mixin'])
26+
)
27+
if not translations_for_mail_render_mixin:
28+
return
29+
30+
# if we are admin, or that we can update mail.template we ignore
31+
if self.env.is_admin() or self.env.user.has_group('mail.group_mail_template_editor'):
32+
return
33+
34+
# Check that we don't add qweb code in translation when you don't have the rights
35+
36+
# prefill cache
37+
ids_by_model_by_lang = {}
38+
tuple_lang_model_id = translations_for_mail_render_mixin.mapped(
39+
lambda translation: (translation.lang, translation.name.split(',')[0], translation.res_id)
40+
)
41+
for lang, model, _id in tuple_lang_model_id:
42+
ids_by_model_by_lang.setdefault(lang, {}).setdefault(model, set()).add(_id)
43+
for lang in ids_by_model_by_lang:
44+
for res_model, res_ids in ids_by_model_by_lang[lang].items():
45+
self.env[res_model].with_context(lang=lang).browse(res_ids)
46+
47+
for trans in translations_for_mail_render_mixin:
48+
res_model, res_id = trans.name.split(',')[0], trans.res_id
49+
rec = self.env[res_model].with_context(lang=trans.lang).browse(res_id)
50+
51+
if rec._is_dynamic():
52+
group = self.env.ref('mail.group_mail_template_editor')
53+
more_info = len(self) > 1 and ' [%s]' % rec or ''
54+
raise AccessError(
55+
_('Only users belonging to the "%(group)s" group can modify translation related to dynamic templates.%(xtra)s',
56+
group=group.name,
57+
xtra=more_info
58+
)
59+
)

addons/mail/tests/test_mail_template.py

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,92 @@ def test_mail_template_acl(self):
7070
with self.assertRaises(AccessError):
7171
self.env['mail.template'].with_user(self.user_employee).create({'body_html': '<p t-esc="\'foo\'"></p>'})
7272

73+
# Standard employee cannot edit templates from another user, non-dynamic and dynamic
74+
with self.assertRaises(AccessError):
75+
mail_template.with_user(self.user_employee).body_html = '<p>foo</p>'
7376
with self.assertRaises(AccessError):
7477
mail_template.with_user(self.user_employee).body_html = '<p t-esc="\'foo\'"></p>'
7578

79+
# Standard employee can edit his own templates if not dynamic
80+
employee_template.with_user(self.user_employee).body_html = '<p>foo</p>'
81+
7682
# Standard employee cannot create and edit templates with dynamic inline fields
7783
with self.assertRaises(AccessError):
7884
self.env['mail.template'].with_user(self.user_employee).create({'email_to': '{{ object.partner_id.email }}'})
7985

86+
# Standard employee cannot edit his own templates if dynamic
87+
with self.assertRaises(AccessError):
88+
employee_template.with_user(self.user_employee).body_html = '<p t-esc="\'foo\'"></p>'
89+
90+
with self.assertRaises(AccessError):
91+
employee_template.with_user(self.user_employee).email_to = '{{ object.partner_id.email }}'
92+
93+
def test_mail_template_acl_translation(self):
94+
''' Test that a user that doenn't have the group_mail_template_editor cannot create / edit
95+
translation with dynamic code if he cannot write dynamic code on the related record itself.
96+
'''
97+
98+
self.env.ref('base.lang_fr').sudo().active = True
99+
100+
employee_template = self.env['mail.template'].with_user(self.user_employee).create({
101+
'model_id': self.env.ref('base.model_res_partner').id,
102+
'subject': 'The subject',
103+
'body_html': '<p>foo</p>',
104+
})
105+
106+
Translation = self.env['ir.translation']
107+
108+
### check qweb dynamic
109+
Translation.insert_missing(employee_template._fields['body_html'], employee_template)
110+
employee_translations_of_body = Translation.with_user(self.user_employee).search(
111+
[('res_id', '=', employee_template.id), ('name', '=', 'mail.template,body_html'), ('lang', '=', 'fr_FR')],
112+
limit=1
113+
)
114+
# keep a copy to create new translation later
115+
body_translation_vals = employee_translations_of_body.read([])[0]
116+
117+
# write on translation for template without dynamic code is allowed
118+
employee_translations_of_body.value = 'non-qweb'
119+
120+
# cannot write dynamic code on mail_template translation for employee without the group mail_template_editor.
121+
with self.assertRaises(AccessError):
122+
employee_translations_of_body.value = '<t t-esc="foo"/>'
123+
124+
employee_translations_of_body.unlink() # delete old translation, to test the creation now
125+
body_translation_vals['value'] = '<p t-esc="foo"/>'
126+
127+
# admin can create
128+
new = Translation.create(body_translation_vals)
129+
new.unlink()
130+
131+
# Employee without mail_template_editor group cannot create dynamic translation for mail.render.mixin
132+
with self.assertRaises(AccessError):
133+
Translation.with_user(self.user_employee).create(body_translation_vals)
134+
135+
136+
### check qweb inline dynamic
137+
Translation.insert_missing(employee_template._fields['subject'], employee_template)
138+
employee_translations_of_subject = Translation.with_user(self.user_employee).search(
139+
[('res_id', '=', employee_template.id), ('name', '=', 'mail.template,subject'), ('lang', '=', 'fr_FR')],
140+
limit=1
141+
)
142+
# keep a copy to create new translation later
143+
subject_translation_vals = employee_translations_of_subject.read([])[0]
144+
145+
# write on translation for template without dynamic code is allowed
146+
employee_translations_of_subject.value = 'non-qweb'
147+
148+
# cannot write dynamic code on mail_template translation for employee without the group mail_template_editor.
149+
with self.assertRaises(AccessError):
150+
employee_translations_of_subject.value = '{{ object.foo }}'
151+
152+
employee_translations_of_subject.unlink() # delete old translation, to test the creation now
153+
subject_translation_vals['value'] = '{{ object.foo }}'
154+
155+
# admin can create
156+
new = Translation.create(subject_translation_vals)
157+
new.unlink()
158+
159+
# Employee without mail_template_editor group cannot create dynamic translation for mail.render.mixin
80160
with self.assertRaises(AccessError):
81-
mail_template.with_user(self.user_employee).email_to = '{{ object.partner_id.email }}'
161+
Translation.with_user(self.user_employee).create(subject_translation_vals)

0 commit comments

Comments
 (0)