19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:28 +01:00
parent 20ddc1b4a3
commit c0efcc53f5
1162 changed files with 125577 additions and 105287 deletions

View file

@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import json
import logging
import werkzeug
from werkzeug.exceptions import Forbidden
from odoo import http
from odoo import _, http
from odoo.exceptions import UserError
from odoo.http import request
from odoo.tools import consteq
from odoo.tools import consteq, email_normalize
_logger = logging.getLogger(__name__)
@ -24,9 +24,12 @@ class MicrosoftOutlookController(http.Controller):
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 not request.env.user.has_group('base.group_system'):
_logger.error('Microsoft Outlook: Non system user try to link an Outlook account.')
raise Forbidden()
if error_description:
_logger.warning("Microsoft Outlook: an error occurred %s", error_description)
return request.render('microsoft_outlook.microsoft_outlook_oauth_error', {
'error': error_description,
'redirect_url': '/odoo',
})
try:
state = json.loads(state)
@ -37,40 +40,97 @@ class MicrosoftOutlookController(http.Controller):
_logger.error('Microsoft Outlook: Wrong state value %r.', state)
raise Forbidden()
if error_description:
record_sudo = self._get_outlook_record(model_name, rec_id, csrf_token)
try:
refresh_token, access_token, expiration = record_sudo._fetch_outlook_refresh_token(code)
except UserError as e:
return request.render('microsoft_outlook.microsoft_outlook_oauth_error', {
'error': error_description,
'model_name': model_name,
'rec_id': rec_id,
'error': str(e),
'redirect_url': self._get_redirect_url(record_sudo),
})
return self._check_email_and_redirect_to_outlook_record(access_token, expiration, refresh_token, record_sudo)
@http.route('/microsoft_outlook/iap_confirm', type='http', auth='user')
def microsoft_outlook_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 `<IAP>/api/mail_oauth/1/outlook`
2. User browser is redirected to the URL we received from IAP
3. User browser is redirected to `<IAP>/api/mail_oauth/1/outlook_callback`
with the authorization_code
4. User browser is redirected to `<DB>/microsoft_outlook/iap_confirm`
"""
record = self._get_outlook_record(model, rec_id, csrf_token)
return self._check_email_and_redirect_to_outlook_record(access_token, expiration, refresh_token, record)
def _get_outlook_record(self, model_name, rec_id, csrf_token):
"""Return the given record after checking the CSRF token."""
model = request.env[model_name]
if not isinstance(model, request.env.registry['microsoft.outlook.mixin']):
# The model must inherits from the "microsoft.outlook.mixin" mixin
_logger.error('Microsoft Outlook: Wrong model %r.', model_name)
raise Forbidden()
record = model.browse(rec_id).exists()
record = model.browse(int(rec_id)).exists().sudo()
if not record:
_logger.error('Microsoft Outlook: Record not found.')
raise Forbidden()
if not csrf_token or not consteq(csrf_token, record._get_outlook_csrf_token()):
_logger.error('Microsoft Outlook: Wrong CSRF token during Outlook authentication.')
raise Forbidden()
try:
refresh_token, access_token, expiration = record._fetch_outlook_refresh_token(code)
except UserError as e:
return request.render('microsoft_outlook.microsoft_outlook_oauth_error', {
'error': str(e.name),
'model_name': model_name,
'rec_id': rec_id,
})
return record
def _check_email_and_redirect_to_outlook_record(self, access_token, expiration, refresh_token, record):
if (record._name == 'ir.mail_server' and (record.owner_user_id or not request.env.user.has_group('base.group_system'))):
# Verify the token information (that the email set on the
# server is the email used to login on Outlook)
# We can not directly get the id_token from the response, even if we verify the signature
# because it comes from the user's browser redirection, and he could give an id_token of one account
# and the refresh_token of a different account.
# So we ask a new token to the outlook API to check the email address
# Because we received the JWT token from the API (or from IAP, with a direct HTTP request),
# we don't even need to check the signature
refresh_token, access_token, id_token, expiration = record._fetch_outlook_access_token(refresh_token)
id_token_data = id_token.split(".")[1]
id_token_data += '=' * (-len(id_token_data) % 4) # `=` padding can be missing
email = json.loads(base64.b64decode(id_token_data)).get('email')
if not email or email_normalize(email) != email_normalize(record[record._email_field]):
_logger.error('Microsoft Outlook: Invalid email address: %r != %s.', email, record[record._email_field])
return request.render('microsoft_outlook.microsoft_outlook_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=email,
email_server=record[record._email_field],
),
'redirect_url': self._get_redirect_url(record),
})
record.write({
'active': True,
'microsoft_outlook_refresh_token': refresh_token,
'microsoft_outlook_access_token': access_token,
'microsoft_outlook_access_token_expiration': expiration,
})
return request.redirect(self._get_redirect_url(record))
return request.redirect(f'/web?#id={rec_id}&model={model_name}&view_type=form')
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}'