[MOV] mail_client_extension: split authentication and main controller code

This commit splits the code located in the mail_client_extension module into
two separate files, an "authentication.py" file containing authentication
related code and a "mail_client_extension.py" file containing everything else.

Purpose is to better organize the code and to avoid having a bloated main
controller.

This commit does not change anything functionally, it only moves and refactors
code.

Task-2382870

UPG-PR: https://github.com/odoo/upgrade/pull/2156
COM-PR: https://github.com/odoo/odoo/pull/66139
This commit is contained in:
nounoubensebia
2021-03-29 11:59:10 +00:00
parent 2bf049fd50
commit fa26aaaa07
3 changed files with 101 additions and 79 deletions
@@ -1,2 +1,5 @@
# -*- coding: utf-8 -*
from . import main
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import authenticate
from . import mail_plugin
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import datetime
import hmac
import json
import logging
import odoo
import werkzeug
from odoo import http
from odoo.http import request
_logger = logging.getLogger(__name__)
class Authenticate(http.Controller):
@http.route('/mail_client_extension/auth', type='http', auth="user", methods=['GET'], website=True)
def auth(self, **values):
"""
Once authenticated this route renders the view that shows an app wants to access Odoo.
The user is invited to allow or deny the app. The form posts to `/mail_client_extension/auth/confirm`.
"""
return request.render('mail_client_extension.app_auth', values)
@http.route('/mail_client_extension/auth/confirm', type='http', auth="user", methods=['POST'])
def auth_confirm(self, scope, friendlyname, redirect, info=None, do=None, **kw):
"""
Called by the `app_auth` template. If the user decided to allow the app to access Odoo, a temporary auth code
is generated and he is redirected to `redirect` with this code in the URL. It should redirect to the app, and
the app should then exchange this auth code for an access token by calling
`/mail_client_extension/auth/access_token`.
"""
parsed_redirect = werkzeug.urls.url_parse(redirect)
params = parsed_redirect.decode_query()
if do:
name = friendlyname if not info else f'{friendlyname}: {info}'
auth_code = self._generate_auth_code(scope, name)
# params is a MultiDict which does not support .update() with kwargs
params.update({'success': 1, 'auth_code': auth_code})
else:
params['success'] = 0
updated_redirect = parsed_redirect.replace(query=werkzeug.urls.url_encode(params))
return werkzeug.utils.redirect(updated_redirect.to_url())
# In this case, an exception will be thrown in case of preflight request if only POST is allowed.
@http.route('/mail_client_extension/auth/access_token', type='json', auth="none", cors="*",
methods=['POST', 'OPTIONS'])
def auth_access_token(self, auth_code, **kw):
"""
Called by the external app to exchange an auth code, which is temporary and was passed in a URL, for an
access token, which is permanent, and can be used in the `Authorization` header to authorize subsequent requests
"""
auth_message = self._get_auth_code_data(auth_code)
if not auth_message:
return {"error": "Invalid code"}
request.uid = auth_message['uid']
scope = 'odoo.plugin.' + auth_message.get('scope', '')
api_key = request.env['res.users.apikeys']._generate(scope, auth_message['name'])
return {'access_token': api_key}
def _get_auth_code_data(self, auth_code):
data, auth_code_signature = auth_code.split('.')
data = base64.b64decode(data)
auth_code_signature = base64.b64decode(auth_code_signature)
signature = odoo.tools.misc.hmac(request.env(su=True), 'mail_client_extension', data).encode()
if not hmac.compare_digest(auth_code_signature, signature):
return None
auth_message = json.loads(data)
# Check the expiration
if datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(auth_message['timestamp']) > datetime.timedelta(
minutes=3):
return None
return auth_message
# Using UTC explicitly in case of a distributed system where the generation and the signature verification do not
# necessarily happen on the same server
def _generate_auth_code(self, scope, name):
auth_dict = {
'scope': scope,
'name': name,
'timestamp': int(datetime.datetime.utcnow().timestamp()),
# <- elapsed time should be < 3 mins when verifying
'uid': request.uid,
}
auth_message = json.dumps(auth_dict, sort_keys=True).encode()
signature = odoo.tools.misc.hmac(request.env(su=True), 'mail_client_extension', auth_message).encode()
auth_code = "%s.%s" % (base64.b64encode(auth_message).decode(), base64.b64encode(signature).decode())
_logger.info('Auth code created - user %s, scope %s', request.env.user, scope)
return auth_code
@@ -1,11 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import datetime
import hmac
import json
import logging
import odoo
import requests
import werkzeug
@@ -17,80 +15,7 @@ from odoo.tools.misc import formatLang
_logger = logging.getLogger(__name__)
class MailClientExtensionController(http.Controller):
@http.route('/mail_client_extension/auth', type='http', auth="user", methods=['GET'], website=True)
def auth(self, **values):
"""
Once authenticated this route renders the view that shows an app wants to access Odoo.
The user is invited to allow or deny the app. The form posts to `/mail_client_extension/auth/confirm`.
"""
return request.render('mail_client_extension.app_auth', values)
@http.route('/mail_client_extension/auth/confirm', type='http', auth="user", methods=['POST'])
def auth_confirm(self, scope, friendlyname, redirect, info=None, do=None, **kw):
"""
Called by the `app_auth` template. If the user decided to allow the app to access Odoo, a temporary auth code
is generated and he is redirected to `redirect` with this code in the URL. It should redirect to the app, and
the app should then exchange this auth code for an access token by calling
`/mail_client_extension/auth/access_token`.
"""
parsed_redirect = werkzeug.urls.url_parse(redirect)
params = parsed_redirect.decode_query()
if do:
name = friendlyname if not info else f'{friendlyname}: {info}'
auth_code = self._generate_auth_code(scope, name)
# params is a MultiDict which does not support .update() with kwargs
params.update({'success': 1, 'auth_code': auth_code})
else:
params['success'] = 0
updated_redirect = parsed_redirect.replace(query=werkzeug.urls.url_encode(params))
return werkzeug.utils.redirect(updated_redirect.to_url())
# In this case, an exception will be thrown in case of preflight request if only POST is allowed.
@http.route('/mail_client_extension/auth/access_token', type='json', auth="none", cors="*", methods=['POST', 'OPTIONS'])
def auth_access_token(self, auth_code, **kw):
"""
Called by the external app to exchange an auth code, which is temporary and was passed in a URL, for an
access token, which is permanent, and can be used in the `Authorization` header to authorize subsequent requests
"""
auth_message = self._get_auth_code_data(auth_code)
if not auth_message:
return {"error": "Invalid code"}
request.uid = auth_message['uid']
scope = 'odoo.plugin.' + auth_message.get('scope', '')
api_key = request.env['res.users.apikeys']._generate(scope, auth_message['name'])
return {'access_token': api_key }
def _get_auth_code_data(self, auth_code):
data, auth_code_signature = auth_code.split('.')
data = base64.b64decode(data)
auth_code_signature = base64.b64decode(auth_code_signature)
signature = odoo.tools.misc.hmac(request.env(su=True), 'mail_client_extension', data).encode()
if not hmac.compare_digest(auth_code_signature, signature):
return None
auth_message = json.loads(data)
# Check the expiration
if datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(auth_message['timestamp']) > datetime.timedelta(minutes=3):
return None
return auth_message
# Using UTC explicitly in case of a distributed system where the generation and the signature verification do not
# necessarily happen on the same server
def _generate_auth_code(self, scope, name):
auth_dict = {
'scope': scope,
'name': name,
'timestamp': int(datetime.datetime.utcnow().timestamp()), # <- elapsed time should be < 3 mins when verifying
'uid': request.uid,
}
auth_message = json.dumps(auth_dict, sort_keys=True).encode()
signature = odoo.tools.misc.hmac(request.env(su=True), 'mail_client_extension', auth_message).encode()
auth_code = "%s.%s" % (base64.b64encode(auth_message).decode(), base64.b64encode(signature).decode())
_logger.info('Auth code created - user %s, scope %s', request.env.user, scope)
return auth_code
class MailPluginController(http.Controller):
def _iap_enrich(self, domain):
enriched_data = {}