mirror of
https://github.com/bringout/oca-ocb-security.git
synced 2026-04-21 20:32:06 +02:00
19.0 vanilla
This commit is contained in:
parent
20ddc1b4a3
commit
c0efcc53f5
1162 changed files with 125577 additions and 105287 deletions
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
|
|
@ -8,15 +7,13 @@ import pytz
|
|||
from dateutil.parser import parse
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import api, fields, models, registry
|
||||
from odoo.tools import ormcache_context
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.osv import expression
|
||||
from odoo import api, fields, models
|
||||
from odoo.fields import Domain
|
||||
from odoo.modules.registry import Registry
|
||||
from odoo.sql_db import BaseCursor
|
||||
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_event import MicrosoftEvent
|
||||
from odoo.addons.microsoft_calendar.utils.microsoft_calendar import MicrosoftCalendarService
|
||||
from odoo.addons.microsoft_calendar.utils.event_id_storage import IDS_SEPARATOR, combine_ids, split_ids
|
||||
from odoo.addons.microsoft_account.models.microsoft_service import TIMEOUT
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
|
@ -40,7 +37,7 @@ def after_commit(func):
|
|||
|
||||
@self.env.cr.postcommit.add
|
||||
def called_after():
|
||||
db_registry = registry(dbname)
|
||||
db_registry = Registry(dbname)
|
||||
with db_registry.cursor() as cr:
|
||||
env = api.Environment(cr, uid, context)
|
||||
try:
|
||||
|
|
@ -55,48 +52,38 @@ def after_commit(func):
|
|||
def microsoft_calendar_token(user):
|
||||
yield user._get_microsoft_calendar_token()
|
||||
|
||||
class MicrosoftSync(models.AbstractModel):
|
||||
|
||||
class MicrosoftCalendarSync(models.AbstractModel):
|
||||
_name = 'microsoft.calendar.sync'
|
||||
_description = "Synchronize a record with Microsoft Calendar"
|
||||
|
||||
microsoft_id = fields.Char('Microsoft Calendar Id', copy=False)
|
||||
|
||||
ms_organizer_event_id = fields.Char(
|
||||
'Organizer event Id',
|
||||
compute='_compute_organizer_event_id',
|
||||
inverse='_set_event_id',
|
||||
search='_search_organizer_event_id',
|
||||
)
|
||||
ms_universal_event_id = fields.Char(
|
||||
'Universal event Id',
|
||||
compute='_compute_universal_event_id',
|
||||
inverse='_set_event_id',
|
||||
search='_search_universal_event_id',
|
||||
)
|
||||
microsoft_id = fields.Char('Organizer event Id', copy=False, index=True)
|
||||
ms_universal_event_id = fields.Char('Universal event Id', copy=False, index=True)
|
||||
|
||||
# This field helps to know when a microsoft event need to be resynced
|
||||
need_sync_m = fields.Boolean(default=True, copy=False)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
def write(self, vals):
|
||||
if 'ms_universal_event_id' in vals:
|
||||
self._from_uids.clear_cache(self)
|
||||
|
||||
fields_to_sync = [x for x in vals.keys() if x in self._get_microsoft_synced_fields()]
|
||||
if fields_to_sync and 'need_sync_m' not in vals and not self.env.user.microsoft_synchronization_stopped:
|
||||
fields_to_sync = [x for x in vals if x in self._get_microsoft_synced_fields()]
|
||||
if fields_to_sync and 'need_sync_m' not in vals and self.env.user._get_microsoft_sync_status() == "sync_active":
|
||||
vals['need_sync_m'] = True
|
||||
|
||||
result = super().write(vals)
|
||||
|
||||
for record in self.filtered(lambda e: e.need_sync_m and e.ms_organizer_event_id):
|
||||
if not vals.get('active', True):
|
||||
# We need to delete the event. Cancel is not sufficant. Errors may occurs
|
||||
record._microsoft_delete(record._get_organizer(), record.ms_organizer_event_id, timeout=3)
|
||||
elif fields_to_sync:
|
||||
values = record._microsoft_values(fields_to_sync)
|
||||
if not values:
|
||||
continue
|
||||
record._microsoft_patch(record._get_organizer(), record.ms_organizer_event_id, values, timeout=3)
|
||||
if self.env.user._get_microsoft_sync_status() != "sync_paused":
|
||||
timeout = self._get_microsoft_graph_timeout()
|
||||
|
||||
for record in self:
|
||||
if record.need_sync_m and record.microsoft_id:
|
||||
if not vals.get('active', True):
|
||||
# We need to delete the event. Cancel is not sufficient. Errors may occur.
|
||||
record._microsoft_delete(record._get_organizer(), record.microsoft_id, timeout=timeout)
|
||||
elif fields_to_sync:
|
||||
values = record._microsoft_values(fields_to_sync)
|
||||
if not values:
|
||||
continue
|
||||
record._microsoft_patch(record._get_organizer(), record.microsoft_id, values, timeout=timeout)
|
||||
|
||||
return result
|
||||
|
||||
|
|
@ -107,53 +94,14 @@ class MicrosoftSync(models.AbstractModel):
|
|||
vals.update({'need_sync_m': False})
|
||||
records = super().create(vals_list)
|
||||
|
||||
records_to_sync = records.filtered(lambda r: r.need_sync_m and r.active)
|
||||
for record in records_to_sync:
|
||||
record._microsoft_insert(record._microsoft_values(self._get_microsoft_synced_fields()), timeout=3)
|
||||
if self.env.user._get_microsoft_sync_status() != "sync_paused":
|
||||
timeout = self._get_microsoft_graph_timeout()
|
||||
|
||||
for record in records:
|
||||
if record.need_sync_m and record.active:
|
||||
record._microsoft_insert(record._microsoft_values(self._get_microsoft_synced_fields()), timeout=timeout)
|
||||
return records
|
||||
|
||||
@api.depends('microsoft_id')
|
||||
def _compute_organizer_event_id(self):
|
||||
for event in self:
|
||||
event.ms_organizer_event_id = split_ids(event.microsoft_id)[0] if event.microsoft_id else False
|
||||
|
||||
@api.depends('microsoft_id')
|
||||
def _compute_universal_event_id(self):
|
||||
for event in self:
|
||||
event.ms_universal_event_id = split_ids(event.microsoft_id)[1] if event.microsoft_id else False
|
||||
|
||||
def _set_event_id(self):
|
||||
for event in self:
|
||||
event.microsoft_id = combine_ids(event.ms_organizer_event_id, event.ms_universal_event_id)
|
||||
|
||||
def _search_event_id(self, operator, value, with_uid):
|
||||
def _domain(v):
|
||||
return ('microsoft_id', '=like', f'%{IDS_SEPARATOR}{v}' if with_uid else f'{v}%')
|
||||
|
||||
if operator == '=' and not value:
|
||||
return (
|
||||
['|', ('microsoft_id', '=', False), ('microsoft_id', '=ilike', f'%{IDS_SEPARATOR}')]
|
||||
if with_uid
|
||||
else [('microsoft_id', '=', False)]
|
||||
)
|
||||
elif operator == '!=' and not value:
|
||||
return (
|
||||
[('microsoft_id', 'ilike', f'{IDS_SEPARATOR}_')]
|
||||
if with_uid
|
||||
else [('microsoft_id', '!=', False)]
|
||||
)
|
||||
return (
|
||||
['|'] * (len(value) - 1) + [_domain(v) for v in value]
|
||||
if operator.lower() == 'in'
|
||||
else [_domain(value)]
|
||||
)
|
||||
|
||||
def _search_organizer_event_id(self, operator, value):
|
||||
return self._search_event_id(operator, value, with_uid=False)
|
||||
|
||||
def _search_universal_event_id(self, operator, value):
|
||||
return self._search_event_id(operator, value, with_uid=True)
|
||||
|
||||
@api.model
|
||||
def _get_microsoft_service(self):
|
||||
return MicrosoftCalendarService(self.env['microsoft.service'])
|
||||
|
|
@ -166,8 +114,9 @@ class MicrosoftSync(models.AbstractModel):
|
|||
|
||||
def unlink(self):
|
||||
synced = self._get_synced_events()
|
||||
for ev in synced:
|
||||
ev._microsoft_delete(ev._get_organizer(), ev.ms_organizer_event_id)
|
||||
if self.env.user._get_microsoft_sync_status() != "sync_paused":
|
||||
for ev in synced:
|
||||
ev._microsoft_delete(ev._get_organizer(), ev.microsoft_id)
|
||||
return super().unlink()
|
||||
|
||||
def _write_from_microsoft(self, microsoft_event, vals):
|
||||
|
|
@ -175,14 +124,7 @@ class MicrosoftSync(models.AbstractModel):
|
|||
|
||||
@api.model
|
||||
def _create_from_microsoft(self, microsoft_event, vals_list):
|
||||
return self.with_context(dont_notify=True).create(vals_list)
|
||||
|
||||
@api.model
|
||||
@ormcache_context('uids', keys=('active_test',))
|
||||
def _from_uids(self, uids):
|
||||
if not uids:
|
||||
return self.browse()
|
||||
return self.search([('ms_universal_event_id', 'in', uids)])
|
||||
return self.with_context(dont_notify=True, skip_contact_description=True).create(vals_list)
|
||||
|
||||
def _sync_odoo2microsoft(self):
|
||||
if not self:
|
||||
|
|
@ -198,9 +140,12 @@ class MicrosoftSync(models.AbstractModel):
|
|||
new_records = records_to_sync - updated_records
|
||||
|
||||
for record in cancelled_records._get_synced_events():
|
||||
record._microsoft_delete(record._get_organizer(), record.ms_organizer_event_id)
|
||||
record._microsoft_delete(record._get_organizer(), record.microsoft_id)
|
||||
for record in new_records:
|
||||
values = record._microsoft_values(self._get_microsoft_synced_fields())
|
||||
sender_user = record._get_event_user_m()
|
||||
if record._is_microsoft_insertion_blocked(sender_user):
|
||||
continue
|
||||
if isinstance(values, dict):
|
||||
record._microsoft_insert(values)
|
||||
else:
|
||||
|
|
@ -210,10 +155,11 @@ class MicrosoftSync(models.AbstractModel):
|
|||
values = record._microsoft_values(self._get_microsoft_synced_fields())
|
||||
if not values:
|
||||
continue
|
||||
record._microsoft_patch(record._get_organizer(), record.ms_organizer_event_id, values)
|
||||
record._microsoft_patch(record._get_organizer(), record.microsoft_id, values)
|
||||
|
||||
def _cancel_microsoft(self):
|
||||
self.microsoft_id = False
|
||||
self.ms_universal_event_id = False
|
||||
self.unlink()
|
||||
|
||||
def _sync_recurrence_microsoft2odoo(self, microsoft_events, new_events=None):
|
||||
|
|
@ -231,7 +177,7 @@ class MicrosoftSync(models.AbstractModel):
|
|||
need_sync_m=False
|
||||
)
|
||||
to_create = recurrents.filter(
|
||||
lambda e: e.seriesMasterId == new_calendar_recurrence['ms_organizer_event_id']
|
||||
lambda e: e.seriesMasterId == new_calendar_recurrence['microsoft_id']
|
||||
)
|
||||
recurrents -= to_create
|
||||
base_values = dict(
|
||||
|
|
@ -261,10 +207,7 @@ class MicrosoftSync(models.AbstractModel):
|
|||
# is specific to the Microsoft user calendar.
|
||||
ms_recurrence_ids = list({x.seriesMasterId for x in recurrents})
|
||||
ms_recurrence_uids = {r.id: r.iCalUId for r in microsoft_events if r.id in ms_recurrence_ids}
|
||||
|
||||
recurrences = self.env['calendar.recurrence'].search([
|
||||
('ms_universal_event_id', 'in', ms_recurrence_uids.values())
|
||||
])
|
||||
recurrences = self.env['calendar.recurrence'].search([('ms_universal_event_id', 'in', microsoft_events.uids)])
|
||||
for recurrent_master_id in ms_recurrence_ids:
|
||||
recurrence_id = recurrences.filtered(
|
||||
lambda ev: ev.ms_universal_event_id == ms_recurrence_uids[recurrent_master_id]
|
||||
|
|
@ -294,7 +237,7 @@ class MicrosoftSync(models.AbstractModel):
|
|||
Update Odoo events from Outlook recurrence and events.
|
||||
"""
|
||||
# get the list of events to update ...
|
||||
events_to_update = events.filter(lambda e: e.seriesMasterId == self.ms_organizer_event_id)
|
||||
events_to_update = events.filter(lambda e: e.seriesMasterId == self.microsoft_id)
|
||||
if self.end_type in ['count', 'forever']:
|
||||
events_to_update = list(events_to_update)[:MAX_RECURRENT_EVENT]
|
||||
|
||||
|
|
@ -345,7 +288,7 @@ class MicrosoftSync(models.AbstractModel):
|
|||
dict(self._microsoft_to_odoo_values(e, with_ids=True), need_sync_m=False)
|
||||
for e in (new - new_recurrence)
|
||||
]
|
||||
synced_events = self.with_context(dont_notify=True)._create_from_microsoft(new, odoo_values)
|
||||
synced_events = self.with_context(dont_notify=True, skip_contact_description=True)._create_from_microsoft(new, odoo_values)
|
||||
synced_recurrences, updated_events = self._sync_recurrence_microsoft2odoo(existing, new_recurrence)
|
||||
synced_events |= updated_events
|
||||
|
||||
|
|
@ -353,12 +296,12 @@ class MicrosoftSync(models.AbstractModel):
|
|||
cancelled_recurrences = self.env['calendar.recurrence'].search([
|
||||
'|',
|
||||
('ms_universal_event_id', 'in', cancelled.uids),
|
||||
('ms_organizer_event_id', 'in', cancelled.ids),
|
||||
('microsoft_id', 'in', cancelled.ids),
|
||||
])
|
||||
cancelled_events = self.browse([
|
||||
e.odoo_id(self.env)
|
||||
for e in cancelled
|
||||
if e.id not in [r.ms_organizer_event_id for r in cancelled_recurrences]
|
||||
if e.id not in [r.microsoft_id for r in cancelled_recurrences]
|
||||
])
|
||||
cancelled_recurrences._cancel_microsoft()
|
||||
cancelled_events = cancelled_events.exists()
|
||||
|
|
@ -419,11 +362,6 @@ class MicrosoftSync(models.AbstractModel):
|
|||
stop_date_condition = any(event.stop >= lower_bound for event in self.calendar_event_ids)
|
||||
return stop_date_condition or update_time_diff >= timedelta(hours=1)
|
||||
|
||||
def _impersonate_user(self, user_id):
|
||||
""" Impersonate a user (mainly the event organizer) to be able to call the Outlook API with its token """
|
||||
# This method is obsolete, as it has been replaced by the `_get_event_user_m` method, which gets the user who will make the request.
|
||||
return user_id.with_user(user_id)
|
||||
|
||||
@after_commit
|
||||
def _microsoft_delete(self, user_id, event_id, timeout=TIMEOUT):
|
||||
"""
|
||||
|
|
@ -433,8 +371,8 @@ class MicrosoftSync(models.AbstractModel):
|
|||
'self' won't exist when this method will be really called due to @after_commit decorator.
|
||||
"""
|
||||
microsoft_service = self._get_microsoft_service()
|
||||
sender_user = self._get_event_user_m(user_id)
|
||||
with microsoft_calendar_token(sender_user.sudo()) as token:
|
||||
sender_user = self._get_event_user_m(user_id).sudo()
|
||||
with microsoft_calendar_token(sender_user) as token:
|
||||
if token and not sender_user.microsoft_synchronization_stopped:
|
||||
microsoft_service.delete(event_id, token=token, timeout=timeout)
|
||||
|
||||
|
|
@ -475,7 +413,8 @@ class MicrosoftSync(models.AbstractModel):
|
|||
self._ensure_attendees_have_email()
|
||||
event_id, uid = microsoft_service.insert(values, token=token, timeout=timeout)
|
||||
self.with_context(dont_notify=True).write({
|
||||
'microsoft_id': combine_ids(event_id, uid),
|
||||
'microsoft_id': event_id,
|
||||
'ms_universal_event_id': uid,
|
||||
'need_sync_m': False,
|
||||
})
|
||||
|
||||
|
|
@ -518,7 +457,19 @@ class MicrosoftSync(models.AbstractModel):
|
|||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _microsoft_values(self, fields_to_sync):
|
||||
@api.model
|
||||
def _get_microsoft_graph_timeout(self):
|
||||
"""Return Microsoft Graph request timeout (seconds).
|
||||
|
||||
Keep current behavior by default (5s), but allow admins to increase it
|
||||
through a system parameter.
|
||||
"""
|
||||
timeout = self.env['ir.config_parameter'].sudo().get_param('microsoft_calendar.graph_timeout')
|
||||
if not timeout or not timeout.isdigit():
|
||||
return 5
|
||||
return max(1, int(timeout))
|
||||
|
||||
def _microsoft_values(self, fields_to_sync, initial_values=()):
|
||||
"""
|
||||
Implements this method to return a dict with values formatted
|
||||
according to the Microsoft Calendar API
|
||||
|
|
@ -550,19 +501,19 @@ class MicrosoftSync(models.AbstractModel):
|
|||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _extend_microsoft_domain(self, domain):
|
||||
def _extend_microsoft_domain(self, domain: Domain):
|
||||
""" Extends the sync domain based on the full_sync_m context parameter.
|
||||
In case of full sync it shouldn't include already synced events.
|
||||
"""
|
||||
if self._context.get('full_sync_m', True):
|
||||
domain = expression.AND([domain, [('ms_universal_event_id', '=', False)]])
|
||||
if self.env.context.get('full_sync_m', True):
|
||||
domain &= Domain('ms_universal_event_id', '=', False)
|
||||
else:
|
||||
is_active_clause = (self._active_name, '=', True) if self._active_name else expression.TRUE_LEAF
|
||||
domain = expression.AND([domain, [
|
||||
'|',
|
||||
'&', ('ms_universal_event_id', '=', False), is_active_clause,
|
||||
('need_sync_m', '=', True),
|
||||
]])
|
||||
is_active_clause = Domain(self._active_name, '=', True) if self._active_name else Domain.TRUE
|
||||
domain &= (Domain('ms_universal_event_id', '=', False) & is_active_clause) | Domain('need_sync_m', '=', True)
|
||||
# Sync only events created/updated after last sync date (with 5 min of time acceptance).
|
||||
if self.env.user.microsoft_last_sync_date:
|
||||
time_offset = timedelta(minutes=5)
|
||||
domain &= Domain('write_date', '>=', self.env.user.microsoft_last_sync_date - time_offset)
|
||||
return domain
|
||||
|
||||
def _get_event_user_m(self, user_id=None):
|
||||
|
|
@ -572,3 +523,21 @@ class MicrosoftSync(models.AbstractModel):
|
|||
the appropriate user accordingly.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _need_video_call(self):
|
||||
"""
|
||||
Implement this method to return True if the event needs a video call
|
||||
:return: bool
|
||||
"""
|
||||
self.ensure_one()
|
||||
return True
|
||||
|
||||
def _is_microsoft_insertion_blocked(self, sender_user):
|
||||
"""
|
||||
Returns True if the record insertion to Microsoft should be blocked.
|
||||
This is a necessary step for ensuring data match between Odoo and Microsoft,
|
||||
as it prevents attendees to synchronize new records on behalf of the owners,
|
||||
otherwise the event ownership would be lost in Outlook and it would block the
|
||||
future record synchronization for the original owner.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue