[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:
@@ -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
|
||||
+2
-77
@@ -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 = {}
|
||||
Reference in New Issue
Block a user