# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import json import logging import requests from werkzeug.exceptions import Forbidden from odoo import _, http from odoo.exceptions import UserError from odoo.http import request from odoo.tools import consteq, email_normalize from odoo.addons.google_gmail.models.google_gmail_mixin import GMAIL_TOKEN_REQUEST_TIMEOUT _logger = logging.getLogger(__name__) class GoogleGmailController(http.Controller): @http.route('/google_gmail/confirm', type='http', auth='user') def google_gmail_callback(self, code=None, state=None, error=None, **kwargs): """Callback URL during the OAuth process. Gmail redirects the user browser to this endpoint with the authorization code. We will fetch the refresh token and the access token thanks to this authorization code and save those values on the given mail server. """ if error: _logger.warning("Google Gmail: an error occurred %s", error) return request.render('google_gmail.google_gmail_oauth_error', { 'error': _('An error occurred during the authentication process.'), 'redirect_url': '/odoo', }) try: state = json.loads(state) model_name = state['model'] rec_id = state['id'] csrf_token = state['csrf_token'] except Exception: _logger.error('Google Gmail: Wrong state value %r.', state) raise Forbidden() record_sudo = self._get_gmail_record(model_name, rec_id, csrf_token) try: refresh_token, access_token, expiration = record_sudo._fetch_gmail_refresh_token(code) except UserError as e: return request.render('google_gmail.google_gmail_oauth_error', { 'error': str(e), 'redirect_url': self._get_redirect_url(record_sudo), }) return self._check_email_and_redirect_to_gmail_record(access_token, expiration, refresh_token, record_sudo) @http.route('/google_gmail/iap_confirm', type='http', auth='user') def google_gmail_iap_callback(self, model, rec_id, csrf_token, access_token, refresh_token, expiration): """Receive back the refresh token and access token from IAP. The authentication process with IAP is done in 4 steps; 1. User database make a request to `/api/mail_oauth/1/gmail` 2. User browser is redirected to the URL we received from IAP 3. User browser is redirected to `/api/mail_oauth/1/gmail_callback` with the authorization_code 4. User browser is redirected to `/google_gmail/iap_confirm` """ record = self._get_gmail_record(model, rec_id, csrf_token) return self._check_email_and_redirect_to_gmail_record(access_token, expiration, refresh_token, record) def _get_gmail_record(self, model_name, rec_id, csrf_token): """Return the record after checking the CSRF token.""" model = request.env[model_name] if not isinstance(model, request.env.registry['google.gmail.mixin']): # The model must inherits from the "google.gmail.mixin" mixin _logger.error('Google Gmail: Wrong model %r.', model_name) raise Forbidden() record = model.browse(int(rec_id)).exists().sudo() if not record: _logger.error('Google Gmail: No record found.') raise Forbidden() if not csrf_token or not consteq(csrf_token, record._get_gmail_csrf_token()): _logger.error('Google Gmail: Wrong CSRF token during Gmail authentication.') raise Forbidden() return record def _check_email_and_redirect_to_gmail_record(self, access_token, expiration, refresh_token, record): # Verify the token information (that the email set on the # server is the email used to login on Gmail) if (record._name == 'ir.mail_server' and (record.owner_user_id or not request.env.user.has_group('base.group_system'))): # https://developers.google.com/identity/protocols/oauth2/scopes response = requests.get( 'https://www.googleapis.com/oauth2/v2/userinfo', params={'access_token': access_token}, timeout=GMAIL_TOKEN_REQUEST_TIMEOUT, ) if not response.ok: _logger.error('Google Gmail: Could not verify the token information: %s.', response.text) raise Forbidden() response = response.json() if not response.get('verified_email') or email_normalize(response.get('email')) != email_normalize(record[record._email_field]): _logger.error('Google Gmail: Invalid email address: %r != %s.', response, record[record._email_field]) return request.render('google_gmail.google_gmail_oauth_error', { 'error': _( "Oops, you're creating an authorization to send from %(email_login)s but your address is %(email_server)s. Make sure your addresses match!", email_login=response.get('email'), email_server=record[record._email_field], ), 'redirect_url': self._get_redirect_url(record), }) record.write({ 'active': True, 'google_gmail_access_token': access_token, 'google_gmail_access_token_expiration': expiration, 'google_gmail_refresh_token': refresh_token, }) return request.redirect(self._get_redirect_url(record)) def _get_redirect_url(self, record): """Return the redirect URL for the given record. If the user configured a personal mail server, we redirect him to the user preference view. If it's an admin and that he configured a standard incoming / outgoing mail server, then we redirect it to the mail server form view. """ if ( (record._name != 'ir.mail_server' or record != request.env.user.outgoing_mail_server_id) and request.env.user.has_group('base.group_system') ): return f'/odoo/{record._name}/{record.id}' return f'/odoo/my-preferences/{request.env.user.id}'