199 lines
9.9 KiB
Python
199 lines
9.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from odoo.tests.common import TransactionCase, new_test_user
|
|
from odoo.exceptions import AccessError
|
|
from odoo.tools import mute_logger
|
|
|
|
|
|
class TestAccessRights(TransactionCase):
|
|
|
|
@classmethod
|
|
@mute_logger('odoo.tests', 'odoo.addons.auth_signup.models.res_users')
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.john = new_test_user(cls.env, login='john', groups='base.group_user')
|
|
cls.raoul = new_test_user(cls.env, login='raoul', groups='base.group_user')
|
|
cls.george = new_test_user(cls.env, login='george', groups='base.group_user')
|
|
cls.portal = new_test_user(cls.env, login='pot', groups='base.group_portal')
|
|
cls.admin_user = new_test_user(cls.env, login='admin_user', groups='base.group_partner_manager')
|
|
|
|
def create_event(self, user, **values):
|
|
return self.env['calendar.event'].with_user(user).create({
|
|
'name': 'Event',
|
|
'start': datetime(2020, 2, 2, 8, 0),
|
|
'stop': datetime(2020, 2, 2, 18, 0),
|
|
'user_id': user.id,
|
|
'partner_ids': [(4, self.george.partner_id.id, 0)],
|
|
**values
|
|
})
|
|
|
|
def read_event(self, user, events, field):
|
|
data = events.with_user(user).read([field])
|
|
if len(events) == 1:
|
|
return data[0][field]
|
|
return [r[field] for r in data]
|
|
|
|
# don't spam logs with ACL failures from portal
|
|
@mute_logger('odoo.addons.base.models.ir_rule')
|
|
def test_privacy(self):
|
|
event = self.create_event(
|
|
self.john,
|
|
privacy='private',
|
|
name='my private event',
|
|
location='in the Sky'
|
|
)
|
|
for user, field, expect, error in [
|
|
# public field, any employee can read
|
|
(self.john, 'privacy', 'private', None),
|
|
(self.george, 'privacy', 'private', None),
|
|
(self.raoul, 'privacy', 'private', None),
|
|
(self.portal, 'privacy', None, AccessError),
|
|
# substituted private field, only owner and invitees can read, other
|
|
# employees get substitution
|
|
(self.john, 'name', 'my private event', None),
|
|
(self.george, 'name', 'my private event', None),
|
|
(self.raoul, 'name', 'Busy', None),
|
|
(self.portal, 'name', None, AccessError),
|
|
# computed from private field
|
|
(self.john, 'display_name', 'my private event', None),
|
|
(self.george, 'display_name', 'my private event', None),
|
|
(self.raoul, 'display_name', 'Busy', None),
|
|
(self.portal, 'display_name', None, AccessError),
|
|
# non-substituted private field, only owner and invitees can read,
|
|
# other employees get an empty field
|
|
(self.john, 'location', 'in the Sky', None),
|
|
(self.george, 'location', 'in the Sky', None),
|
|
(self.raoul, 'location', False, None),
|
|
(self.portal, 'location', None, AccessError),
|
|
# non-substituted sequence field
|
|
(self.john, 'partner_ids', self.george.partner_id, None),
|
|
(self.george, 'partner_ids', self.george.partner_id, None),
|
|
(self.raoul, 'partner_ids', self.env['res.partner'], None),
|
|
(self.portal, 'partner_ids', None, AccessError),
|
|
]:
|
|
self.env.invalidate_all()
|
|
with self.subTest("private read", user=user.display_name, field=field, error=error):
|
|
e = event.with_user(user)
|
|
if error:
|
|
with self.assertRaises(error):
|
|
_ = e[field]
|
|
else:
|
|
self.assertEqual(e[field], expect)
|
|
|
|
def test_private_and_public(self):
|
|
private = self.create_event(
|
|
self.john,
|
|
privacy='private',
|
|
location='in the Sky',
|
|
)
|
|
public = self.create_event(
|
|
self.john,
|
|
privacy='public',
|
|
location='In Hell',
|
|
)
|
|
# invalidate cache before reading, otherwise read() might leak private data
|
|
self.env.invalidate_all()
|
|
[private_location, public_location] = self.read_event(self.raoul, private + public, 'location')
|
|
self.assertEqual(private_location, False, "Private value should be obfuscated")
|
|
self.assertEqual(public_location, 'In Hell', "Public value should not be obfuscated")
|
|
|
|
def test_read_group_public(self):
|
|
event = self.create_event(self.john)
|
|
data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['start'], groupby='start')
|
|
self.assertTrue(data, "It should be able to read group")
|
|
data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['name'],
|
|
groupby='name')
|
|
self.assertTrue(data, "It should be able to read group")
|
|
|
|
def test_read_group_private(self):
|
|
event = self.create_event(self.john, privacy='private')
|
|
result = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['name'], groupby='name')
|
|
self.assertFalse(result, "Private events should not be fetched")
|
|
|
|
|
|
def test_read_group_agg(self):
|
|
event = self.create_event(self.john)
|
|
data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['start'], groupby='start:week')
|
|
self.assertTrue(data, "It should be able to read group")
|
|
|
|
def test_read_group_list(self):
|
|
event = self.create_event(self.john)
|
|
data = self.env['calendar.event'].with_user(self.raoul).read_group([('id', '=', event.id)], fields=['start'], groupby=['start'])
|
|
self.assertTrue(data, "It should be able to read group")
|
|
|
|
def test_private_attendee(self):
|
|
event = self.create_event(
|
|
self.john,
|
|
privacy='private',
|
|
location='in the Sky',
|
|
)
|
|
partners = (self.john|self.raoul).mapped('partner_id')
|
|
event.write({'partner_ids': [(6, 0, partners.ids)]})
|
|
self.assertEqual(self.read_event(self.raoul, event, 'location'), 'in the Sky',
|
|
"Owner should be able to read the event")
|
|
with self.assertRaises(AccessError):
|
|
self.read_event(self.portal, event, 'location')
|
|
|
|
def test_meeting_edit_access_notification_handle_in_odoo(self):
|
|
# set notifications to "handle in Odoo" in Preferences for john, raoul, and george
|
|
(self.john | self.raoul | self.george).write({'notification_type': 'inbox'})
|
|
|
|
# raoul creates a meeting for john, excluding themselves
|
|
meeting = self.env['calendar.event'].with_user(self.raoul).create({
|
|
'name': 'Test Meeting',
|
|
'start': datetime.now(),
|
|
'stop': datetime.now() + timedelta(hours=2),
|
|
'user_id': self.john.id,
|
|
'partner_ids': [(4, self.raoul.partner_id.id)],
|
|
})
|
|
|
|
# george tries to modify the start date of the meeting to a future date
|
|
# this verifies that users with "handle in Odoo" notification setting can
|
|
# successfully edit meetings created by other users. If this write fails,
|
|
# it indicates that there might be an issue with access rights for meeting attendees.
|
|
meeting = meeting.with_user(self.george)
|
|
meeting.write({
|
|
'start': datetime.now() + timedelta(days=2),
|
|
'stop': datetime.now() + timedelta(days=2, hours=2),
|
|
})
|
|
|
|
def test_admin_cant_fetch_uninvited_private_events(self):
|
|
"""
|
|
Administrators must not be able to fetch information from private events which
|
|
they are not attending (i.e. events which it is not an event partner). The privacy
|
|
of the event information must always be kept. Public events can be read normally.
|
|
"""
|
|
john_private_evt = self.create_event(self.john, name='priv', privacy='private', location='loc_1', description='priv')
|
|
john_public_evt = self.create_event(self.john, name='pub', privacy='public', location='loc_2', description='pub')
|
|
self.env.invalidate_all()
|
|
|
|
# For the private event, ensure that no private field can be read, such as: 'name', 'location' and 'description'.
|
|
for (field, value) in [('name', 'Busy'), ('location', False), ('description', False)]:
|
|
hidden_information = self.read_event(self.admin_user, john_private_evt, field)
|
|
self.assertEqual(hidden_information, value, "The field '%s' information must be hidden, even for uninvited admins." % field)
|
|
|
|
# For the public event, ensure that the same fields can be read by the admin.
|
|
for (field, value) in [('name', 'pub'), ('location', 'loc_2'), ('description', "<p>pub</p>")]:
|
|
field_information = self.read_event(self.admin_user, john_public_evt, field)
|
|
self.assertEqual(str(field_information), value, "The field '%s' information must be readable by the admin." % field)
|
|
|
|
def test_admin_cant_edit_uninvited_events(self):
|
|
"""
|
|
Administrators must not be able to edit events that they are not attending.
|
|
The event is property of the organizer and its attendees only, private or not.
|
|
"""
|
|
john_public_evt = self.create_event(self.john, name='pub', privacy='public', location='loc_2', description='pub')
|
|
john_private_evt = self.create_event(self.john, name='priv', privacy='private', location='loc_1', description='priv')
|
|
|
|
for event in [john_public_evt, john_private_evt]:
|
|
# Ensure that uninvited admin can not edit the event since it is not an event partner (attendee).
|
|
with self.assertRaises(AccessError):
|
|
event.with_user(self.admin_user)._compute_user_can_edit()
|
|
|
|
# Ensure that AccessError is raised when trying to update the uninvited event.
|
|
with self.assertRaises(AccessError):
|
|
event.with_user(self.admin_user).write({'name': 'forbidden-update'})
|