[ADD] {google,fetchmail}_gmail: OAuth for gmail servers
Purpose ======= Less secured apps are no longer supported by google, therefore, we need to transition to the OAuth2 authentication system. Specifications ============== 1. User will need to fill their Gmail API credentials in the main settings page 2. Then, in the incoming / outgoing mail server form view, they will need to tick the Gmail support checkbox 3. A link will be available to be redirected to Gmail and accept the permission 4. The user can now copy / paste the authorization code in Odoo, set his email as "login" and then send / receive emails with Gmail Task-2170676 closes odoo/odoo#83424 X-original-commit: ff223afc5300b2421b8ce935bb468f503c938c5d Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
+10
@@ -267,6 +267,11 @@ file_filter = addons/fetchmail/i18n/<lang>.po
|
||||
source_file = addons/fetchmail/i18n/fetchmail.pot
|
||||
source_lang = en
|
||||
|
||||
[odoo-master.fetchmail_gmail]
|
||||
file_filter = addons/fetchmail_gmail/i18n/<lang>.po
|
||||
source_file = addons/fetchmail_gmail/i18n/fetchmail_gmail.pot
|
||||
source_lang = en
|
||||
|
||||
[odoo-master.fleet]
|
||||
file_filter = addons/fleet/i18n/<lang>.po
|
||||
source_file = addons/fleet/i18n/fleet.pot
|
||||
@@ -307,6 +312,11 @@ file_filter = addons/google_recaptcha/i18n/<lang>.po
|
||||
source_file = addons/google_recaptcha/i18n/google_recaptcha.pot
|
||||
source_lang = en
|
||||
|
||||
[odoo-master.google_gmail]
|
||||
file_filter = addons/google_gmail/i18n/<lang>.po
|
||||
source_file = addons/google_gmail/i18n/google_gmail.pot
|
||||
source_lang = en
|
||||
|
||||
[odoo-master.google_spreadsheet]
|
||||
file_filter = addons/google_spreadsheet/i18n/<lang>.po
|
||||
source_file = addons/google_spreadsheet/i18n/google_spreadsheet.pot
|
||||
|
||||
@@ -105,7 +105,7 @@ odoo_mailgate: "|/path/to/odoo-mailgate.py --host=localhost -u %(uid)d -p PASSWO
|
||||
connection = IMAP4_SSL(self.server, int(self.port))
|
||||
else:
|
||||
connection = IMAP4(self.server, int(self.port))
|
||||
connection.login(self.user, self.password)
|
||||
self._imap_login(connection)
|
||||
elif self.server_type == 'pop':
|
||||
if self.is_ssl:
|
||||
connection = POP3_SSL(self.server, int(self.port))
|
||||
@@ -119,6 +119,16 @@ odoo_mailgate: "|/path/to/odoo-mailgate.py --host=localhost -u %(uid)d -p PASSWO
|
||||
connection.sock.settimeout(MAIL_TIMEOUT)
|
||||
return connection
|
||||
|
||||
def _imap_login(self, connection):
|
||||
"""Authenticate the IMAP connection.
|
||||
|
||||
Can be overridden in other module for different authentication methods.
|
||||
|
||||
:param connection: The IMAP connection to authenticate
|
||||
"""
|
||||
self.ensure_one()
|
||||
connection.login(self.user, self.password)
|
||||
|
||||
def button_confirm_login(self):
|
||||
for server in self:
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
"name": "Fetchmail Gmail",
|
||||
"version": "1.0",
|
||||
"category": "Hidden",
|
||||
"description": "Google authentication for incoming mail server",
|
||||
"depends": [
|
||||
"google_gmail",
|
||||
"fetchmail",
|
||||
],
|
||||
"data": ["views/fetchmail_server_views.xml"],
|
||||
"auto_install": True,
|
||||
"license": "LGPL-3",
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * fetchmail_gmail
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-01-25 14:36+0000\n"
|
||||
"PO-Revision-Date: 2022-01-25 14:36+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_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
|
||||
msgid "Authorization Code"
|
||||
msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
|
||||
msgid "Gmail"
|
||||
msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: code:addons/fetchmail_gmail/models/fetchmail_server.py:0
|
||||
#, python-format
|
||||
msgid "Gmail authentication only supports IMAP server type."
|
||||
msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: model:ir.model,name:fetchmail_gmail.model_fetchmail_server
|
||||
msgid "Incoming Mail Server"
|
||||
msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
|
||||
msgid ""
|
||||
"Setup your Gmail API credentials in the general settings to link a Gmail "
|
||||
"account."
|
||||
msgstr ""
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import fetchmail_server
|
||||
@@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class FetchmailServer(models.Model):
|
||||
_name = 'fetchmail.server'
|
||||
_inherit = ['fetchmail.server', 'google.gmail.mixin']
|
||||
|
||||
@api.constrains('use_google_gmail_service', 'server_type')
|
||||
def _check_use_google_gmail_service(self):
|
||||
if any(server.use_google_gmail_service and server.server_type != 'imap' for server in self):
|
||||
raise UserError(_('Gmail authentication only supports IMAP server type.'))
|
||||
|
||||
@api.onchange('use_google_gmail_service')
|
||||
def _onchange_use_google_gmail_service(self):
|
||||
"""Set the default configuration for a IMAP Gmail server."""
|
||||
if self.use_google_gmail_service:
|
||||
self.server = 'imap.gmail.com'
|
||||
self.server_type = 'imap'
|
||||
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
|
||||
self.google_gmail_access_token_expiration = False
|
||||
|
||||
def _imap_login(self, connection):
|
||||
"""Authenticate the IMAP connection.
|
||||
|
||||
If the mail server is Gmail, we use the OAuth2 authentication protocol.
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.use_google_gmail_service:
|
||||
auth_string = self._generate_oauth2_string(self.user, self.google_gmail_refresh_token)
|
||||
connection.authenticate('XOAUTH2', lambda x: auth_string)
|
||||
connection.select('INBOX')
|
||||
else:
|
||||
super(FetchmailServer, self)._imap_login(connection)
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="fetchmail_server_view_form" model="ir.ui.view">
|
||||
<field name="name">fetchmail.server.view.form.inherit.gmail</field>
|
||||
<field name="model">fetchmail.server</field>
|
||||
<field name="inherit_id" ref="fetchmail.view_email_server_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="server" position="before">
|
||||
<field name="use_google_gmail_service" string="Gmail" attrs="{'readonly': [('state', '=', 'done')]}"/>
|
||||
</field>
|
||||
<field name="user" position="after">
|
||||
<field string="Authorization Code" name="google_gmail_authorization_code" password="True"
|
||||
attrs="{'required': [('use_google_gmail_service', '=', True)], 'invisible': [('use_google_gmail_service', '=', False)], 'readonly': [('state', '=', 'done')]}"
|
||||
style="word-break: break-word;"/>
|
||||
<field name="google_gmail_uri"
|
||||
class="fa fa-arrow-right oe_edit_only"
|
||||
widget="url"
|
||||
text=" Get an Authorization Code"
|
||||
attrs="{'invisible': ['|', ('use_google_gmail_service', '=', False), ('google_gmail_uri', '=', False)]}"
|
||||
nolabel="1"/>
|
||||
<div class="alert alert-warning" role="alert"
|
||||
attrs="{'invisible': ['|', ('use_google_gmail_service', '=', False), ('google_gmail_uri', '!=', False)]}">
|
||||
Setup your Gmail API credentials in the general settings to link a Gmail account.
|
||||
</div>
|
||||
</field>
|
||||
<field name="password" position="attributes">
|
||||
<attribute name="attrs">{'required' : [('server_type', '!=', 'local'), ('use_google_gmail_service', '=', False), ('password', '!=', False)], 'invisible' : [('use_google_gmail_service', '=', True)]}</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -9,6 +9,7 @@ import requests
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -119,6 +120,42 @@ class GoogleService(models.AbstractModel):
|
||||
error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid")
|
||||
raise self.env['res.config.settings'].get_config_warning(error_msg)
|
||||
|
||||
@api.model
|
||||
def _get_access_token(self, refresh_token, service, scope):
|
||||
"""Fetch the access token thanks to the refresh token."""
|
||||
get_param = self.env['ir.config_parameter'].sudo().get_param
|
||||
client_id = get_param('google_%s_client_id' % service, default=False)
|
||||
client_secret = get_param('google_%s_client_secret' % service, default=False)
|
||||
|
||||
if not client_id or not client_secret:
|
||||
raise UserError(_('Google %s is not yet configured.', service.title()))
|
||||
|
||||
if not refresh_token:
|
||||
raise UserError(_('The refresh token for authentication is not set.'))
|
||||
|
||||
try:
|
||||
result = requests.post(
|
||||
GOOGLE_TOKEN_ENDPOINT,
|
||||
data={
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret,
|
||||
'refresh_token': refresh_token,
|
||||
'grant_type': 'refresh_token',
|
||||
'scope': scope,
|
||||
},
|
||||
headers={'Content-type': 'application/x-www-form-urlencoded'},
|
||||
timeout=TIMEOUT,
|
||||
)
|
||||
result.raise_for_status()
|
||||
except requests.HTTPError:
|
||||
raise UserError(
|
||||
_('Something went wrong during the token generation. Please request again an authorization code.')
|
||||
)
|
||||
|
||||
json_result = result.json()
|
||||
|
||||
return json_result.get('access_token'), json_result.get('expires_in')
|
||||
|
||||
@api.model
|
||||
def _do_request(self, uri, params=None, headers=None, method='POST', preuri="https://www.googleapis.com", timeout=TIMEOUT):
|
||||
""" Execute the request to Google API. Return a tuple ('HTTP_CODE', 'HTTP_RESPONSE')
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
"name": "Google Gmail",
|
||||
"version": "1.0",
|
||||
"category": "Hidden",
|
||||
"description": "Gmail support for incoming / outgoing mail servers",
|
||||
"depends": [
|
||||
"mail",
|
||||
"google_account",
|
||||
],
|
||||
"data": [
|
||||
"views/ir_mail_server_views.xml",
|
||||
"views/res_config_settings_views.xml",
|
||||
],
|
||||
"auto_install": True,
|
||||
"license": "LGPL-3",
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * google_gmail
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-01-25 14:36+0000\n"
|
||||
"PO-Revision-Date: 2022-01-25 14:36+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: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.res_config_settings_view_form
|
||||
msgid "<span class=\"o_form_label\">Gmail Credentials</span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_google_gmail_mixin__google_gmail_access_token
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_ir_mail_server__google_gmail_access_token
|
||||
msgid "Access Token"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_google_gmail_mixin__google_gmail_access_token_expiration
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_ir_mail_server__google_gmail_access_token_expiration
|
||||
msgid "Access Token Expiration Timestamp"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_google_gmail_mixin__google_gmail_authorization_code
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_ir_mail_server__google_gmail_authorization_code
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.ir_mail_server_view_form
|
||||
msgid "Authorization Code"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.res_config_settings_view_form
|
||||
msgid "Client ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.res_config_settings_view_form
|
||||
msgid "Client Secret"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model,name:google_gmail.model_res_config_settings
|
||||
msgid "Config Settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_google_gmail_mixin__display_name
|
||||
msgid "Display Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.ir_mail_server_view_form
|
||||
msgid "Gmail"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_google_gmail_mixin__use_google_gmail_service
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_ir_mail_server__use_google_gmail_service
|
||||
msgid "Gmail Authentication"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_res_config_settings__google_gmail_client_identifier
|
||||
msgid "Gmail Client Id"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_res_config_settings__google_gmail_client_secret
|
||||
msgid "Gmail Client Secret"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model,name:google_gmail.model_google_gmail_mixin
|
||||
msgid "Google Gmail Mixin"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_google_gmail_mixin__id
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_google_gmail_mixin____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model,name:google_gmail.model_ir_mail_server
|
||||
msgid "Mail Server"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_google_gmail_mixin__google_gmail_refresh_token
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_ir_mail_server__google_gmail_refresh_token
|
||||
msgid "Refresh Token"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.res_config_settings_view_form
|
||||
msgid "Send and receive email with your Gmail account."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.ir_mail_server_view_form
|
||||
msgid ""
|
||||
"Setup your Gmail API credentials in the general settings to link a Gmail "
|
||||
"account."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,help:google_gmail.field_google_gmail_mixin__google_gmail_uri
|
||||
#: model:ir.model.fields,help:google_gmail.field_ir_mail_server__google_gmail_uri
|
||||
msgid "The URL to generate the authorization code from Google"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_google_gmail_mixin__google_gmail_uri
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_ir_mail_server__google_gmail_uri
|
||||
msgid "URI"
|
||||
msgstr ""
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import google_gmail_mixin
|
||||
from . import ir_mail_server
|
||||
from . import res_config_settings
|
||||
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GoogleGmailMixin(models.AbstractModel):
|
||||
|
||||
_name = 'google.gmail.mixin'
|
||||
_description = 'Google Gmail Mixin'
|
||||
|
||||
_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_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')
|
||||
def _compute_gmail_uri(self):
|
||||
Config = self.env['ir.config_parameter'].sudo()
|
||||
google_gmail_client_id = Config.get_param('google_gmail_client_id')
|
||||
google_gmail_client_secret = Config.get_param('google_gmail_client_secret')
|
||||
|
||||
if not google_gmail_client_id or not google_gmail_client_secret:
|
||||
self.google_gmail_uri = False
|
||||
else:
|
||||
google_gmail_uri = self.env['google.service']._get_google_token_uri('gmail', scope=self._SERVICE_SCOPE)
|
||||
self.google_gmail_uri = google_gmail_uri
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for values in vals_list:
|
||||
if values.get('google_gmail_authorization_code'):
|
||||
# Generate the refresh token
|
||||
values['google_gmail_refresh_token'] = self.env['google.service'].generate_refresh_token(
|
||||
'gmail', values['google_gmail_authorization_code'])
|
||||
values['google_gmail_access_token'] = False
|
||||
values['google_gmail_access_token_expiration'] = False
|
||||
|
||||
return super(GoogleGmailMixin, self).create(vals_list)
|
||||
|
||||
def write(self, values):
|
||||
authorization_code = values.get('google_gmail_authorization_code')
|
||||
if (
|
||||
authorization_code
|
||||
and not all(authorization_code == code for code in self.mapped('google_gmail_authorization_code'))
|
||||
):
|
||||
# Update the refresh token
|
||||
values['google_gmail_refresh_token'] = self.env['google.service'].generate_refresh_token(
|
||||
'gmail', authorization_code)
|
||||
values['google_gmail_access_token'] = False
|
||||
values['google_gmail_access_token_expiration'] = False
|
||||
|
||||
return super(GoogleGmailMixin, self).write(values)
|
||||
|
||||
def _generate_oauth2_string(self, user, refresh_token):
|
||||
"""Generate a OAuth2 string which can be used for authentication.
|
||||
|
||||
:param user: Email address of the Gmail account to authenticate
|
||||
:param refresh_token: Refresh token for the given Gmail account
|
||||
|
||||
:return: The SASL argument for the OAuth2 mechanism.
|
||||
"""
|
||||
self.ensure_one()
|
||||
now_timestamp = int(time.time())
|
||||
if not self.google_gmail_access_token \
|
||||
or not self.google_gmail_access_token_expiration \
|
||||
or self.google_gmail_access_token_expiration < now_timestamp:
|
||||
self.google_gmail_access_token, expires_in = self.env['google.service']._get_access_token(
|
||||
refresh_token, 'gmail', self._SERVICE_SCOPE)
|
||||
self.google_gmail_access_token_expiration = now_timestamp + expires_in
|
||||
|
||||
_logger.info('Google Gmail: fetch new access token. Expire in %i minutes', expires_in // 60)
|
||||
else:
|
||||
_logger.info(
|
||||
'Google Gmail: reuse existing access token. Expire in %i minutes',
|
||||
(self.google_gmail_access_token_expiration - now_timestamp) // 60)
|
||||
|
||||
return 'user=%s\1auth=Bearer %s\1\1' % (user, self.google_gmail_access_token)
|
||||
@@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
|
||||
from odoo import models, api
|
||||
|
||||
|
||||
class IrMailServer(models.Model):
|
||||
"""Represents an SMTP server, able to send outgoing emails, with SSL and TLS capabilities."""
|
||||
|
||||
_name = 'ir.mail_server'
|
||||
_inherit = ['ir.mail_server', 'google.gmail.mixin']
|
||||
|
||||
@api.onchange('smtp_encryption')
|
||||
def _onchange_encryption(self):
|
||||
"""Do not change the SMTP configuration if it's a Gmail server
|
||||
|
||||
(e.g. the port which is already set)"""
|
||||
if not self.use_google_gmail_service:
|
||||
super()._onchange_encryption()
|
||||
|
||||
@api.onchange('use_google_gmail_service')
|
||||
def _onchange_use_google_gmail_service(self):
|
||||
if self.use_google_gmail_service:
|
||||
self.smtp_host = 'smtp.gmail.com'
|
||||
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
|
||||
self.google_gmail_access_token_expiration = False
|
||||
|
||||
def _smtp_login(self, connection, smtp_user, smtp_password):
|
||||
if len(self) == 1 and self.use_google_gmail_service:
|
||||
auth_string = self._generate_oauth2_string(smtp_user, self.google_gmail_refresh_token)
|
||||
oauth_param = base64.b64encode(auth_string.encode()).decode()
|
||||
connection.ehlo()
|
||||
connection.docmd('AUTH', 'XOAUTH2 %s' % oauth_param)
|
||||
else:
|
||||
super(IrMailServer, self)._smtp_login(connection, smtp_user, smtp_password)
|
||||
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
google_gmail_client_identifier = fields.Char('Gmail Client Id', config_parameter='google_gmail_client_id')
|
||||
google_gmail_client_secret = fields.Char('Gmail Client Secret', config_parameter='google_gmail_client_secret')
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="ir_mail_server_view_form" model="ir.ui.view">
|
||||
<field name="name">ir.mail_server.view.form.inherit.gmail</field>
|
||||
<field name="model">ir.mail_server</field>
|
||||
<field name="inherit_id" ref="base.ir_mail_server_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="smtp_host" position="before">
|
||||
<field name="use_google_gmail_service" string="Gmail"/>
|
||||
</field>
|
||||
<field name="smtp_user" position="after">
|
||||
<field string="Authorization Code" name="google_gmail_authorization_code" password="True"
|
||||
attrs="{'required': [('use_google_gmail_service', '=', True)], 'invisible': [('use_google_gmail_service', '=', False)]}"
|
||||
style="word-break: break-word;"/>
|
||||
<field name="google_gmail_uri"
|
||||
class="fa fa-arrow-right oe_edit_only"
|
||||
widget="url"
|
||||
text=" Get an Authorization Code"
|
||||
attrs="{'invisible': ['|', ('use_google_gmail_service', '=', False), ('google_gmail_uri', '=', False)]}"
|
||||
nolabel="1"/>
|
||||
<div class="alert alert-warning" role="alert"
|
||||
attrs="{'invisible': ['|', ('use_google_gmail_service', '=', False), ('google_gmail_uri', '!=', False)]}">
|
||||
Setup your Gmail API credentials in the general settings to link a Gmail account.
|
||||
</div>
|
||||
</field>
|
||||
<field name="smtp_pass" position="attributes">
|
||||
<attribute name="attrs">{'invisible' : [('use_google_gmail_service', '=', True)]}</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.google_gmail</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div id="emails" position="inside">
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="google_gmail_setting"
|
||||
attrs="{'invisible': [('external_email_server_default', '=', False)]}">
|
||||
<div class="o_setting_right_pane">
|
||||
<span class="o_form_label">Gmail Credentials</span>
|
||||
<div class="text-muted">
|
||||
Send and receive email with your Gmail account.
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="row mt16" id="gmail_client_identifier">
|
||||
<label string="Client ID" for="google_gmail_client_identifier" class="col-lg-3 o_light_label"/>
|
||||
<field name="google_gmail_client_identifier" class="ml-2"/>
|
||||
</div>
|
||||
<div class="row mt16" id="gmail_client_secret">
|
||||
<label string="Client Secret" for="google_gmail_client_secret" class="col-lg-3 o_light_label"/>
|
||||
<field name="google_gmail_client_secret" class="ml-2"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -226,7 +226,10 @@ class IrMailServer(models.Model):
|
||||
elif not host:
|
||||
mail_server, smtp_from = self.sudo()._find_mail_server(smtp_from)
|
||||
|
||||
if not mail_server:
|
||||
mail_server = self.env['ir.mail_server']
|
||||
ssl_context = None
|
||||
|
||||
if mail_server:
|
||||
smtp_server = mail_server.smtp_host
|
||||
smtp_port = mail_server.smtp_port
|
||||
@@ -314,7 +317,7 @@ class IrMailServer(models.Model):
|
||||
local, at, domain = smtp_user.rpartition('@')
|
||||
if at:
|
||||
smtp_user = local + at + idna.encode(domain).decode('ascii')
|
||||
connection.login(smtp_user, smtp_password or '')
|
||||
mail_server._smtp_login(connection, smtp_user, smtp_password or '')
|
||||
|
||||
# Some methods of SMTP don't check whether EHLO/HELO was sent.
|
||||
# Anyway, as it may have been sent by login(), all subsequent usages should consider this command as sent.
|
||||
@@ -327,6 +330,18 @@ class IrMailServer(models.Model):
|
||||
|
||||
return connection
|
||||
|
||||
def _smtp_login(self, connection, smtp_user, smtp_password):
|
||||
"""Authenticate the SMTP connection.
|
||||
|
||||
Can be overridden in other module for different authentication methods.Can be
|
||||
called on the model itself or on a singleton.
|
||||
|
||||
:param connection: The SMTP connection to authenticate
|
||||
:param smtp_user: The user to used for the authentication
|
||||
:param smtp_password: The password to used for the authentication
|
||||
"""
|
||||
connection.login(smtp_user, smtp_password)
|
||||
|
||||
def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
|
||||
attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
|
||||
body_alternative=None, subtype_alternative='plain'):
|
||||
|
||||
Reference in New Issue
Block a user