[FIX] mass_mailing: improve opened traces tracking
Purpose of this commit is to improve tracking of opened traces. Notably a token is added to ensure we do not mess with traces and have unique tracking URLs. MIGRATION REMARK Emails sent before the migration will not be marked as opened anymore after migration. We recommend to avoid sending statistically important mass mailings about one week before migrating database. Indeed statistics show that most of open emails happen within the first week after being sent. Task 2223146 closes odoo/odoo#49139 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
committed by
Thibault Delavallée
parent
59265847aa
commit
1c7c837a10
@@ -7,6 +7,7 @@ import werkzeug
|
||||
from odoo import _, exceptions, http, tools
|
||||
from odoo.http import request
|
||||
from odoo.tools import consteq
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
|
||||
class MassMailController(http.Controller):
|
||||
@@ -91,9 +92,12 @@ class MassMailController(http.Controller):
|
||||
return True
|
||||
return 'error'
|
||||
|
||||
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='public')
|
||||
def track_mail_open(self, mail_id, **post):
|
||||
@http.route('/mail/track/<int:mail_id>/<string:token>/blank.gif', type='http', auth='public')
|
||||
def track_mail_open(self, mail_id, token, **post):
|
||||
""" Email tracking. """
|
||||
if not consteq(token, tools.hmac(request.env(su=True), 'mass_mailing-mail_mail-open', mail_id)):
|
||||
raise BadRequest()
|
||||
|
||||
request.env['mailing.trace'].sudo().set_opened(mail_mail_ids=[mail_id])
|
||||
response = werkzeug.wrappers.Response()
|
||||
response.mimetype = 'image/gif'
|
||||
|
||||
@@ -26,8 +26,8 @@ class MailMail(models.Model):
|
||||
|
||||
def _get_tracking_url(self):
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
track_url = werkzeug.urls.url_join(base_url, 'mail/track/%s/blank.gif' % self.id)
|
||||
return '<img src="%s" alt=""/>' % track_url
|
||||
token = tools.hmac(self.env(su=True), 'mass_mailing-mail_mail-open', self.id)
|
||||
return werkzeug.urls.url_join(base_url, 'mail/track/%s/%s/blank.gif' % (self.id, token))
|
||||
|
||||
def _get_unsubscribe_url(self, email_to):
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
@@ -64,8 +64,11 @@ class MailMail(models.Model):
|
||||
|
||||
# generate tracking URL
|
||||
tracking_url = self._get_tracking_url()
|
||||
if tracking_url:
|
||||
body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div')
|
||||
body = tools.append_content_to_html(
|
||||
body,
|
||||
'<img src="%s"/>' % tracking_url,
|
||||
plaintext=False,
|
||||
)
|
||||
|
||||
body = self.env['mail.render.mixin']._replace_local_links(body)
|
||||
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
from . import common
|
||||
from . import test_mailing_internals
|
||||
from . import test_mailing_list
|
||||
from . import test_mailing_controllers
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import werkzeug
|
||||
|
||||
from odoo.addons.mass_mailing.tests.common import TestMassMailCommon
|
||||
from odoo.tests.common import HttpCase
|
||||
|
||||
|
||||
class TestMassMailingControllers(TestMassMailCommon, HttpCase):
|
||||
|
||||
def test_tracking_url_token(self):
|
||||
mail_mail = self.env['mail.mail'].create({})
|
||||
|
||||
response = self.url_open(mail_mail._get_tracking_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
url = werkzeug.urls.url_join(base_url, 'mail/track/%s/fake_token/blank.gif' % mail_mail.id)
|
||||
|
||||
response = self.url_open(url)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
@@ -8,6 +8,8 @@ Miscellaneous tools used by OpenERP.
|
||||
import cProfile
|
||||
import collections
|
||||
import datetime
|
||||
import hmac as hmac_lib
|
||||
import hashlib
|
||||
import io
|
||||
import os
|
||||
import pickle as pickle_
|
||||
@@ -1530,3 +1532,24 @@ def traverse_containers(val, type_):
|
||||
elif isinstance(val, collections.abc.Sequence):
|
||||
for v in val:
|
||||
yield from traverse_containers(v, type_)
|
||||
|
||||
|
||||
def hmac(env, scope, message, hash_function=hashlib.sha256):
|
||||
"""Compute HMAC with `database.secret` config parameter as key.
|
||||
|
||||
:param env: sudo environment to use for retrieving config parameter
|
||||
:param message: message to authenticate
|
||||
:param scope: scope of the authentication, to have different signature for the same
|
||||
message in different usage
|
||||
:param hash_function: hash function to use for HMAC (default: SHA-256)
|
||||
"""
|
||||
if not scope:
|
||||
raise ValueError('Non-empty scope required')
|
||||
|
||||
secret = env['ir.config_parameter'].get_param('database.secret')
|
||||
message = repr((scope, message))
|
||||
return hmac_lib.new(
|
||||
secret.encode(),
|
||||
message.encode(),
|
||||
hash_function,
|
||||
).hexdigest()
|
||||
|
||||
Reference in New Issue
Block a user