[FIX] base: keep evaluation context unchanged

Before this commit, the evaluation context was forced with sudo(False)
as the call to _render_qweb_html was made with sudo(True). The sudo
was removed to prevent rendering a report on any object.

This was problematic for reports such as sale.order portal page where
the public user does not have access to the record but uses a custom
verification mechanism using an access_token field and then calling
_render with sudo.

This commit moves the sudo call to keep the _get_evaluation_context
with the same environment and allow to call _render with sudo

Add tests with sale portal building the report as low-priviledge user

closes odoo/odoo#56890

X-original-commit: 57665a02f7b4cd113f8f40ec47c58a518fe8669c
Related: odoo/enterprise#12845
Signed-off-by: Martin Trigaux (mat) <mat@odoo.com>
This commit is contained in:
Martin Trigaux
2020-09-01 12:50:44 +00:00
parent a108987c7c
commit ba693a7e02
2 changed files with 67 additions and 11 deletions
+57 -1
View File
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.sale.tests.common import TestSaleCommon
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tests import tagged
from odoo.tests import HttpCase, tagged
@tagged('post_install', '-at_install')
@@ -122,3 +122,59 @@ class TestAccessRights(TestSaleCommon):
# Employee can't delete the SO
with self.assertRaises(AccessError):
self.order.with_user(self.company_data['default_user_employee']).unlink()
@tagged('post_install', '-at_install')
class TestAccessRightsControllers(HttpCase):
def test_access_controller(self):
portal_so = self.env.ref("sale.portal_sale_order_2").sudo()
portal_so._portal_ensure_token()
token = portal_so.access_token
private_so = self.env.ref("sale.sale_order_1")
self.authenticate(None, None)
# Test public user can't print an order without a token
req = self.url_open(
url='/my/orders/%s?report_type=pdf' % portal_so.id,
allow_redirects=False,
)
self.assertEqual(req.status_code, 302)
# or with a random token
req = self.url_open(
url='/my/orders/%s?access_token=%s&report_type=pdf' % (
portal_so.id,
"foo",
),
allow_redirects=False,
)
self.assertEqual(req.status_code, 302)
# but works fine with the right token
req = self.url_open(
url='/my/orders/%s?access_token=%s&report_type=pdf' % (
portal_so.id,
token,
),
allow_redirects=False,
)
self.assertEqual(req.status_code, 200)
self.authenticate("portal", "portal")
# do not need the token when logged in
req = self.url_open(
url='/my/orders/%s?report_type=pdf' % portal_so.id,
allow_redirects=False,
)
self.assertEqual(req.status_code, 200)
# but still can't access another order
req = self.url_open(
url='/my/orders/%s?report_type=pdf' % private_so.id,
allow_redirects=False,
)
self.assertEqual(req.status_code, 302)
+10 -10
View File
@@ -694,13 +694,13 @@ class IrActionsReport(models.Model):
data = {}
data.setdefault('report_type', 'pdf')
# access the report details with sudo() but evaluation context as sudo(False)
# access the report details with sudo() but evaluation context as current user
self_sudo = self.sudo()
# In case of test environment without enough workers to perform calls to wkhtmltopdf,
# fallback to render_html.
if (tools.config['test_enable'] or tools.config['test_file']) and not self.env.context.get('force_report_rendering'):
return self_sudo._render_qweb_html(res_ids, data=data)
return self._render_qweb_html(res_ids, data=data)
# As the assets are generated during the same transaction as the rendering of the
# templates calling them, there is a scenario where the assets are unreachable: when
@@ -791,7 +791,7 @@ class IrActionsReport(models.Model):
data = {}
data.setdefault('report_type', 'text')
data = self._get_rendering_context(docids, data)
return self._render_template(self.report_name, data), 'text'
return self._render_template(self.sudo().report_name, data), 'text'
@api.model
def _render_qweb_html(self, docids, data=None):
@@ -801,7 +801,7 @@ class IrActionsReport(models.Model):
data = {}
data.setdefault('report_type', 'html')
data = self._get_rendering_context(docids, data)
return self._render_template(self.report_name, data), 'html'
return self._render_template(self.sudo().report_name, data), 'html'
@api.model
def _get_rendering_context_model(self):
@@ -810,23 +810,23 @@ class IrActionsReport(models.Model):
@api.model
def _get_rendering_context(self, docids, data):
# access the report details with sudo() but evaluation context as current user
self_sudo = self.sudo()
# If the report is using a custom model to render its html, we must use it.
# Otherwise, fallback on the generic html rendering.
report_model = self._get_rendering_context_model()
report_model = self_sudo._get_rendering_context_model()
data = data and dict(data) or {}
if report_model is not None:
# _render_ may be executed in sudo but evaluation context as real user
report_model = report_model.sudo(False)
data.update(report_model._get_report_values(docids, data=data))
else:
# _render_ may be executed in sudo but evaluation context as real user
docs = self.env[self.model].sudo(False).browse(docids)
docs = self.env[self_sudo.model].browse(docids)
data.update({
'doc_ids': docids,
'doc_model': self.model,
'doc_model': self_sudo.model,
'docs': docs,
})
return data