Purpose ======= Change all masculine nouns in Odoo's code to neutral nouns (when possible), making sure that demo data is correctly handled. This is particularly important since our code is open source, and nowadays lots of machine learning models are trained on open source repositories. With this small change we contribute to training more "fair" models, and teaching models that "employee" or "user" != "he". This also affects some text visible by the user, hence making it more inclusive for Odoo users. Task-2853046 closes odoo/odoo#91292 Related: odoo/enterprise#27302 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
107 lines
5.2 KiB
Python
107 lines
5.2 KiB
Python
# -*- 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', '/mail_plugin/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`.
|
|
|
|
old route name "/mail_client_extension/auth is deprecated as of saas-14.3,it is not needed for newer
|
|
versions of the mail plugin but necessary for supporting older versions
|
|
"""
|
|
return request.render('mail_plugin.app_auth', values)
|
|
|
|
@http.route(['/mail_client_extension/auth/confirm', '/mail_plugin/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 they are 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/auth/access_token`.
|
|
|
|
old route name "/mail_client_extension/auth/confirm is deprecated as of saas-14.3,it is not needed for newer
|
|
versions of the mail plugin but necessary for supporting older versions
|
|
"""
|
|
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
|
|
# the state attribute is needed for the gmail connector
|
|
params.update({'success': 1, 'auth_code': auth_code, 'state': kw.get('state', '')})
|
|
else:
|
|
params.update({'success': 0, 'state': kw.get('state', '')})
|
|
updated_redirect = parsed_redirect.replace(query=werkzeug.urls.url_encode(params))
|
|
return request.redirect(updated_redirect.to_url(), local=False)
|
|
|
|
# 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', '/mail_plugin/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
|
|
|
|
old route name "/mail_client_extension/auth/access_token is deprecated as of saas-14.3,it is not needed for newer
|
|
versions of the mail plugin but necessary for supporting older versions
|
|
"""
|
|
if not auth_code:
|
|
return {"error": "Invalid code"}
|
|
auth_message = self._get_auth_code_data(auth_code)
|
|
if not auth_message:
|
|
return {"error": "Invalid code"}
|
|
request.update_env(user=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_plugin', 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_plugin', 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
|