[IMP] base : improve messages text in access_error pop-ups

* Improve layout of access error messages from group restriction
 * Improve layout of access error messages from security records rules
 * Use the error title in the js crash manager when possible
 * adapt unit tests

closes odoo/odoo#50369

Taskid: 2206787
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Sébastien Geelen (sge)
2020-06-30 13:59:23 +00:00
parent d24758db56
commit 6d93c72564
5 changed files with 156 additions and 69 deletions
@@ -140,7 +140,7 @@ class TestUi(tests.HttpCase):
post_data['attachment_tokens'] = attachment.access_token
res = self.url_open(url=post_url, data=post_data)
self.assertEqual(res.status_code, 403)
self.assertIn("Sorry, you are not allowed to access documents of type 'Journal Entry' (account.move).", res.text)
self.assertIn("You are not allowed to access 'Journal Entry' (account.move) records.", res.text)
# Test attachment can't be associated if not "pending" state
post_data['token'] = invoice._portal_ensure_token()
@@ -222,7 +222,12 @@ var CrashManager = AbstractService.extend({
return;
}
var message = error.data ? error.data.message : error.message;
var title = _.str.capitalize(error.type) || _t("Something went wrong !");
var title = _t("Something went wrong !");
if (error.type) {
title = _.str.capitalize(error.type);
} else if (error.data && error.data.title) {
title = _.str.capitalize(error.data.title);
}
return this._displayWarning(message, title, options);
},
show_error: function (error) {
+22 -14
View File
@@ -1718,26 +1718,34 @@ class IrModelAccess(models.Model):
if not r and raise_exception:
groups = '\n'.join('\t- %s' % g for g in self.group_names_with_access(model, mode))
document_kind = self.env['ir.model']._get(model).name or model
msg_heads = {
# Messages are declared in extenso so they are properly exported in translation terms
'read': _("Sorry, you are not allowed to access documents of type '%(document_kind)s' (%(document_model)s)."),
'write': _("Sorry, you are not allowed to modify documents of type '%(document_kind)s' (%(document_model)s)."),
'create': _("Sorry, you are not allowed to create documents of type '%(document_kind)s' (%(document_model)s)."),
'unlink': _("Sorry, you are not allowed to delete documents of type '%(document_kind)s' (%(document_model)s)."),
}
msg_params = {
'document_kind': self.env['ir.model']._get(model).name or model,
'document_model': model,
'read': _("You are not allowed to access '%(document_kind)s' (%(document_model)s) records.", document_kind=document_kind, document_model=model),
'write': _("You are not allowed to modify '%(document_kind)s' (%(document_model)s) records.", document_kind=document_kind, document_model=model),
'create': _("You are not allowed to create '%(document_kind)s' (%(document_model)s) records.", document_kind=document_kind, document_model=model),
'unlink': _("You are not allowed to delete '%(document_kind)s' (%(document_model)s) records.", document_kind=document_kind, document_model=model),
}
operation_error = msg_heads[mode]
if groups:
msg_tail = _("This operation is allowed for the groups:\n%(groups_list)s")
msg_params['groups_list'] = groups
group_info = _("This operation is allowed for the following groups:\n%(groups_list)s", groups_list=groups)
else:
msg_tail = _("No group currently allows this operation.")
msg_tail += u' - ({} {}, {} {})'.format(_('Operation:'), mode, _('User:'), self._uid)
group_info = _("No group currently allows this operation.")
resolution_info = _("Contact your administrator to request access if necessary.")
_logger.info('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, self._uid, model)
msg = '%s %s' % (msg_heads[mode], msg_tail)
raise AccessError(msg % msg_params)
msg = """{operation_error}
{group_info}
{resolution_info}""".format(
operation_error=operation_error,
group_info=group_info,
resolution_info=resolution_info)
raise AccessError(msg)
return bool(r)
+47 -22
View File
@@ -220,36 +220,61 @@ class IrRule(models.Model):
model = records._name
description = self.env['ir.model']._get(model).name or model
if not self.env.user.has_group('base.group_no_one'):
return AccessError(_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: "%(document_kind)s" (%(document_model)s), Operation: %(operation)s)') % {
'document_kind': description,
'document_model': model,
'operation': operation,
})
msg_heads = {
# Messages are declared in extenso so they are properly exported in translation terms
'read': _("Due to security restrictions, you are not allowed to access '%(document_kind)s' (%(document_model)s) records.", document_kind=description, document_model=model),
'write': _("Due to security restrictions, you are not allowed to modify '%(document_kind)s' (%(document_model)s) records.", document_kind=description, document_model=model),
'create': _("Due to security restrictions, you are not allowed to create '%(document_kind)s' (%(document_model)s) records.", document_kind=description, document_model=model),
'unlink': _("Due to security restrictions, you are not allowed to delete '%(document_kind)s' (%(document_model)s) records.", document_kind=description, document_model=model)
}
operation_error = msg_heads[operation]
resolution_info = _("Contact your administrator to request access if necessary.")
if not self.env.user.has_group('base.group_no_one') or not self.env.user.has_group('base.group_user'):
msg = """{operation_error}
{resolution_info}""".format(
operation_error=operation_error,
resolution_info=resolution_info)
return AccessError(msg)
# This extended AccessError is only displayed in debug mode.
# Note that by default, public and portal users do not have
# the group "base.group_no_one", even if debug mode is enabled,
# so it is relatively safe here to include the list of rules and
# record names.
# so it is relatively safe here to include the list of rules and record names.
rules = self._get_failing(records, mode=operation).sudo()
error = AccessError(_("""The requested operation ("%(operation)s" on "%(document_kind)s" (%(document_model)s)) was rejected because of the following rules:
%(rules_list)s
%(multi_company_warning)s
(Records: %(example_records)s, User: %(user_id)s)""") % {
'operation': operation,
'document_kind': description,
'document_model': model,
'rules_list': '\n'.join('- %s' % rule.name for rule in rules),
'multi_company_warning': ('\n' + _('Note: this might be a multi-company issue.') + '\n') if any(
'company_id' in (r.domain_force or []) for r in rules) else '',
'example_records': ' - '.join(['%s (id=%s)' % (rec.display_name, rec.id) for rec in records[:6].sudo()]),
'user_id': '%s (id=%s)' % (self.env.user.name, self.env.user.id),
})
records_description = ', '.join(['%s (id=%s)' % (rec.display_name, rec.id) for rec in records[:6].sudo()])
failing_records = _("Records: %s", records_description)
user_description = '%s (id=%s)' % (self.env.user.name, self.env.user.id)
failing_user = _("User: %s", user_description)
rules_description = '\n'.join('- %s' % rule.name for rule in rules)
failing_rules = _("This restriction is due to the following rules:\n%s", rules_description)
if any('company_id' in (r.domain_force or []) for r in rules):
failing_rules += "\n\n" + _('Note: this might be a multi-company issue.')
msg = """{operation_error}
{failing_records}
{failing_user}
{failing_rules}
{resolution_info}""".format(
operation_error=operation_error,
failing_records=failing_records,
failing_user=failing_user,
failing_rules=failing_rules,
resolution_info=resolution_info)
# clean up the cache of records prefetched with display_name above
for record in records[:6]:
record._cache.clear()
return error
return AccessError(msg)
#
# Hack for field 'global': this field cannot be defined like others, because
@@ -117,7 +117,11 @@ class TestACLFeedback(Feedback):
self.record.with_user(self.user).write({'val': 10})
self.assertEqual(
ctx.exception.args[0],
"""Sorry, you are not allowed to modify documents of type 'Object For Test Access Right' (test_access_right.some_obj). No group currently allows this operation. - (Operation: write, User: %d)""" % self.user.id
"""You are not allowed to modify 'Object For Test Access Right' (test_access_right.some_obj) records.
No group currently allows this operation.
Contact your administrator to request access if necessary."""
)
def test_one_group(self):
@@ -127,12 +131,20 @@ class TestACLFeedback(Feedback):
})
self.assertEqual(
ctx.exception.args[0],
"""Sorry, you are not allowed to create documents of type 'Object For Test Access Right' (test_access_right.some_obj). This operation is allowed for the groups:\n\t- Group 0 - (Operation: create, User: %d)""" % self.user.id
"""You are not allowed to create 'Object For Test Access Right' (test_access_right.some_obj) records.
This operation is allowed for the following groups:\n\t- Group 0
Contact your administrator to request access if necessary."""
)
def test_two_groups(self):
r = self.record.with_user(self.user)
expected = """Sorry, you are not allowed to access documents of type 'Object For Test Access Right' (test_access_right.some_obj). This operation is allowed for the groups:\n\t- Group 0\n\t- Group 1 - (Operation: read, User: %d)""" % self.user.id
expected = """You are not allowed to access 'Object For Test Access Right' (test_access_right.some_obj) records.
This operation is allowed for the following groups:\n\t- Group 0\n\t- Group 1
Contact your administrator to request access if necessary."""
with self.assertRaises(AccessError) as ctx:
# noinspection PyStatementEffect
r.val
@@ -171,19 +183,26 @@ class TestIRRuleFeedback(Feedback):
self.record.write({'val': 1})
self.assertEqual(
ctx.exception.args[0],
'The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: "Object For Test Access Right" (test_access_right.some_obj), Operation: write)'
)
"""Due to security restrictions, you are not allowed to modify 'Object For Test Access Right' (test_access_right.some_obj) records.
Contact your administrator to request access if necessary.""")
# debug mode
self.env.ref('base.group_no_one').write({'users': [(4, self.user.id)]})
self.env.ref('base.group_user').write({'users': [(4, self.user.id)]})
with self.assertRaises(AccessError) as ctx:
self.record.write({'val': 1})
self.assertEqual(
ctx.exception.args[0],
"""The requested operation ("write" on "Object For Test Access Right" (test_access_right.some_obj)) was rejected because of the following rules:
"""Due to security restrictions, you are not allowed to modify 'Object For Test Access Right' (test_access_right.some_obj) records.
Records: %s (id=%s)
User: %s (id=%s)
This restriction is due to the following rules:
- rule 0
(Records: %s (id=%s), User: %s (id=%s))""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
Contact your administrator to request access if necessary.""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
)
@@ -196,58 +215,73 @@ class TestIRRuleFeedback(Feedback):
p.with_user(self.user).write({'val': 1})
def test_locals(self):
self.env.ref('base.group_no_one').write(
{'users': [(4, self.user.id)]})
self.env.ref('base.group_no_one').write({'users': [(4, self.user.id)]})
self.env.ref('base.group_user').write({'users': [(4, self.user.id)]})
self._make_rule('rule 0', '[("val", "=", 42)]')
self._make_rule('rule 1', '[("val", "=", 78)]')
with self.assertRaises(AccessError) as ctx:
self.record.write({'val': 1})
self.assertEqual(
ctx.exception.args[0],
"""The requested operation ("write" on "Object For Test Access Right" (test_access_right.some_obj)) was rejected because of the following rules:
"""Due to security restrictions, you are not allowed to modify 'Object For Test Access Right' (test_access_right.some_obj) records.
Records: %s (id=%s)
User: %s (id=%s)
This restriction is due to the following rules:
- rule 0
- rule 1
(Records: %s (id=%s), User: %s (id=%s))""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
Contact your administrator to request access if necessary.""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
)
def test_globals_all(self):
self.env.ref('base.group_no_one').write(
{'users': [(4, self.user.id)]})
self.env.ref('base.group_no_one').write({'users': [(4, self.user.id)]})
self.env.ref('base.group_user').write({'users': [(4, self.user.id)]})
self._make_rule('rule 0', '[("val", "=", 42)]', global_=True)
self._make_rule('rule 1', '[("val", "=", 78)]', global_=True)
with self.assertRaises(AccessError) as ctx:
self.record.write({'val': 1})
self.assertEqual(
ctx.exception.args[0],
"""The requested operation ("write" on "Object For Test Access Right" (test_access_right.some_obj)) was rejected because of the following rules:
"""Due to security restrictions, you are not allowed to modify 'Object For Test Access Right' (test_access_right.some_obj) records.
Records: %s (id=%s)
User: %s (id=%s)
This restriction is due to the following rules:
- rule 0
- rule 1
(Records: %s (id=%s), User: %s (id=%s))""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
Contact your administrator to request access if necessary.""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
)
def test_globals_any(self):
""" Global rules are AND-eded together, so when an access fails it
might be just one of the rules, and we want an exact listing
"""
self.env.ref('base.group_no_one').write(
{'users': [(4, self.user.id)]})
self.env.ref('base.group_no_one').write({'users': [(4, self.user.id)]})
self.env.ref('base.group_user').write({'users': [(4, self.user.id)]})
self._make_rule('rule 0', '[("val", "=", 42)]', global_=True)
self._make_rule('rule 1', '[(1, "=", 1)]', global_=True)
with self.assertRaises(AccessError) as ctx:
self.record.write({'val': 1})
self.assertEqual(
ctx.exception.args[0],
"""The requested operation ("write" on "Object For Test Access Right" (test_access_right.some_obj)) was rejected because of the following rules:
"""Due to security restrictions, you are not allowed to modify 'Object For Test Access Right' (test_access_right.some_obj) records.
Records: %s (id=%s)
User: %s (id=%s)
This restriction is due to the following rules:
- rule 0
(Records: %s (id=%s), User: %s (id=%s))""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
Contact your administrator to request access if necessary.""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
)
def test_combination(self):
self.env.ref('base.group_no_one').write(
{'users': [(4, self.user.id)]})
self.env.ref('base.group_no_one').write({'users': [(4, self.user.id)]})
self.env.ref('base.group_user').write({'users': [(4, self.user.id)]})
self._make_rule('rule 0', '[("val", "=", 42)]', global_=True)
self._make_rule('rule 1', '[(1, "=", 1)]', global_=True)
self._make_rule('rule 2', '[(0, "=", 1)]')
@@ -256,53 +290,68 @@ class TestIRRuleFeedback(Feedback):
self.record.write({'val': 1})
self.assertEqual(
ctx.exception.args[0],
"""The requested operation ("write" on "Object For Test Access Right" (test_access_right.some_obj)) was rejected because of the following rules:
"""Due to security restrictions, you are not allowed to modify 'Object For Test Access Right' (test_access_right.some_obj) records.
Records: %s (id=%s)
User: %s (id=%s)
This restriction is due to the following rules:
- rule 0
- rule 2
- rule 3
(Records: %s (id=%s), User: %s (id=%s))""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
Contact your administrator to request access if necessary.""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
)
def test_warn_company(self):
""" If one of the failing rules mentions company_id, add a note that
this might be a multi-company issue.
"""
self.env.ref('base.group_no_one').write(
{'users': [(4, self.user.id)]})
self.env.ref('base.group_no_one').write({'users': [(4, self.user.id)]})
self.env.ref('base.group_user').write({'users': [(4, self.user.id)]})
self._make_rule('rule 0', "[('company_id', '=', user.company_id.id)]")
self._make_rule('rule 1', '[("val", "=", 0)]', global_=True)
with self.assertRaises(AccessError) as ctx:
self.record.write({'val': 1})
self.assertEqual(
ctx.exception.args[0],
"""The requested operation ("write" on "Object For Test Access Right" (test_access_right.some_obj)) was rejected because of the following rules:
"""Due to security restrictions, you are not allowed to modify 'Object For Test Access Right' (test_access_right.some_obj) records.
Records: %s (id=%s)
User: %s (id=%s)
This restriction is due to the following rules:
- rule 0
Note: this might be a multi-company issue.
(Records: %s (id=%s), User: %s (id=%s))""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
Contact your administrator to request access if necessary.""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
)
def test_read(self):
""" because of prefetching, read() goes through a different codepath
to apply rules
"""
self.env.ref('base.group_no_one').write(
{'users': [(4, self.user.id)]})
self.env.ref('base.group_no_one').write({'users': [(4, self.user.id)]})
self.env.ref('base.group_user').write({'users': [(4, self.user.id)]})
self._make_rule('rule 0', "[('company_id', '=', user.company_id.id)]", attr='read')
self._make_rule('rule 1', '[("val", "=", 1)]', global_=True, attr='read')
with self.assertRaises(AccessError) as ctx:
_ = self.record.val
self.assertEqual(
ctx.exception.args[0],
"""The requested operation ("read" on "Object For Test Access Right" (test_access_right.some_obj)) was rejected because of the following rules:
"""Due to security restrictions, you are not allowed to access 'Object For Test Access Right' (test_access_right.some_obj) records.
Records: %s (id=%s)
User: %s (id=%s)
This restriction is due to the following rules:
- rule 0
- rule 1
Note: this might be a multi-company issue.
(Records: %s (id=%s), User: %s (id=%s))""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
Contact your administrator to request access if necessary.""" % (self.record.display_name, self.record.id, self.user.name, self.user.id)
)
p = self.env['test_access_right.parent'].create({'obj_id': self.record.id})