19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:34 +01:00
parent 5faf7397c5
commit 2696f14ed7
721 changed files with 220375 additions and 91221 deletions

View file

@ -1,9 +1,9 @@
# -*- 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.tests import Form
from odoo.exceptions import AccessError
from odoo.tools import mute_logger
@ -19,6 +19,7 @@ class TestAccessRights(TransactionCase):
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,base.group_user')
cls.admin_system_user = new_test_user(cls.env, login='admin_system_user', groups='base.group_system')
def create_event(self, user, **values):
return self.env['calendar.event'].with_user(user).create({
@ -94,32 +95,32 @@ class TestAccessRights(TransactionCase):
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.assertFalse(private_location, "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')
data = self.env['calendar.event'].with_user(self.raoul)._read_group([('id', '=', event.id)], groupby=['start:month'])
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')
data = self.env['calendar.event'].with_user(self.raoul)._read_group([('id', '=', event.id)], 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')
result = self.env['calendar.event'].with_user(self.raoul)._read_group([('id', '=', event.id)], 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')
data = self.env['calendar.event'].with_user(self.raoul)._read_group([('id', '=', event.id)], 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'])
data = self.env['calendar.event'].with_user(self.raoul)._read_group([('id', '=', event.id)], groupby=['start:month'])
self.assertTrue(data, "It should be able to read group")
def test_private_attendee(self):
@ -158,69 +159,216 @@ class TestAccessRights(TransactionCase):
'stop': datetime.now() + timedelta(days=2, hours=2),
})
def test_event_default_privacy_as_private(self):
""" Check the privacy of events with owner's event default privacy as 'private'. """
# Set organizer default privacy as 'private' and create event privacies default, public, private and confidential.
self.george.with_user(self.george).calendar_default_privacy = 'private'
default_event = self.create_event(self.george)
public_event = self.create_event(self.george, privacy='public')
private_event = self.create_event(self.george, privacy='private')
confidential_event = self.create_event(self.george, privacy='confidential')
# With another user who is not an event attendee, try accessing the events.
query_default_event = self.env['calendar.event'].with_user(self.raoul)._read_group([('id', '=', default_event.id)], groupby=['name'])
query_public_event = self.env['calendar.event'].with_user(self.raoul)._read_group([('id', '=', public_event.id)], groupby=['name'])
query_private_event = self.env['calendar.event'].with_user(self.raoul)._read_group([('id', '=', private_event.id)], groupby=['name'])
query_confidential_event = self.env['calendar.event'].with_user(self.raoul)._read_group([('id', '=', confidential_event.id)], groupby=['name'])
# Ensure that each event is accessible or not according to its privacy.
self.assertFalse(query_default_event, "Event must be inaccessible because the user has default privacy as 'private'.")
self.assertTrue(query_public_event, "Public event must be accessible to other users.")
self.assertFalse(query_private_event, "Private event must be inaccessible to other users.")
self.assertTrue(query_confidential_event, "Confidential event must be accessible to other internal users.")
def test_edit_private_event_of_other_user(self):
"""
Ensure that it is not possible editing the private event of another user when the current user is not an
attendee/organizer of that event. Attendees should be able to edit it, others will receive AccessError on write.
"""
def ensure_user_can_update_event(self, event, user):
event.with_user(user).write({'name': user.name})
self.assertEqual(event.name, user.name, 'Event name should be updated by user %s' % user.name)
# Prepare events attendees/partners including organizer (john) and another user (raoul).
events_attendees = [
(0, 0, {'partner_id': self.john.partner_id.id, 'state': 'accepted'}),
(0, 0, {'partner_id': self.raoul.partner_id.id, 'state': 'accepted'})
]
events_partners = [self.john.partner_id.id, self.raoul.partner_id.id]
# Set calendar default privacy as private and create a normal event, only attendees/organizer can edit it.
self.john.with_user(self.john).calendar_default_privacy = 'private'
johns_default_privacy_event = self.create_event(self.john, name='my event with default privacy', attendee_ids=events_attendees, partner_ids=events_partners)
ensure_user_can_update_event(self, johns_default_privacy_event, self.john)
ensure_user_can_update_event(self, johns_default_privacy_event, self.raoul)
with self.assertRaises(AccessError):
self.assertEqual(len(self.john.res_users_settings_id), 1, "Res Users Settings for the user is not defined.")
self.assertEqual(self.john.res_users_settings_id.calendar_default_privacy, 'private', "Privacy field update was lost.")
johns_default_privacy_event.with_user(self.george).write({'name': 'blocked-update-by-non-attendee'})
# Set calendar default privacy as public and create a private event, only attendees/organizer can edit it.
self.john.with_user(self.john).calendar_default_privacy = 'public'
johns_private_event = self.create_event(self.john, name='my private event', privacy='private', attendee_ids=events_attendees, partner_ids=events_partners)
ensure_user_can_update_event(self, johns_private_event, self.john)
ensure_user_can_update_event(self, johns_private_event, self.raoul)
with self.assertRaises(AccessError):
self.assertEqual(len(self.john.res_users_settings_id), 1, "Res Users Settings for the user is not defined.")
self.assertEqual(self.john.res_users_settings_id.calendar_default_privacy, 'public', "Privacy field update was lost.")
johns_private_event.with_user(self.george).write({'name': 'blocked-update-by-non-attendee'})
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', '<div>pub<br>'
'<strong>Organized by</strong><br>'
'john (base.group_user)<br><a href="mailto:j.j@example.com">j.j@example.com</a><br><br>'
'<strong>Contact Details</strong><br>'
'george (base.group_user)<br><a href="mailto:g.g@example.com">g.g@example.com</a></div>',
),
]:
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_private_events(self):
"""
Administrators must not be able to edit private events that they are not attending.
The event is property of the organizer and its attendees only (for private events in the backend).
"""
john_private_evt = self.create_event(self.john, name='priv', privacy='private', location='loc_1', description='priv')
# Ensure that uninvited admin can not edit the event since it is not an event partner (attendee).
with self.assertRaises(AccessError):
john_private_evt.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):
john_private_evt.with_user(self.admin_user).write({'name': 'forbidden-update'})
def test_admin_edit_uninvited_non_private_events(self):
"""
Administrators must be able to edit (public, confidential) events that they are not attending.
This feature is widely used for customers since it is useful editing normal user's events on their behalf.
"""
for privacy in ['public', 'confidential']:
john_event = self.create_event(self.john, name='event', privacy=privacy, location='loc')
# Ensure that uninvited admin can edit this type of event.
john_event.with_user(self.admin_user)._compute_user_can_edit()
self.assertTrue(john_event.user_can_edit, f"Event of type {privacy} must be editable by uninvited admins.")
john_event.with_user(self.admin_user).write({'name': 'update'})
self.assertEqual(john_event.name, 'update', f"Simple write must be allowed for uninvited admins in {privacy} events.")
def test_hide_sensitive_fields_private_events_from_uninvited_admins(self):
"""
Ensure that it is not possible fetching sensitive fields for uninvited administrators,
i.e. admins who are not attendees of private events. Sensitive fields are fields that
could contain sensitive information, such as 'name', 'description', 'location', etc.
"""
sensitive_fields = [
'location', 'attendee_ids', 'partner_ids', 'description',
'videocall_location', 'categ_ids', 'message_ids',
]
sensitive_fields = {
'name', 'location', 'attendee_ids', 'description', 'alarm_ids',
'categ_ids', 'message_ids', 'partner_ids', 'videocall_location'
}
# Create event with all sensitive fields defined on it.
event_type = self.env['calendar.event.type'].create({'name': 'type'})
john_private_evt = self.create_event(
self.john,
name='private-event',
privacy='private',
location='private-location',
description='private-description',
attendee_status='accepted',
partner_ids=[self.john.partner_id.id, self.raoul.partner_id.id],
categ_ids=[event_type.id],
videocall_location='private-url.com'
)
john_private_evt.message_post(body="Message to be hidden.")
# Read the event as an uninvited administrator and ensure that the sensitive fields were hidden.
# Do the same for the search_read method: the information of sensitive fields must be hidden.
# Search_fetch the event as an uninvited administrator and ensure that the sensitive fields were hidden.
# This method goes through the _fetch_query method which covers all variations of read(), search_read() and export_data().
private_event_domain = ('id', '=', john_private_evt.id)
readed_event = john_private_evt.with_user(self.admin_user).read(sensitive_fields + ['name'])
search_readed_event = self.env['calendar.event'].with_user(self.admin_user).search_read([private_event_domain])
for event in [readed_event, search_readed_event]:
self.assertEqual(len(event), 1, "The event itself must be fetched since the record is not hidden from uninvited admins.")
self.assertEqual(event[0]['name'], "Busy", "Event name must be 'Busy', hiding the information from uninvited administrators.")
for field in sensitive_fields:
self.assertFalse(event[0][field], "Field %s contains private information, it must be hidden from uninvited administrators." % field)
# Ensure that methods like 'mapped', 'filtered', 'filtered_domain', '_search' and 'read_group' do not
# bypass the override of read, which will hide the private information of the events from uninvited administrators.
sensitive_stored_fields = ['name', 'location', 'description', 'videocall_location']
searched_event = self.env['calendar.event'].with_user(self.admin_user).search([private_event_domain])
for field in sensitive_stored_fields:
# For each method, fetch the information of the private event as an uninvited administrator.
check_mapped_event = searched_event.with_user(self.admin_user).mapped(field)
check_filtered_event = searched_event.with_user(self.admin_user).filtered(lambda ev: ev.id == john_private_evt.id)
check_filtered_domain = searched_event.with_user(self.admin_user).filtered_domain([private_event_domain])
check_search_query = self.env['calendar.event'].with_user(self.admin_user)._search([private_event_domain])
check_search_object = self.env['calendar.event'].with_user(self.admin_user).browse(check_search_query)
check_read_group = self.env['calendar.event'].with_user(self.admin_user).read_group([private_event_domain], [field], [field])
search_fetch_event = self.env['calendar.event'].with_user(self.admin_user).search_fetch([private_event_domain], sensitive_fields)
self.assertEqual(len(search_fetch_event), 1, "The event itself must be fetched since the record is not hidden from uninvited admins.")
for field in sensitive_fields:
if field == 'name':
# The 'name' field is manually changed to 'Busy' by default. We need to ensure it is shown as 'Busy' in all following methods.
self.assertEqual(check_mapped_event, ['Busy'], 'Private event name should be shown as Busy using the mapped function.')
self.assertEqual(check_filtered_event.name, 'Busy', 'Private event name should be shown as Busy using the filtered function.')
self.assertEqual(check_filtered_domain.name, 'Busy', 'Private event name should be shown as Busy using the filtered_domain function.')
self.assertEqual(check_search_object.name, 'Busy', 'Private event name should be shown as Busy using the _search function.')
self.assertEqual(search_fetch_event['name'], "Busy", "Event name must be 'Busy', hiding the information from uninvited administrators.")
else:
# The remaining private fields should be falsy for uninvited administrators.
self.assertFalse(check_mapped_event[0], 'Private event field "%s" should be hidden when using the mapped function.' % field)
self.assertFalse(check_filtered_event[field], 'Private event field "%s" should be hidden when using the filtered function.' % field)
self.assertFalse(check_filtered_domain[field], 'Private event field "%s" should be hidden when using the filtered_domain function.' % field)
self.assertFalse(check_search_object[field], 'Private event field "%s" should be hidden when using the _search function.' % field)
self.assertFalse(search_fetch_event[field], "Field %s contains private information, it must be hidden from uninvited administrators." % field)
# Private events are excluded from read_group by default, ensure that we do not fetch it.
self.assertFalse(len(check_read_group), 'Private event should be hidden using the function _read_group.')
def test_user_update_calendar_default_privacy(self):
"""
Ensure that administrators and normal users can update their own calendar
default privacy from the 'res.users' related field without throwing any error.
Updates from others users are blocked during write (except for Default User Template from admins).
"""
for privacy in ['public', 'private', 'confidential']:
# Update normal user and administrator 'calendar_default_privacy' simulating their own update.
self.john.with_user(self.john).write({'calendar_default_privacy': privacy})
self.admin_system_user.with_user(self.admin_system_user).write({'calendar_default_privacy': privacy})
self.assertEqual(self.john.calendar_default_privacy, privacy, 'Normal user must be able to update its calendar default privacy.')
self.assertEqual(self.admin_system_user.calendar_default_privacy, privacy, 'Admin must be able to update its calendar default privacy.')
# Update the Default 'calendar.default_privacy' as an administrator.
self.env['ir.config_parameter'].sudo().set_param("calendar.default_privacy", privacy)
# All calendar default privacy updates must be blocked during write.
with self.assertRaises(AccessError):
self.john.with_user(self.admin_system_user).write({'calendar_default_privacy': privacy})
with self.assertRaises(AccessError):
self.admin_system_user.with_user(self.john).write({'calendar_default_privacy': privacy})
def test_check_private_event_conditions_by_internal_user(self):
""" Ensure that internal user (non-admin) will see that admin's event is private. """
# Update admin calendar_default_privacy with 'private' option. Create private event for admin.
self.admin_user.with_user(self.admin_user).write({'calendar_default_privacy': 'private'})
admin_user_private_evt = self.create_event(self.admin_user, name='My Event', privacy=False, partner_ids=[self.admin_user.partner_id.id])
# Ensure that intrnal user will see the admin's event as private.
self.assertTrue(
admin_user_private_evt.with_user(self.raoul)._check_private_event_conditions(),
"Privacy check must be True since the new event is private (following John's calendar default privacy)."
)
def test_recurring_event_with_alarms_for_non_admin(self):
"""
Test that non-admin user can modify recurring events with alarms
without triggering access errors when accessing ir.cron.trigger records.
"""
alarm = self.env['calendar.alarm'].create({
'name': '15 minutes before',
'alarm_type': 'email',
'duration': 15,
'interval': 'minutes',
})
with Form(self.env['calendar.event'].with_user(self.john)) as event_form:
event_form.name = 'yearly Team Meeting'
event_form.start = datetime(2024, 1, 15, 9, 0)
event_form.stop = datetime(2024, 1, 15, 10, 0)
event_form.recurrency = True
event_form.rrule_type_ui = 'yearly'
event_form.count = 3
event_form.alarm_ids.add(alarm)
event_form.partner_ids.add(self.john.partner_id)
recurring_event = event_form.save()
self.assertTrue(recurring_event.recurrence_id, "Recurrence should be created")
with Form(recurring_event.with_user(self.john)) as form:
form.partner_ids.add(self.raoul.partner_id)
self.assertIn(self.raoul.partner_id.id, recurring_event.partner_ids.ids, "Partner should be added as attendee")