123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- # -*- 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
- from werkzeug.exceptions import NotFound
- _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):
- if not request.env.user._is_internal():
- raise NotFound()
- 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
|