From f0d74843f8d2a10e0a69462950543fabe658d703 Mon Sep 17 00:00:00 2001 From: Alexandre Fayolle Date: Tue, 2 Feb 2021 18:50:43 +0100 Subject: [PATCH 1/3] [IMP] connector_office_hr_leave When a manager confirms a leave, sometimes the token has expired and this can generate an error. This PR changes the logic to check the expiration date of the token before synchronizing to Office365 and in case the token is expired we request a login. When the token is refreshed, the pending synchronizations are performed. --- connector_office_365/models/res_users.py | 8 +- .../models/__init__.py | 1 + .../models/hr_leave.py | 76 ++++++++++++++----- .../models/res_users.py | 47 ++++++++++++ 4 files changed, 111 insertions(+), 21 deletions(-) create mode 100644 connector_office_365_hr_leave/models/res_users.py diff --git a/connector_office_365/models/res_users.py b/connector_office_365/models/res_users.py index b0b2b9b7..cdeb0d1a 100644 --- a/connector_office_365/models/res_users.py +++ b/connector_office_365/models/res_users.py @@ -55,7 +55,7 @@ def button_office_365_authenticate(self): self.ensure_one() url = self.office_365_authorization_url( - [ + [ # list of scopes 'User.Read', 'Calendars.ReadWrite', 'offline_access' @@ -84,7 +84,11 @@ def _office_365_get_session(self, scope=None): redirect_uri = config.get_param('web.base.url') + '/office-365-oauth/success' token = None - if self.office_365_access_token and self.office_365_expiration > fields.Datetime.now(): + if ( + self.office_365_access_token and + self.office_365_expiration and + self.office_365_expiration > fields.Datetime.now() + ): token = { 'access_token': self.office_365_access_token, 'refresh_token': self.office_365_refresh_token, diff --git a/connector_office_365_hr_leave/models/__init__.py b/connector_office_365_hr_leave/models/__init__.py index 2866f59c..e508e0a9 100644 --- a/connector_office_365_hr_leave/models/__init__.py +++ b/connector_office_365_hr_leave/models/__init__.py @@ -1,2 +1,3 @@ from . import calendar_event from . import hr_leave +from . import res_users diff --git a/connector_office_365_hr_leave/models/hr_leave.py b/connector_office_365_hr_leave/models/hr_leave.py index 54fbfd04..533c194d 100644 --- a/connector_office_365_hr_leave/models/hr_leave.py +++ b/connector_office_365_hr_leave/models/hr_leave.py @@ -1,8 +1,11 @@ -# Copyright 2019 Camptocamp +# Copyright 2019-2021 Camptocamp # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging -from odoo import api, fields, models, _ -from odoo.exceptions import UserError +from odoo import api, fields, models +from odoo.addons.connector_office_365 import Office365Error + +_logger = logging.getLogger(__name__) class HolidaysRequest(models.Model): @@ -27,23 +30,34 @@ def office_365_authenticate(self): def office_365_manual_push(self): self.ensure_one() + self.sudo().need_o365_manual_push = False self._office_365_push() - self.need_o365_manual_push = False + + def _office_365_activity_require_auth(self): + # create activity to ask user to authenticate + xmlid = ( + 'connector_office_365_hr_leave.' + 'mail_act_office_365_authenticate' + ) + self.sudo().activity_schedule(xmlid, user_id=self.user_id.id) def _office_365_push(self): user = self.meeting_id.user_id - if user.office_365_access_token: - self.meeting_id.sudo(user).with_context( - origin_leave_id=self.id - ).office_365_push() + if user.office_365_access_token and user.office_365_expiration > fields.Datetime.now(): + try: + self.meeting_id.sudo(user).with_context( + origin_leave_id=self.id + ).office_365_push() + except Office365Error as exc: + _logger.warning( + 'Error pushing meeting %d of leave %d: %s', + self.meeting_id.id, self.id, exc + ) + self.sudo().need_o365_manual_push = True else: - self.need_o365_manual_push = True - # create activity to ask user to authenticate - xmlid = ( - 'connector_office_365_hr_leave.' - 'mail_act_office_365_authenticate' - ) - self.activity_schedule(xmlid, user_id=self.user_id.id) + self.sudo().need_o365_manual_push = True + if self.need_o365_manual_push: + self._office_365_activity_require_auth() def _validate_leave_request(self): super()._validate_leave_request() @@ -54,7 +68,31 @@ def _validate_leave_request(self): @api.multi def action_refuse(self): - return super( - HolidaysRequest, - self.with_context(o365_override_user=True) - ).action_refuse() + res = False + activity_scheduled = set() + now = fields.Datetime.now() + leaves_non_expired_tokens = self.filtered( + lambda r: r.meeting_id.user.office_365_access_token and + r.meeting_id.office_365_expiration > now + ) + leaves_expired_tokens = self - leaves_non_expired_tokens + for rec in leaves_non_expired_tokens: + try: + res = super( + HolidaysRequest, + rec.with_context(o365_override_user=True) + ).action_refuse() + except Office365Error: + leaves_expired_tokens += rec + for rec in leaves_expired_tokens: + # don't delete event from office365, create an activity + # requesting the user to auth on office (at most 1 activity per user) + res = super( + HolidaysRequest, + rec.with_context(office_365_force=True) + ).action_refuse() + # don't flood the user with activities + if rec.user_id not in activity_scheduled: + self._office_365_activity_require_auth() + activity_scheduled.add(rec.user_id) + return res diff --git a/connector_office_365_hr_leave/models/res_users.py b/connector_office_365_hr_leave/models/res_users.py new file mode 100644 index 00000000..6043bb96 --- /dev/null +++ b/connector_office_365_hr_leave/models/res_users.py @@ -0,0 +1,47 @@ +# Copyright 2021 Camptocamp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import logging + +from odoo import api, models + +_logger = logging.getLogger(__name__) + + +class ResUsers(models.Model): + _inherit = 'res.users' + + @api.multi + def office_365_persist_token(self, token): + tok = super().office_365_persist_token(token) + self._async_office_365_push_events() + return tok + + def _async_office_365_push_events(self): + self.ensure_one() + try: + leaves = self.env['hr.leave'].search( + [ + ('need_o365_manual_push', '=', True), + ('user_id', '=', self.id), + ] + ) + if leaves: + _logger.info('Pushing pending validated leaves to office365: %s', leaves) + for leave in leaves: + leave.office_365_manual_push() + except Exception: + _logger.exception('unable to push pending validated leaves') + # ignore for now, will be retried later + try: + # delete pending meetings from canceled leaves + leaves = self.env['hr.leave'].search( + [('state', 'in', ('draft', 'cancel')), + ('meeting_id', '!=', False), + ] + ) + if leaves: + _logger.info('Removing calendar events of refused leaves from Office365', leaves) + leaves.with_context(o365_override_user=True).mapped('meeting_id').unlink() + except Exception: + _logger.exception('unable to remove refused leaves events') + # ignore for now, will be retried later From 8c3e1ef6a04ae57c6a1c711d7f0ce2a9e18c8951 Mon Sep 17 00:00:00 2001 From: Alexandre Fayolle Date: Tue, 30 Mar 2021 16:09:47 +0200 Subject: [PATCH 2/3] Update connector_office_365_hr_leave/models/hr_leave.py Co-authored-by: Iryna Vyshnevska <45663766+i-vyshnevska@users.noreply.github.com> --- connector_office_365_hr_leave/models/hr_leave.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector_office_365_hr_leave/models/hr_leave.py b/connector_office_365_hr_leave/models/hr_leave.py index 533c194d..b63b5f9e 100644 --- a/connector_office_365_hr_leave/models/hr_leave.py +++ b/connector_office_365_hr_leave/models/hr_leave.py @@ -93,6 +93,6 @@ def action_refuse(self): ).action_refuse() # don't flood the user with activities if rec.user_id not in activity_scheduled: - self._office_365_activity_require_auth() + rec._office_365_activity_require_auth() activity_scheduled.add(rec.user_id) return res From 11754908661bcb26a188446833270538c738291a Mon Sep 17 00:00:00 2001 From: Alexandre Fayolle Date: Mon, 12 Apr 2021 18:17:14 +0200 Subject: [PATCH 3/3] fixup! Update connector_office_365_hr_leave/models/hr_leave.py --- connector_office_365_hr_leave/models/hr_leave.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector_office_365_hr_leave/models/hr_leave.py b/connector_office_365_hr_leave/models/hr_leave.py index b63b5f9e..3b051f97 100644 --- a/connector_office_365_hr_leave/models/hr_leave.py +++ b/connector_office_365_hr_leave/models/hr_leave.py @@ -72,7 +72,7 @@ def action_refuse(self): activity_scheduled = set() now = fields.Datetime.now() leaves_non_expired_tokens = self.filtered( - lambda r: r.meeting_id.user.office_365_access_token and + lambda r: r.meeting_id.user_id.office_365_access_token and r.meeting_id.office_365_expiration > now ) leaves_expired_tokens = self - leaves_non_expired_tokens