[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:
Robot Odoo
2019-01-14 15:00:23 +01:00
committed by GitHub
8 changed files with 133 additions and 69 deletions
+6 -1
View File
@@ -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)
+19 -27
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_link_tracker_user link.tracker access.link.tracker.user model_link_tracker base.group_user 1 0 0 0
3 access_link_tracker_public link.tracker access.link.tracker.public model_link_tracker base.group_public 1 0 0 0 0
4 access_link_tracker_code_user access_link_tracker_system link.tracker.code access.link.tracker.system model_link_tracker_code model_link_tracker base.group_user base.group_system 1 0 1 0 1 0 1
5 access_link_tracker_code_public access_link_tracker_code_user link.tracker.code access.link.tracker.code.user model_link_tracker_code base.group_public base.group_user 1 0 0 0
6 access_link_tracker_click_user access_link_tracker_code_public link.tracker.click access.link.tracker.code.public model_link_tracker_click model_link_tracker_code base.group_user base.group_public 1 0 0 0 0
7 access_link_tracker_click_public access_link_tracker_code_system link.tracker.click access.link.tracker.code.system model_link_tracker_click model_link_tracker_code base.group_public base.group_system 1 0 1 0 1 0 1
8 access_link_tracker_click_user access.link.tracker.click.user model_link_tracker_click base.group_user 1 0 0 0
9 access_link_tracker_click_public access.link.tracker.click.public model_link_tracker_click base.group_public 0 0 0 0
10 access_link_tracker_click_system access.link.tracker.click.system model_link_tracker_click base.group_system 1 1 1 1
+6 -1
View File
@@ -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')
+5 -17
View File
@@ -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>
+22 -17
View File
@@ -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)