[MERGE][REF] link_tracker: strengthen ACLs and add tests
[REF] link_tracker: strengthen ACLs and add tests Purpose of this merge is to clean ACLs of link tracker models. Those are rather technical and should not be accessed as it is by external people. Let us delegate control to controllers and lessen a bit model accessibility. Some code is cleaned and code field is made required on link.tracker.code model to ensure database integritry. See commits for more details. This merge is linked to task ID 1904277 and closes PR #28242.
This commit is contained in:
@@ -8,9 +8,14 @@ from odoo.http import request
|
||||
|
||||
|
||||
class LinkTracker(http.Controller):
|
||||
|
||||
@http.route('/r/<string:code>', type='http', auth='none', website=True)
|
||||
def full_url_redirect(self, code, **post):
|
||||
country_code = request.session.geoip and request.session.geoip.get('country_code') or False
|
||||
request.env['link.tracker.click'].add_click(code, request.httprequest.remote_addr, country_code, stat_id=False)
|
||||
request.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip=request.httprequest.remote_addr,
|
||||
country_code=country_code
|
||||
)
|
||||
redirect_url = request.env['link.tracker'].get_url_from_code(code)
|
||||
return werkzeug.utils.redirect(redirect_url or '', 301)
|
||||
|
||||
@@ -194,7 +194,7 @@ class LinkTracker(models.Model):
|
||||
return {'Error': "This filter doesn't exist."}
|
||||
|
||||
@api.model
|
||||
def get_url_from_code(self, code, context=None):
|
||||
def get_url_from_code(self, code):
|
||||
code_rec = self.env['link.tracker.code'].sudo().search([('code', '=', code)])
|
||||
|
||||
if not code_rec:
|
||||
@@ -211,7 +211,7 @@ class LinkTrackerCode(models.Model):
|
||||
_name = "link.tracker.code"
|
||||
_description = "Link Tracker Code"
|
||||
|
||||
code = fields.Char(string='Short URL Code', store=True)
|
||||
code = fields.Char(string='Short URL Code', required=True, store=True)
|
||||
link_id = fields.Many2one('link.tracker', 'Link', required=True, ondelete='cascade')
|
||||
|
||||
_sql_constraints = [
|
||||
@@ -236,36 +236,28 @@ class LinkTrackerClick(models.Model):
|
||||
_description = "Link Tracker Click"
|
||||
|
||||
link_id = fields.Many2one('link.tracker', 'Link', required=True, ondelete='cascade')
|
||||
click_date = fields.Date(string='Create Date')
|
||||
ip = fields.Char(string='Internet Protocol')
|
||||
country_id = fields.Many2one('res.country', 'Country')
|
||||
|
||||
@api.model
|
||||
def add_click(self, code, ip, country_code, stat_id=False):
|
||||
self = self.sudo()
|
||||
code_rec = self.env['link.tracker.code'].search([('code', '=', code)])
|
||||
def _prepare_click_values_from_route(self, **route_values):
|
||||
click_values = dict((fname, route_values[fname]) for fname in self._fields if fname in route_values)
|
||||
if not click_values.get('country_id') and route_values.get('country_code'):
|
||||
click_values['country_id'] = self.env['res.country'].search([('code', '=', route_values['country_code'])], limit=1).id
|
||||
return click_values
|
||||
|
||||
if not code_rec:
|
||||
@api.model
|
||||
def add_click(self, code, **route_values):
|
||||
""" Main API to add a click on a link. """
|
||||
tracker_code = self.env['link.tracker.code'].search([('code', '=', code)])
|
||||
if not tracker_code:
|
||||
return None
|
||||
|
||||
again = self.search_count([('link_id', '=', code_rec.link_id.id), ('ip', '=', ip)])
|
||||
ip = route_values.get('ip', False)
|
||||
existing = self.search_count(['&', ('link_id', '=', tracker_code.link_id.id), ('ip', '=', ip)])
|
||||
if existing:
|
||||
return None
|
||||
|
||||
if not again:
|
||||
self.create(
|
||||
self._get_click_values_from_route(dict(
|
||||
code=code,
|
||||
ip=ip,
|
||||
country_code=country_code,
|
||||
stat_id=stat_id,
|
||||
)))
|
||||
route_values['link_id'] = tracker_code.link_id.id
|
||||
click_values = self._prepare_click_values_from_route(**route_values)
|
||||
|
||||
def _get_click_values_from_route(self, route_values):
|
||||
code = self.env['link.tracker.code'].search([('code', '=', route_values['code'])], limit=1)
|
||||
country = self.env['res.country'].search([('code', '=', route_values['country_code'])], limit=1)
|
||||
|
||||
return {
|
||||
'link_id': code.link_id.id,
|
||||
'create_date': datetime.date.today(),
|
||||
'ip': route_values['ip'],
|
||||
'country_id': country.id,
|
||||
}
|
||||
return self.create(click_values)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_link_tracker_user,link.tracker,model_link_tracker,base.group_user,1,0,0,0
|
||||
access_link_tracker_public,link.tracker,model_link_tracker,base.group_public,1,0,0,0
|
||||
access_link_tracker_code_user,link.tracker.code,model_link_tracker_code,base.group_user,1,0,0,0
|
||||
access_link_tracker_code_public,link.tracker.code,model_link_tracker_code,base.group_public,1,0,0,0
|
||||
access_link_tracker_click_user,link.tracker.click,model_link_tracker_click,base.group_user,1,0,0,0
|
||||
access_link_tracker_click_public,link.tracker.click,model_link_tracker_click,base.group_public,1,0,0,0
|
||||
access_link_tracker_user,access.link.tracker.user,model_link_tracker,base.group_user,1,0,0,0
|
||||
access_link_tracker_public,access.link.tracker.public,model_link_tracker,base.group_public,0,0,0,0
|
||||
access_link_tracker_system,access.link.tracker.system,model_link_tracker,base.group_system,1,1,1,1
|
||||
access_link_tracker_code_user,access.link.tracker.code.user,model_link_tracker_code,base.group_user,1,0,0,0
|
||||
access_link_tracker_code_public,access.link.tracker.code.public,model_link_tracker_code,base.group_public,0,0,0,0
|
||||
access_link_tracker_code_system,access.link.tracker.code.system,model_link_tracker_code,base.group_system,1,1,1,1
|
||||
access_link_tracker_click_user,access.link.tracker.click.user,model_link_tracker_click,base.group_user,1,0,0,0
|
||||
access_link_tracker_click_public,access.link.tracker.click.public,model_link_tracker_click,base.group_public,0,0,0,0
|
||||
access_link_tracker_click_system,access.link.tracker.click.system,model_link_tracker_click,base.group_system,1,1,1,1
|
||||
|
||||
|
@@ -107,7 +107,12 @@ class MassMailController(http.Controller):
|
||||
# which mass_mailing doesn't depend on
|
||||
country_code = request.session.get('geoip', False) and request.session.geoip.get('country_code', False)
|
||||
|
||||
request.env['link.tracker.click'].add_click(code, request.httprequest.remote_addr, country_code, stat_id=stat_id)
|
||||
request.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip=request.httprequest.remote_addr,
|
||||
country_code=country_code,
|
||||
mail_stat_id=stat_id
|
||||
)
|
||||
return werkzeug.utils.redirect(request.env['link.tracker'].get_url_from_code(code), 301)
|
||||
|
||||
@http.route('/mailing/blacklist/check', type='json', auth='none')
|
||||
|
||||
@@ -210,49 +210,37 @@
|
||||
|
||||
<!-- Generate some clicks -->
|
||||
<function model="link.tracker.click" name="add_click">
|
||||
<!-- code, ip, country_code, stat_id -->
|
||||
<value model="link.tracker.code"
|
||||
search="[('link_id.url', '=', 'http://www.example.com')]"
|
||||
use="code"/>
|
||||
<value eval="'100.01.02.03'"/>
|
||||
<value eval="'BE'"/>
|
||||
<value eval="ref('mass_mail_1_stat_0')"/>
|
||||
<value eval="{'ip': '100.01.02.03', 'country_code': 'BE', 'mail_stat_id': ref('mass_mail_1_stat_0')}"/>
|
||||
</function>
|
||||
<function model="link.tracker.click" name="add_click">
|
||||
<!-- code, ip, country_code, stat_id -->
|
||||
<value model="link.tracker.code"
|
||||
search="[('link_id.url', '=', 'http://www.example.net/page/contactus')]"
|
||||
use="code"/>
|
||||
<value eval="'100.01.02.03'"/>
|
||||
<value eval="'BE'"/>
|
||||
<value eval="ref('mass_mail_1_stat_0')"/>
|
||||
<value eval="{'ip': '100.01.02.03', 'country_code': 'BE', 'mail_stat_id': ref('mass_mail_1_stat_0')}"/>
|
||||
</function>
|
||||
<function model="link.tracker.click" name="add_click">
|
||||
<!-- code, ip, country_code, stat_id -->
|
||||
<value model="link.tracker.code"
|
||||
search="[('link_id.url', '=', 'http://www.example.com')]"
|
||||
use="code"/>
|
||||
<value eval="'100.01.02.04'"/>
|
||||
<value eval="'BE'"/>
|
||||
<value eval="ref('mass_mail_1_stat_1')"/>
|
||||
<value eval="{'ip': '100.01.02.04', 'country_code': 'BE', 'mail_stat_id': ref('mass_mail_1_stat_1')}"/>
|
||||
</function>
|
||||
<function model="link.tracker.click" name="add_click">
|
||||
<!-- code, ip, country_code, stat_id -->
|
||||
<value model="link.tracker.code"
|
||||
search="[('link_id.url', '=', 'http://www.example.net/page/contactus')]"
|
||||
use="code"/>
|
||||
<value eval="{'ip': '100.01.02.04', 'country_code': 'BE', 'mail_stat_id': ref('mass_mail_1_stat_0')}"/>
|
||||
<value eval="'100.01.02.04'"/>
|
||||
<value eval="'BE'"/>
|
||||
<value eval="ref('mass_mail_1_stat_0')"/>
|
||||
</function>
|
||||
<function model="link.tracker.click" name="add_click">
|
||||
<!-- code, ip, country_code, stat_id -->
|
||||
<value model="link.tracker.code"
|
||||
search="[('link_id.url', '=', 'http://www.example.com')]"
|
||||
use="code"/>
|
||||
<value eval="'100.01.02.05'"/>
|
||||
<value eval="'BE'"/>
|
||||
<value eval="ref('mass_mail_1_stat_2')"/>
|
||||
<value eval="{'ip': '100.01.02.05', 'country_code': 'BE', 'mail_stat_id': ref('mass_mail_1_stat_2')}"/>
|
||||
</function>
|
||||
|
||||
</data>
|
||||
|
||||
@@ -18,22 +18,27 @@ class LinkTrackerClick(models.Model):
|
||||
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mass Mailing')
|
||||
mass_mailing_campaign_id = fields.Many2one('mail.mass_mailing.campaign', string='Mass Mailing Campaign')
|
||||
|
||||
@api.model
|
||||
def add_click(self, code, ip, country_code, stat_id=False):
|
||||
res = super(LinkTrackerClick, self).add_click(code, ip, country_code, stat_id=stat_id)
|
||||
if stat_id:
|
||||
stat_sudo = self.env['mail.mail.statistics'].sudo().browse(stat_id)
|
||||
stat_sudo.set_opened()
|
||||
stat_sudo.set_clicked()
|
||||
return res
|
||||
def _prepare_click_values_from_route(self, **route_values):
|
||||
click_values = super(LinkTrackerClick, self)._prepare_click_values_from_route(**route_values)
|
||||
|
||||
if click_values.get('mail_stat_id'):
|
||||
stat_sudo = self.env['mail.mail.statistics'].sudo().browse(route_values['mail_stat_id']).exists()
|
||||
if not stat_sudo:
|
||||
click_values['mail_stat_id'] = False
|
||||
else:
|
||||
if not click_values.get('mass_mailing_campaign_id'):
|
||||
click_values['mass_mailing_campaign_id'] = stat_sudo.mass_mailing_campaign_id.id
|
||||
if not click_values.get('mass_mailing_id'):
|
||||
click_values['mass_mailing_id'] = stat_sudo.mass_mailing_id.id
|
||||
|
||||
def _get_click_values_from_route(self, route_values):
|
||||
click_values = super(LinkTrackerClick, self)._get_click_values_from_route(route_values)
|
||||
if route_values['stat_id']:
|
||||
mail_stat = self.env['mail.mail.statistics'].browse(route_values['stat_id'])
|
||||
click_values['mail_stat_id'] = mail_stat.id
|
||||
if mail_stat.mass_mailing_campaign_id:
|
||||
click_values['mass_mailing_campaign_id'] = mail_stat.mass_mailing_campaign_id.id
|
||||
if mail_stat.mass_mailing_id:
|
||||
click_values['mass_mailing_id'] = mail_stat.mass_mailing_id.id
|
||||
return click_values
|
||||
|
||||
@api.model
|
||||
def add_click(self, code, **route_values):
|
||||
click = super(LinkTrackerClick, self).add_click(code, **route_values)
|
||||
|
||||
if click and click.mail_stat_id:
|
||||
click.mail_stat_id.set_opened()
|
||||
click.mail_stat_id.set_clicked()
|
||||
|
||||
return click
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from . import test_blacklist
|
||||
from . import test_blacklist_mixin
|
||||
from . import test_composer
|
||||
from . import test_link
|
||||
from . import test_mail_auto_blacklist
|
||||
from . import test_mail_channel
|
||||
from . import test_mass_mailing
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import users
|
||||
from odoo.addons.test_mass_mailing.tests import common
|
||||
|
||||
|
||||
class TestLinkTracker(common.MassMailingCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLinkTracker, self).setUp()
|
||||
|
||||
self.link = self.env['link.tracker'].create({
|
||||
'url': 'https://www.example.com'
|
||||
})
|
||||
|
||||
self.click = self.env['link.tracker.click'].create({
|
||||
'link_id': self.link.id,
|
||||
'ip': '100.00.00.00',
|
||||
'country_id': self.env.ref('base.fr').id,
|
||||
})
|
||||
|
||||
def test_add_link(self):
|
||||
code = self.link.code
|
||||
self.assertEqual(self.link.count, 1)
|
||||
|
||||
# click from a new IP should create a new entry
|
||||
click = self.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip='100.00.00.01',
|
||||
country_code='BEL'
|
||||
)
|
||||
self.assertEqual(click.ip, '100.00.00.01')
|
||||
self.assertEqual(click.country_id, self.env.ref('base.be'))
|
||||
self.assertEqual(self.link.count, 2)
|
||||
|
||||
# click from same IP (even another country) does not create a new entry
|
||||
click = self.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip='100.00.00.01',
|
||||
country_code='FRA'
|
||||
)
|
||||
self.assertEqual(click, None)
|
||||
self.assertEqual(self.link.count, 2)
|
||||
|
||||
@users('marketing')
|
||||
def test_add_link_mail_stat(self):
|
||||
mailing = self.env['mail.mass_mailing'].create({'name': 'Test Mailing'})
|
||||
code = self.link.code
|
||||
self.assertEqual(self.link.count, 1)
|
||||
stat = self.env['mail.mail.statistics'].create({'mass_mailing_id': mailing.id})
|
||||
self.assertFalse(stat.opened)
|
||||
self.assertFalse(stat.clicked)
|
||||
|
||||
# click from a new IP should create a new entry and update stat when provided
|
||||
click = self.env['link.tracker.click'].sudo().add_click(
|
||||
code,
|
||||
ip='100.00.00.01',
|
||||
country_code='BEL',
|
||||
mail_stat_id=stat.id
|
||||
)
|
||||
self.assertEqual(self.link.count, 2)
|
||||
self.assertEqual(click.mass_mailing_id, mailing)
|
||||
self.assertTrue(stat.opened)
|
||||
self.assertTrue(stat.clicked)
|
||||
Reference in New Issue
Block a user