From 6048902d6ed6b4969fb2639b24ac780967c8f32b Mon Sep 17 00:00:00 2001 From: std-odoo Date: Thu, 3 Mar 2022 08:29:00 +0000 Subject: [PATCH 1/3] [FIX] google_gmail: do not copy the authorization code Bug === When we copy a GMail outgoing / incoming mail server, an error occurs because we try to refetch the access token, based on the same authorization code (which can be used only once). To fix this issue, we do not copy the authorization code (and other related fields). Task-2751996 X-original-commit: 2879976160bc0777cde1368ed2b69ce821c85ff1 Part-of: odoo/odoo#87498 --- addons/google_gmail/models/google_gmail_mixin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/google_gmail/models/google_gmail_mixin.py b/addons/google_gmail/models/google_gmail_mixin.py index 2426568327d91..c128e994edad9 100644 --- a/addons/google_gmail/models/google_gmail_mixin.py +++ b/addons/google_gmail/models/google_gmail_mixin.py @@ -17,10 +17,10 @@ class GoogleGmailMixin(models.AbstractModel): _SERVICE_SCOPE = 'https://mail.google.com/' use_google_gmail_service = fields.Boolean('Gmail Authentication') - google_gmail_authorization_code = fields.Char(string='Authorization Code', groups='base.group_system') - google_gmail_refresh_token = fields.Char(string='Refresh Token', groups='base.group_system') - google_gmail_access_token = fields.Char(string='Access Token', groups='base.group_system') - google_gmail_access_token_expiration = fields.Integer(string='Access Token Expiration Timestamp', groups='base.group_system') + google_gmail_authorization_code = fields.Char(string='Authorization Code', groups='base.group_system', copy=False) + google_gmail_refresh_token = fields.Char(string='Refresh Token', groups='base.group_system', copy=False) + google_gmail_access_token = fields.Char(string='Access Token', groups='base.group_system', copy=False) + google_gmail_access_token_expiration = fields.Integer(string='Access Token Expiration Timestamp', groups='base.group_system', copy=False) google_gmail_uri = fields.Char(compute='_compute_gmail_uri', string='URI', help='The URL to generate the authorization code from Google', groups='base.group_system') @api.depends('google_gmail_authorization_code') From 28ca6c2e82190fdff76c817c0a16cc42968a275a Mon Sep 17 00:00:00 2001 From: std-odoo Date: Wed, 30 Mar 2022 08:58:42 +0000 Subject: [PATCH 2/3] [IMP] {microsoft, fetchmail}_outlook: do not reset the email config Purpose ======= Do not reset the email configuration when unchecking "is Gmail". Task-2751996 Part-of: odoo/odoo#87498 --- addons/fetchmail_gmail/models/fetchmail_server.py | 2 -- addons/google_gmail/models/ir_mail_server.py | 1 - 2 files changed, 3 deletions(-) diff --git a/addons/fetchmail_gmail/models/fetchmail_server.py b/addons/fetchmail_gmail/models/fetchmail_server.py index 5dffbfea53f0d..3d3db306aabe8 100644 --- a/addons/fetchmail_gmail/models/fetchmail_server.py +++ b/addons/fetchmail_gmail/models/fetchmail_server.py @@ -23,8 +23,6 @@ def _onchange_use_google_gmail_service(self): self.is_ssl = True self.port = 993 else: - self.server_type = 'pop' - self.is_ssl = False self.google_gmail_authorization_code = False self.google_gmail_refresh_token = False self.google_gmail_access_token = False diff --git a/addons/google_gmail/models/ir_mail_server.py b/addons/google_gmail/models/ir_mail_server.py index 5f6dd40f54a6a..93b51e24e3191 100644 --- a/addons/google_gmail/models/ir_mail_server.py +++ b/addons/google_gmail/models/ir_mail_server.py @@ -27,7 +27,6 @@ def _onchange_use_google_gmail_service(self): self.smtp_encryption = 'starttls' self.smtp_port = 587 else: - self.smtp_encryption = 'none' self.google_gmail_authorization_code = False self.google_gmail_refresh_token = False self.google_gmail_access_token = False From 1e1a494aa587e6f1a8637e0514079eab9680f905 Mon Sep 17 00:00:00 2001 From: std-odoo Date: Wed, 2 Feb 2022 08:21:33 +0000 Subject: [PATCH 3/3] [ADD] fetchmail_outlook, microsoft_outlook: add OAuth authentication Purpose ======= As it has been done for Gmail, we want to add the OAuth authentication for the incoming / outgoing mail server. Specifications ============== The user has to create a project on Outlook and fill the credentials in Odoo. Once it's done, he can create an incoming / outgoing mail server. For the authentication flow is a bit different from Gmail. For Outlook the user is redirected to Outlook where he'll accept the permission. Once it's done, he's redirected again to the mail server form view and the tokens are automatically added on the mail server. Technical ========= There are 3 tokens used for the OAuth authentication. 1. The authentication code. This one is only used to get the refresh token and the first access token. It's the code returned by the user browser during the authentication flow. 2. The refresh token. This one will never change once the user is authenticated. This token is used to get new access token once they are expired. 3. The access token. Those tokens have an expiration date (1 hour) and are used in the XOAUTH2 protocol to authenticate the IMAP / SMTP connection. During the authentication process, we can also give a state that will be returned by the user browser. This state contains 1. The model and the ID of the mail server (as the same mixin manage both incoming and outgoing mail server) 2. A CSRF token which sign those values and is verified once the browser redirect the user to the Odoo database. This is useful so a malicious user can not send a link to an admin to disconnect the mail server. Task-2751996 X-original-commit: e54d63b3c0f39fd8a05e430442cf84d1d6c8de78 Part-of: odoo/odoo#87498 --- .tx/config | 10 + .../views/fetchmail_server_views.xml | 1 + addons/fetchmail_outlook/__init__.py | 4 + addons/fetchmail_outlook/__manifest__.py | 17 ++ .../i18n/fetchmail_outlook.pot | 125 +++++++++++ addons/fetchmail_outlook/models/__init__.py | 4 + .../models/fetchmail_server.py | 58 +++++ addons/fetchmail_outlook/tests/__init__.py | 4 + .../tests/test_fetchmail_outlook.py | 59 +++++ .../views/fetchmail_server_views.xml | 47 ++++ addons/microsoft_outlook/__init__.py | 5 + addons/microsoft_outlook/__manifest__.py | 18 ++ .../microsoft_outlook/controllers/__init__.py | 4 + addons/microsoft_outlook/controllers/main.py | 76 +++++++ .../i18n/microsoft_outlook.pot | 209 ++++++++++++++++++ addons/microsoft_outlook/models/__init__.py | 7 + .../models/ir_mail_server.py | 62 ++++++ .../models/microsoft_outlook_mixin.py | 188 ++++++++++++++++ .../models/res_config_settings.py | 11 + .../views/ir_mail_server_views.xml | 42 ++++ .../views/res_config_settings_views.xml | 35 +++ addons/microsoft_outlook/views/templates.xml | 15 ++ 22 files changed, 1001 insertions(+) create mode 100644 addons/fetchmail_outlook/__init__.py create mode 100644 addons/fetchmail_outlook/__manifest__.py create mode 100644 addons/fetchmail_outlook/i18n/fetchmail_outlook.pot create mode 100644 addons/fetchmail_outlook/models/__init__.py create mode 100644 addons/fetchmail_outlook/models/fetchmail_server.py create mode 100644 addons/fetchmail_outlook/tests/__init__.py create mode 100644 addons/fetchmail_outlook/tests/test_fetchmail_outlook.py create mode 100644 addons/fetchmail_outlook/views/fetchmail_server_views.xml create mode 100644 addons/microsoft_outlook/__init__.py create mode 100644 addons/microsoft_outlook/__manifest__.py create mode 100644 addons/microsoft_outlook/controllers/__init__.py create mode 100644 addons/microsoft_outlook/controllers/main.py create mode 100644 addons/microsoft_outlook/i18n/microsoft_outlook.pot create mode 100644 addons/microsoft_outlook/models/__init__.py create mode 100644 addons/microsoft_outlook/models/ir_mail_server.py create mode 100644 addons/microsoft_outlook/models/microsoft_outlook_mixin.py create mode 100644 addons/microsoft_outlook/models/res_config_settings.py create mode 100644 addons/microsoft_outlook/views/ir_mail_server_views.xml create mode 100644 addons/microsoft_outlook/views/res_config_settings_views.xml create mode 100644 addons/microsoft_outlook/views/templates.xml diff --git a/.tx/config b/.tx/config index fc226b1b2d917..b12e0f7846fd2 100644 --- a/.tx/config +++ b/.tx/config @@ -267,6 +267,11 @@ file_filter = addons/fetchmail_gmail/i18n/.po source_file = addons/fetchmail_gmail/i18n/fetchmail_gmail.pot source_lang = en +[odoo-14.fetchmail_outlook] +file_filter = addons/fetchmail_outlook/i18n/.po +source_file = addons/fetchmail_outlook/i18n/fetchmail_outlook.pot +source_lang = en + [odoo-14.fleet] file_filter = addons/fleet/i18n/.po source_file = addons/fleet/i18n/fleet.pot @@ -517,6 +522,11 @@ file_filter = addons/microsoft_calendar/i18n/.po source_file = addons/microsoft_calendar/i18n/microsoft_calendar.pot source_lang = en +[odoo-14.microsoft_outlook] +file_filter = addons/microsoft_outlook/i18n/.po +source_file = addons/microsoft_outlook/i18n/microsoft_outlook.pot +source_lang = en + [odoo-14.mrp] file_filter = addons/mrp/i18n/.po source_file = addons/mrp/i18n/mrp.pot diff --git a/addons/fetchmail_gmail/views/fetchmail_server_views.xml b/addons/fetchmail_gmail/views/fetchmail_server_views.xml index abbb906f13f60..6a550b39f68d4 100644 --- a/addons/fetchmail_gmail/views/fetchmail_server_views.xml +++ b/addons/fetchmail_gmail/views/fetchmail_server_views.xml @@ -3,6 +3,7 @@ fetchmail.server.view.form.inherit.gmail fetchmail.server + 100 diff --git a/addons/fetchmail_outlook/__init__.py b/addons/fetchmail_outlook/__init__.py new file mode 100644 index 0000000000000..dc5e6b693d19d --- /dev/null +++ b/addons/fetchmail_outlook/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/fetchmail_outlook/__manifest__.py b/addons/fetchmail_outlook/__manifest__.py new file mode 100644 index 0000000000000..a77db1b1c2bf5 --- /dev/null +++ b/addons/fetchmail_outlook/__manifest__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + "name": "Fetchmail Outlook", + "version": "1.0", + "category": "Hidden", + "description": "OAuth authentication for incoming Outlook mail server", + "depends": [ + "microsoft_outlook", + "fetchmail", + ], + "data": [ + "views/fetchmail_server_views.xml", + ], + "auto_install": True, +} diff --git a/addons/fetchmail_outlook/i18n/fetchmail_outlook.pot b/addons/fetchmail_outlook/i18n/fetchmail_outlook.pot new file mode 100644 index 0000000000000..3efb1c23464bc --- /dev/null +++ b/addons/fetchmail_outlook/i18n/fetchmail_outlook.pot @@ -0,0 +1,125 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * fetchmail_outlook +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-03-30 08:55+0000\n" +"PO-Revision-Date: 2022-03-30 08:55+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: fetchmail_outlook +#: model_terms:ir.ui.view,arch_db:fetchmail_outlook.fetchmail_server_view_form +msgid "" +"\n" +" Connect your Outlook account" +msgstr "" + +#. module: fetchmail_outlook +#: model_terms:ir.ui.view,arch_db:fetchmail_outlook.fetchmail_server_view_form +msgid "" +"\n" +" Edit Settings" +msgstr "" + +#. module: fetchmail_outlook +#: model_terms:ir.ui.view,arch_db:fetchmail_outlook.fetchmail_server_view_form +msgid "" +" + + + fetchmail.server.view.form.inherit.outlook + fetchmail.server + 1000 + + + + + + + + + + +
+
+ + Outlook Token Valid + + + + +
+
+ + {} + +
+
+
diff --git a/addons/microsoft_outlook/__init__.py b/addons/microsoft_outlook/__init__.py new file mode 100644 index 0000000000000..7d34c7c054abd --- /dev/null +++ b/addons/microsoft_outlook/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import controllers +from . import models diff --git a/addons/microsoft_outlook/__manifest__.py b/addons/microsoft_outlook/__manifest__.py new file mode 100644 index 0000000000000..7ae700cd02dd0 --- /dev/null +++ b/addons/microsoft_outlook/__manifest__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + "name": "Microsoft Outlook", + "version": "1.0", + "category": "Hidden", + "description": "Outlook support for outgoing mail servers", + "depends": [ + "mail", + ], + "data": [ + "views/ir_mail_server_views.xml", + "views/res_config_settings_views.xml", + "views/templates.xml", + ], + "auto_install": False, +} diff --git a/addons/microsoft_outlook/controllers/__init__.py b/addons/microsoft_outlook/controllers/__init__.py new file mode 100644 index 0000000000000..afcdc91f726cf --- /dev/null +++ b/addons/microsoft_outlook/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -* +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import main diff --git a/addons/microsoft_outlook/controllers/main.py b/addons/microsoft_outlook/controllers/main.py new file mode 100644 index 0000000000000..9206275a184ba --- /dev/null +++ b/addons/microsoft_outlook/controllers/main.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import json +import logging +import werkzeug + +from werkzeug.exceptions import Forbidden + +from odoo import http +from odoo.exceptions import UserError +from odoo.http import request +from odoo.tools import consteq + +_logger = logging.getLogger(__name__) + + +class MicrosoftOutlookController(http.Controller): + @http.route('/microsoft_outlook/confirm', type='http', auth='user') + def microsoft_outlook_callback(self, code=None, state=None, error_description=None, **kwargs): + """Callback URL during the OAuth process. + + Outlook 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 not request.env.user.has_group('base.group_system'): + _logger.error('Microsoft Outlook: Non system user try to link an Outlook account.') + raise Forbidden() + + try: + state = json.loads(state) + model_name = state['model'] + rec_id = state['id'] + csrf_token = state['csrf_token'] + except Exception: + _logger.error('Microsoft Outlook: Wrong state value %r.', state) + raise Forbidden() + + if error_description: + return request.render('microsoft_outlook.microsoft_outlook_oauth_error', { + 'error': error_description, + 'model_name': model_name, + 'rec_id': rec_id, + }) + + model = request.env[model_name] + + if not issubclass(type(model), request.env.registry['microsoft.outlook.mixin']): + # The model must inherits from the "microsoft.outlook.mixin" mixin + raise Forbidden() + + record = model.browse(rec_id).exists() + if not record: + 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, + }) + + record.write({ + 'microsoft_outlook_refresh_token': refresh_token, + 'microsoft_outlook_access_token': access_token, + 'microsoft_outlook_access_token_expiration': expiration, + }) + + return werkzeug.utils.redirect(f'/web?#id={rec_id}&model={model_name}&view_type=form', 303) diff --git a/addons/microsoft_outlook/i18n/microsoft_outlook.pot b/addons/microsoft_outlook/i18n/microsoft_outlook.pot new file mode 100644 index 0000000000000..4e496079c3be8 --- /dev/null +++ b/addons/microsoft_outlook/i18n/microsoft_outlook.pot @@ -0,0 +1,209 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * microsoft_outlook +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-03-30 08:57+0000\n" +"PO-Revision-Date: 2022-03-30 08:57+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: microsoft_outlook +#: model_terms:ir.ui.view,arch_db:microsoft_outlook.ir_mail_server_view_form +msgid "" +"\n" +" Connect your Outlook account" +msgstr "" + +#. module: microsoft_outlook +#: model_terms:ir.ui.view,arch_db:microsoft_outlook.ir_mail_server_view_form +msgid "" +"\n" +" Edit Settings" +msgstr "" + +#. module: microsoft_outlook +#: model_terms:ir.ui.view,arch_db:microsoft_outlook.ir_mail_server_view_form +msgid "" +" + + + ir.mail_server.view.form.inherit.outlook + ir.mail_server + + + + + + + + + + +
+
+ + Outlook Token Valid + + + + +
+
+
+
+
diff --git a/addons/microsoft_outlook/views/res_config_settings_views.xml b/addons/microsoft_outlook/views/res_config_settings_views.xml new file mode 100644 index 0000000000000..94cdb4b6705c8 --- /dev/null +++ b/addons/microsoft_outlook/views/res_config_settings_views.xml @@ -0,0 +1,35 @@ + + + + + res.config.settings.view.form.inherit.microsoft_outlook + res.config.settings + + +
+
+
+ Outlook Credentials +
+ Send and receive email with your Outlook account. +
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/microsoft_outlook/views/templates.xml b/addons/microsoft_outlook/views/templates.xml new file mode 100644 index 0000000000000..7139c4bb6e642 --- /dev/null +++ b/addons/microsoft_outlook/views/templates.xml @@ -0,0 +1,15 @@ + + + +