diff --git a/addons/mail/i18n/mail.pot b/addons/mail/i18n/mail.pot
index 13a684723f1..caa67ad0252 100644
--- a/addons/mail/i18n/mail.pot
+++ b/addons/mail/i18n/mail.pot
@@ -4856,6 +4856,7 @@ msgstr ""
#. module: mail
#: code:addons/mail/models/mail_render_mixin.py:0
#: code:addons/mail/models/mail_render_mixin.py:0
+#: code:addons/mail/models/mail_render_mixin.py:0
#, python-format
msgid "Only users belonging to the \"%s\" group can modify dynamic templates."
msgstr ""
diff --git a/addons/mail/models/mail_render_mixin.py b/addons/mail/models/mail_render_mixin.py
index a43df861a14..8e8f2e3b171 100644
--- a/addons/mail/models/mail_render_mixin.py
+++ b/addons/mail/models/mail_render_mixin.py
@@ -127,6 +127,23 @@ class MailRenderMixin(models.AbstractModel):
# allow specifying rendering options directly from field when using the render mixin
return name in ['render_engine', 'render_options'] or super()._valid_field_parameter(field, name)
+ @api.model_create_multi
+ def create(self, values_list):
+ record = super().create(values_list)
+ if self._unrestricted_rendering:
+ # If the rendering is unrestricted (e.g. mail.template),
+ # check the user is part of the mail editor group to create a new template if the template is dynamic
+ record._check_access_right_dynamic_template()
+ return record
+
+ def write(self, vals):
+ super().write(vals)
+ if self._unrestricted_rendering:
+ # If the rendering is unrestricted (e.g. mail.template),
+ # check the user is part of the mail editor group to modify a template if the template is dynamic
+ self._check_access_right_dynamic_template()
+ return True
+
# ------------------------------------------------------------
# TOOLS
# ------------------------------------------------------------
@@ -214,6 +231,48 @@ class MailRenderMixin(models.AbstractModel):
""").format(preview_markup)
return tools.prepend_html_content(html, html_preview)
return html
+
+ # ------------------------------------------------------------
+ # SECURITY
+ # ------------------------------------------------------------
+
+ def _is_dynamic(self):
+ for template in self:
+ for fname, field in template._fields.items():
+ engine = getattr(field, 'render_engine', 'inline_template')
+ if engine in ('qweb', 'qweb_view'):
+ if self._is_dynamic_template_qweb(template[fname]):
+ return True
+ else:
+ if self._is_dynamic_template_inline_template(template[fname]):
+ return True
+ return False
+
+ @api.model
+ def _is_dynamic_template_qweb(self, template_src):
+ if template_src:
+ try:
+ node = html.fragment_fromstring(template_src, create_parent='div')
+ self.env["ir.qweb"].with_context(raise_on_code=True)._compile(node)
+ except QWebException as e:
+ if isinstance(e.__cause__, PermissionError):
+ return True
+ raise
+ return False
+
+ @api.model
+ def _is_dynamic_template_inline_template(self, template_txt):
+ if template_txt:
+ template_instructions = parse_inline_template(str(template_txt))
+ if len(template_instructions) > 1 or template_instructions[0][1]:
+ return True
+ return False
+
+ def _check_access_right_dynamic_template(self):
+ if not self.env.su and not self.env.user.has_group('mail.group_mail_template_editor') and self._is_dynamic():
+ group = self.env.ref('mail.group_mail_template_editor')
+ raise AccessError(_('Only users belonging to the "%s" group can modify dynamic templates.', group.name))
+
# ------------------------------------------------------------
# RENDERING
# ------------------------------------------------------------
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index 33fefed4c3d..5158c30bae2 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -31,7 +31,7 @@ access_mail_tracking_value_portal,mail.tracking.value.portal,model_mail_tracking
access_mail_tracking_value_user,mail.tracking.value.user,model_mail_tracking_value,base.group_user,0,0,0,0
access_mail_tracking_value_system,mail.tracking.value.system,model_mail_tracking_value,base.group_system,1,1,1,1
access_publisher_warranty_contract_all,publisher.warranty.contract.all,model_publisher_warranty_contract,,1,1,1,1
-access_mail_template,mail.template,model_mail_template,base.group_user,1,0,0,0
+access_mail_template,mail.template,model_mail_template,base.group_user,1,1,1,1
access_mail_template_editor,mail.template_editor,model_mail_template,mail.group_mail_template_editor,1,1,1,1
access_mail_template_system,mail.template_system,model_mail_template,base.group_system,1,1,1,1
access_mail_shortcode,mail.shortcode,model_mail_shortcode,base.group_user,1,1,1,1
diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml
index 31a7b11a9db..1c5fd4a30eb 100644
--- a/addons/mail/security/mail_security.xml
+++ b/addons/mail/security/mail_security.xml
@@ -65,6 +65,28 @@
+
+ Employees can only change their own templates
+
+ [('create_uid', '=', user.id)]
+
+
+
+
+
+
+
+
+ Mail Template Editors - Edit All Templates
+
+ [(1, '=', 1)]
+
+
+
+
+
+
+
Administrators can access all User Settings.
diff --git a/addons/mail/tests/test_mail_template.py b/addons/mail/tests/test_mail_template.py
index 73345107aef..3bc2412a90b 100644
--- a/addons/mail/tests/test_mail_template.py
+++ b/addons/mail/tests/test_mail_template.py
@@ -57,9 +57,25 @@ class TestMailTemplate(MailCommon):
mail_template.with_user(self.user_admin).name = 'New name'
self.assertEqual(mail_template.name, 'New name')
- # Standard employee can not
+ # Standard employee can create and edit non-dynamic templates
+ employee_template = self.env['mail.template'].with_user(self.user_employee).create({'body_html': '