From 78ac6de52d690d278fd0cde5476bbeaec86cb124 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= <tde@odoo.com>
Date: Mon, 20 Mar 2017 16:11:47 +0100
Subject: [PATCH] [IMP] mail: factorize code that handles alias security in
 mail gateway

Currently check of alias security is directly embedded in mail.thread
routing method. In this commit we move this check in the mail.alias.mixin
class itself. Doing this allows models to extend or improve the behavior
depending on how inheriting models use aliases.
---
 addons/hr/models/__init__.py      |  1 -
 addons/hr/models/mail_alias.py    | 21 ++++++++++-
 addons/hr/models/mail_thread.py   | 62 +++++++++++--------------------
 addons/mail/models/mail_alias.py  | 18 +++++++++
 addons/mail/models/mail_thread.py | 26 ++++++-------
 5 files changed, 70 insertions(+), 58 deletions(-)

diff --git a/addons/hr/models/__init__.py b/addons/hr/models/__init__.py
index 33a14fc63171..e396b616d67c 100644
--- a/addons/hr/models/__init__.py
+++ b/addons/hr/models/__init__.py
@@ -4,6 +4,5 @@
 import hr
 import hr_config_settings
 import mail_alias
-import mail_thread
 import res_partner
 import res_users
diff --git a/addons/hr/models/mail_alias.py b/addons/hr/models/mail_alias.py
index 710b7efaff7a..a88a6a4530fa 100644
--- a/addons/hr/models/mail_alias.py
+++ b/addons/hr/models/mail_alias.py
@@ -1,10 +1,29 @@
 # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 
-from odoo import fields, models
+from odoo import fields, models, tools
 
 
 class Alias(models.Model):
     _inherit = 'mail.alias'
 
     alias_contact = fields.Selection(selection_add=[('employees', 'Authenticated Employees')])
+
+
+class MailAlias(models.AbstractModel):
+    _inherit = 'mail.alias.mixin'
+
+    def _alias_check_contact(self, message, message_dict, alias):
+        if alias.alias_contact == 'employees' and self.ids:
+            email_from = tools.decode_message_header(message, 'From')
+            email_address = tools.email_split(email_from)[0]
+            employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)
+            if not employee:
+                employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1)
+            if not employee:
+                return {
+                    'error_message': 'restricted to employees',
+                    'error_template': self.env.ref('hr.mail_template_data_unknown_employee_email_address').body_html,
+                }
+            return True
+        return super(MailAlias, self)._alias_check_contact(message, message_dict, alias)
diff --git a/addons/hr/models/mail_thread.py b/addons/hr/models/mail_thread.py
index 06eec55240d6..25ee48bcd6d8 100644
--- a/addons/hr/models/mail_thread.py
+++ b/addons/hr/models/mail_thread.py
@@ -1,44 +1,24 @@
 # -*- coding: utf-8 -*-
 # Part of Odoo. See LICENSE file for full copyright and licensing details.
 
-import logging
-
-from odoo import api, models, _
-from odoo.tools import decode_message_header, email_split
-
-_logger = logging.getLogger(__name__)
-
-
-class MailThread(models.AbstractModel):
-    _inherit = "mail.thread"
-
-    @api.model
-    def message_route_verify(self, message, message_dict, route,
-                             update_author=True, assert_model=True,
-                             create_fallback=True, allow_private=False,
-                             drop_alias=False):
-        res = super(MailThread, self).message_route_verify(
-            message, message_dict, route,
-            update_author=update_author,
-            assert_model=assert_model,
-            create_fallback=create_fallback,
-            allow_private=allow_private,
-            drop_alias=drop_alias)
-
-        if res:
-            alias = route[4]
-            email_from = decode_message_header(message, 'From')
-            message_id = message.get('Message-Id')
-
-            # Alias: check alias_contact settings for employees
-            if alias and alias.alias_contact == 'employees':
-                email_address = email_split(email_from)[0]
-                employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)
-                if not employee:
-                    employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1)
-                if not employee:
-                    mail_template = self.env.ref('hr.mail_template_data_unknown_employee_email_address')
-                    self._routing_warn(_('alias %s does not accept unknown employees') % alias.alias_name, _('skipping'), message_id, route, False)
-                    self._routing_create_bounce_email(email_from, mail_template.body_html, message)
-                    return False
-        return res
+from odoo import models, tools
+from odoo.tools import email_split
+
+
+class MailAlias(models.AbstractModel):
+    _inherit = 'mail.alias.mixin'
+
+    def _alias_check_contact(self, message, message_dict, alias):
+        if alias.alias_contact == 'employees' and self.ids:
+            email_from = tools.decode_message_header(message, 'From')
+            email_address = email_split(email_from)[0]
+            employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1)
+            if not employee:
+                employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1)
+            if not employee:
+                return {
+                    'error_message': 'restricted to employees',
+                    'error_template': self.env.ref('hr.mail_template_data_unknown_employee_email_address').body_html,
+                }
+            return True
+        return super(MailAlias, self)._alias_check_contact(message, message_dict, alias)
diff --git a/addons/mail/models/mail_alias.py b/addons/mail/models/mail_alias.py
index 67233efcd77c..df0d84062e71 100644
--- a/addons/mail/models/mail_alias.py
+++ b/addons/mail/models/mail_alias.py
@@ -262,3 +262,21 @@ class AliasMixin(models.AbstractModel):
             record.with_context({'mail_notrack': True}).alias_id = alias
             _logger.info('Mail alias created for %s %s (id %s)',
                          record._name, record.display_name, record.id)
+
+    def _alias_check_contact(self, message, message_dict, alias):
+        author = self.env['res.partner'].browse(message_dict.get('author_id', False))
+        if alias.alias_contact == 'followers' and self.ids:
+            if not hasattr(self, "message_partner_ids") or not hasattr(self, "message_channel_ids"):
+                return {
+                    'error_mesage': _('incorrectly configured alias'),
+                }
+            accepted_partner_ids = self.message_partner_ids | self.message_channel_ids.mapped('channel_partner_ids')
+            if not author or author not in accepted_partner_ids:
+                return {
+                    'error_mesage': _('restricted to followers'),
+                }
+        elif alias.alias_contact == 'partners' and not author:
+            return {
+                'error_message': _('restricted to known authors')
+            }
+        return True
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 4e8a1ada142d..baffc4fc1748 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -944,27 +944,23 @@ class MailThread(models.AbstractModel):
         if not author_id and update_author:
             author_ids = self.env['mail.thread']._find_partner_from_emails([email_from], res_model=model, res_id=thread_id)
             if author_ids:
-                author_id = author_ids[0]
-                message_dict['author_id'] = author_id
+                message_dict['author_id'] = author_ids[0]
 
         # Alias: check alias_contact settings
-        if alias and alias.alias_contact == 'followers' and (thread_id or alias.alias_parent_thread_id):
+        if alias:
             if thread_id:
                 obj = record_set[0]
-            else:
+            elif alias.alias_parent_thread_id:
                 obj = self.env[alias.alias_parent_model_id.model].browse(alias.alias_parent_thread_id)
-            accepted_partner_ids = list(
-                set(partner.id for partner in obj.message_partner_ids) |
-                set(partner.id for channel in obj.message_channel_ids for partner in channel.channel_partner_ids)
-            )
-            if not author_id or author_id not in accepted_partner_ids:
-                self._routing_warn(_('alias %s restricted to internal followers') % alias.alias_name, _('skipping'), message_id, route, False)
-                self._routing_create_bounce_email(email_from, _generic_bounce_body_html, message)
+            elif model and hasattr(record_set, '_alias_check_contact'):
+                obj = self.env[model]
+            else:
+                obj = self.env['mail.alias.mixin']
+            check_result = obj._alias_check_contact(message, message_dict, alias)
+            if check_result is not True:
+                self._routing_warn(_('alias %s: %s') % (alias.alias_name, check_result.get('error_message', _('unknown error'))), _('skipping'), message_id, route, False)
+                self._routing_create_bounce_email(email_from, check_result.get('error_template', _generic_bounce_body_html), message)
                 return False
-        elif alias and alias.alias_contact == 'partners' and not author_id:
-            self._routing_warn(_('alias %s does not accept unknown author') % alias.alias_name, _('skipping'), message_id, route, False)
-            self._routing_create_bounce_email(email_from, _generic_bounce_body_html, message)
-            return False
 
         if not model and not thread_id and not alias and not allow_private:
             return False
-- 
GitLab