[IMP] google_calendar: move credentials from res_user table to a dedicated table
Before this commit, several token related data were stored on the res_users table. During event sync, the table could be locked and this commit aims to move these data to a dedicated table to avoid bottleneck effets. Taskid: 2484335 Part-of: odoo/odoo#68700
This commit is contained in:
@@ -20,7 +20,7 @@ class GoogleAuth(http.Controller):
|
||||
if kw.get('code'):
|
||||
access_token, refresh_token, ttl = request.env['google.service']._get_google_tokens(kw['code'], service)
|
||||
# LUL TODO only defined in google_calendar
|
||||
request.env.user._set_auth_tokens(access_token, refresh_token, ttl)
|
||||
request.env.user.google_cal_account_id._set_auth_tokens(access_token, refresh_token, ttl)
|
||||
return request.redirect(url_return)
|
||||
elif kw.get('error'):
|
||||
return request.redirect("%s%s%s" % (url_return, "?error=", kw['error']))
|
||||
|
||||
@@ -7,3 +7,4 @@ from . import calendar
|
||||
from . import calendar_recurrence_rule
|
||||
from . import res_users
|
||||
from . import calendar_attendee
|
||||
from . import google_credentials
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import requests
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.addons.google_account.models.google_service import GOOGLE_TOKEN_ENDPOINT
|
||||
from odoo.addons.google_calendar.utils.google_calendar import GoogleCalendarService, InvalidSyncToken
|
||||
from odoo.addons.google_calendar.models.google_sync import google_calendar_token
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class GoogleCredentials(models.Model):
|
||||
""""Google Account of res_users"""
|
||||
|
||||
_name = 'google.calendar.credentials'
|
||||
_description = 'Google Calendar Account Data'
|
||||
|
||||
user_ids = fields.One2many('res.users', 'google_cal_account_id', required=True)
|
||||
calendar_rtoken = fields.Char('Refresh Token', copy=False, groups="base.group_system")
|
||||
calendar_token = fields.Char('User token', copy=False, groups="base.group_system")
|
||||
calendar_token_validity = fields.Datetime('Token Validity', copy=False, groups="base.group_system")
|
||||
calendar_sync_token = fields.Char('Next Sync Token', copy=False, groups="base.group_system")
|
||||
calendar_cal_id = fields.Char('Calendar ID', copy=False, help='Last Calendar ID who has been synchronized. If it is changed, we remove all links between GoogleID and Odoo Google Internal ID')
|
||||
synchronization_stopped = fields.Boolean('Google Synchronization stopped', copy=False)
|
||||
|
||||
def _set_auth_tokens(self, access_token, refresh_token, ttl):
|
||||
self.write({
|
||||
'calendar_rtoken': refresh_token,
|
||||
'calendar_token': access_token,
|
||||
'calendar_token_validity': fields.Datetime.now() + timedelta(seconds=ttl) if ttl else False,
|
||||
})
|
||||
|
||||
def _google_calendar_authenticated(self):
|
||||
self.ensure_one()
|
||||
return bool(self.sudo().calendar_rtoken)
|
||||
|
||||
def _is_google_calendar_valid(self):
|
||||
self.ensure_one()
|
||||
return self.calendar_token_validity and self.calendar_token_validity >= (fields.Datetime.now() + timedelta(minutes=1))
|
||||
|
||||
def _refresh_google_calendar_token(self):
|
||||
# LUL TODO similar code exists in google_drive. Should be factorized in google_account
|
||||
self.ensure_one()
|
||||
get_param = self.env['ir.config_parameter'].sudo().get_param
|
||||
client_id = get_param('google_calendar_client_id')
|
||||
client_secret = get_param('google_calendar_client_secret')
|
||||
|
||||
if not client_id or not client_secret:
|
||||
raise UserError(_("The account for the Google Calendar service is not configured."))
|
||||
|
||||
headers = {"content-type": "application/x-www-form-urlencoded"}
|
||||
data = {
|
||||
'refresh_token': self.calendar_rtoken,
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'grant_type': 'refresh_token',
|
||||
}
|
||||
|
||||
try:
|
||||
_dummy, response, _dummy = self.env['google.service']._do_request(GOOGLE_TOKEN_ENDPOINT, params=data, headers=headers, method='POST', preuri='')
|
||||
ttl = response.get('expires_in')
|
||||
self.write({
|
||||
'calendar_token': response.get('access_token'),
|
||||
'calendar_token_validity': fields.Datetime.now() + timedelta(seconds=ttl),
|
||||
})
|
||||
except requests.HTTPError as error:
|
||||
if error.response.status_code in (400, 401): # invalid grant or invalid client
|
||||
# Delete refresh token and make sure it's commited
|
||||
self.env.cr.rollback()
|
||||
self._set_auth_tokens(False, False, 0)
|
||||
self.env.cr.commit()
|
||||
error_key = error.response.json().get("error", "nc")
|
||||
error_msg = _("An error occurred while generating the token. Your authorization code may be invalid or has already expired [%s]. "
|
||||
"You should check your Client ID and secret on the Google APIs plateform or try to stop and restart your calendar synchronisation.",
|
||||
error_key)
|
||||
raise UserError(error_msg)
|
||||
|
||||
def _sync_google_calendar(self, calendar_service: GoogleCalendarService):
|
||||
self.ensure_one()
|
||||
if self.synchronization_stopped:
|
||||
return False
|
||||
|
||||
# don't attempt to sync when another sync is already in progress, as we wouldn't be
|
||||
# able to commit the transaction anyway (row is locked)
|
||||
self.env.cr.execute("""SELECT id FROM res_users WHERE id = %s FOR NO KEY UPDATE SKIP LOCKED""", [self.id])
|
||||
if not self.env.cr.rowcount:
|
||||
_logger.info("skipping calendar sync, locked user %s", self.login)
|
||||
return False
|
||||
|
||||
full_sync = not bool(self.calendar_sync_token)
|
||||
with google_calendar_token(self) as token:
|
||||
try:
|
||||
events, next_sync_token, default_reminders = calendar_service.get_events(self.calendar_sync_token, token=token)
|
||||
except InvalidSyncToken:
|
||||
events, next_sync_token, default_reminders = calendar_service.get_events(token=token)
|
||||
full_sync = True
|
||||
self.calendar_sync_token = next_sync_token
|
||||
|
||||
# Google -> Odoo
|
||||
events.clear_type_ambiguity(self.env)
|
||||
recurrences = events.filter(lambda e: e.is_recurrence())
|
||||
synced_recurrences = self.env['calendar.recurrence']._sync_google2odoo(recurrences)
|
||||
synced_events = self.env['calendar.event']._sync_google2odoo(events - recurrences, default_reminders=default_reminders)
|
||||
|
||||
# Odoo -> Google
|
||||
recurrences = self.env['calendar.recurrence']._get_records_to_sync(full_sync=full_sync)
|
||||
recurrences -= synced_recurrences
|
||||
recurrences._sync_odoo2google(calendar_service)
|
||||
synced_events |= recurrences.calendar_event_ids - recurrences._get_outliers()
|
||||
events = self.env['calendar.event']._get_records_to_sync(full_sync=full_sync)
|
||||
(events - synced_events)._sync_odoo2google(calendar_service)
|
||||
|
||||
return bool(events | synced_events) or bool(recurrences | synced_recurrences)
|
||||
@@ -2,90 +2,44 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import requests
|
||||
from odoo.addons.google_calendar.models.google_sync import google_calendar_token
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.loglevels import exception_to_unicode
|
||||
from odoo.addons.google_account.models.google_service import GOOGLE_TOKEN_ENDPOINT
|
||||
from odoo import api, fields, models, Command
|
||||
from odoo.addons.google_calendar.utils.google_calendar import GoogleCalendarService, InvalidSyncToken
|
||||
from odoo.addons.google_calendar.models.google_sync import google_calendar_token
|
||||
from odoo.loglevels import exception_to_unicode
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class User(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
google_calendar_rtoken = fields.Char('Refresh Token', copy=False, groups="base.group_system")
|
||||
google_calendar_token = fields.Char('User token', copy=False, groups="base.group_system")
|
||||
google_calendar_token_validity = fields.Datetime('Token Validity', copy=False)
|
||||
google_calendar_sync_token = fields.Char('Next Sync Token', copy=False)
|
||||
google_calendar_cal_id = fields.Char('Calendar ID', copy=False, help='Last Calendar ID who has been synchronized. If it is changed, we remove all links between GoogleID and Odoo Google Internal ID')
|
||||
google_synchronization_stopped = fields.Boolean('Google Synchronization stopped', copy=False)
|
||||
google_cal_account_id = fields.Many2one('google.calendar.credentials')
|
||||
google_calendar_rtoken = fields.Char(related='google_cal_account_id.calendar_rtoken', groups="base.group_system")
|
||||
google_calendar_token = fields.Char(related='google_cal_account_id.calendar_token')
|
||||
google_calendar_token_validity = fields.Datetime(related='google_cal_account_id.calendar_token_validity')
|
||||
google_calendar_sync_token = fields.Char(related='google_cal_account_id.calendar_sync_token')
|
||||
google_calendar_cal_id = fields.Char(related='google_cal_account_id.calendar_cal_id')
|
||||
google_synchronization_stopped = fields.Boolean(related='google_cal_account_id.synchronization_stopped', readonly=False)
|
||||
|
||||
_sql_constraints = [
|
||||
('google_token_uniq', 'unique (google_cal_account_id)', "The user has already a google account"),
|
||||
]
|
||||
|
||||
|
||||
@property
|
||||
def SELF_READABLE_FIELDS(self):
|
||||
return super().SELF_READABLE_FIELDS + ['google_synchronization_stopped']
|
||||
return super().SELF_READABLE_FIELDS + ['google_synchronization_stopped', 'google_cal_account_id']
|
||||
|
||||
@property
|
||||
def SELF_WRITEABLE_FIELDS(self):
|
||||
return super().SELF_WRITEABLE_FIELDS + ['google_synchronization_stopped']
|
||||
|
||||
def _set_auth_tokens(self, access_token, refresh_token, ttl):
|
||||
self.write({
|
||||
'google_calendar_rtoken': refresh_token,
|
||||
'google_calendar_token': access_token,
|
||||
'google_calendar_token_validity': fields.Datetime.now() + timedelta(seconds=ttl) if ttl else False,
|
||||
})
|
||||
|
||||
def _google_calendar_authenticated(self):
|
||||
return bool(self.sudo().google_calendar_rtoken)
|
||||
return super().SELF_WRITEABLE_FIELDS + ['google_synchronization_stopped', 'google_cal_account_id']
|
||||
|
||||
def _get_google_calendar_token(self):
|
||||
self.ensure_one()
|
||||
if self.google_calendar_rtoken and not self._is_google_calendar_valid():
|
||||
self._refresh_google_calendar_token()
|
||||
return self.google_calendar_token
|
||||
|
||||
def _is_google_calendar_valid(self):
|
||||
return self.google_calendar_token_validity and self.google_calendar_token_validity >= (fields.Datetime.now() + timedelta(minutes=1))
|
||||
|
||||
def _refresh_google_calendar_token(self):
|
||||
# LUL TODO similar code exists in google_drive. Should be factorized in google_account
|
||||
self.ensure_one()
|
||||
get_param = self.env['ir.config_parameter'].sudo().get_param
|
||||
client_id = get_param('google_calendar_client_id')
|
||||
client_secret = get_param('google_calendar_client_secret')
|
||||
|
||||
if not client_id or not client_secret:
|
||||
raise UserError(_("The account for the Google Calendar service is not configured."))
|
||||
|
||||
headers = {"content-type": "application/x-www-form-urlencoded"}
|
||||
data = {
|
||||
'refresh_token': self.google_calendar_rtoken,
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'grant_type': 'refresh_token',
|
||||
}
|
||||
|
||||
try:
|
||||
dummy, response, dummy = self.env['google.service']._do_request(GOOGLE_TOKEN_ENDPOINT, params=data, headers=headers, method='POST', preuri='')
|
||||
ttl = response.get('expires_in')
|
||||
self.write({
|
||||
'google_calendar_token': response.get('access_token'),
|
||||
'google_calendar_token_validity': fields.Datetime.now() + timedelta(seconds=ttl),
|
||||
})
|
||||
except requests.HTTPError as error:
|
||||
if error.response.status_code in (400, 401): # invalid grant or invalid client
|
||||
# Delete refresh token and make sure it's commited
|
||||
self.env.cr.rollback()
|
||||
self._set_auth_tokens(False, False, 0)
|
||||
self.env.cr.commit()
|
||||
error_key = error.response.json().get("error", "nc")
|
||||
error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired [%s]", error_key)
|
||||
raise UserError(error_msg)
|
||||
if self.google_cal_account_id.calendar_rtoken and not self.google_cal_account_id._is_google_calendar_valid():
|
||||
self.sudo().google_cal_account_id._refresh_google_calendar_token()
|
||||
return self.google_cal_account_id.calendar_token
|
||||
|
||||
def _sync_google_calendar(self, calendar_service: GoogleCalendarService):
|
||||
self.ensure_one()
|
||||
@@ -102,11 +56,11 @@ class User(models.Model):
|
||||
full_sync = not bool(self.google_calendar_sync_token)
|
||||
with google_calendar_token(self) as token:
|
||||
try:
|
||||
events, next_sync_token, default_reminders = calendar_service.get_events(self.google_calendar_sync_token, token=token)
|
||||
events, next_sync_token, default_reminders = calendar_service.get_events(self.google_cal_account_id.calendar_sync_token, token=token)
|
||||
except InvalidSyncToken:
|
||||
events, next_sync_token, default_reminders = calendar_service.get_events(token=token)
|
||||
full_sync = True
|
||||
self.google_calendar_sync_token = next_sync_token
|
||||
self.google_cal_account_id.calendar_sync_token = next_sync_token
|
||||
|
||||
# Google -> Odoo
|
||||
events.clear_type_ambiguity(self.env)
|
||||
@@ -144,6 +98,8 @@ class User(models.Model):
|
||||
|
||||
def restart_google_synchronization(self):
|
||||
self.ensure_one()
|
||||
if not self.google_cal_account_id:
|
||||
self.google_cal_account_id = self.env['google.calendar.credentials'].sudo().create([{'user_ids': [Command.set(self.ids)]}])
|
||||
self.google_synchronization_stopped = False
|
||||
self.env['calendar.recurrence']._restart_google_sync()
|
||||
self.env['calendar.event']._restart_google_sync()
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
google_calendar_account_reset,google_calendar_account_reset_access_right,model_google_calendar_account_reset,base.group_system,1,1,1,0
|
||||
google_calendar.access_google_calendar_credentials,access_google_calendar_credentials,google_calendar.model_google_calendar_credentials,base.group_user,1,0,0,0
|
||||
|
||||
|
Reference in New Issue
Block a user