[FIX] google_gmail, fetchmail_gmail: use Odoo endpoint for authentication
Purpose ======= The flow where we copy / paste the authorization code will be depreciated. Because of that, we know use the newest authentication system which use redirect URI. Technical ========= Now, the user is redirected to an Odoo endpoint "google_gmail/confirm" and the access token / refresh token are automatically fetched. Documentation https://developers.google.com/identity/protocols/oauth2/native-app Task-2852560 closes odoo/odoo#94476 X-original-commit: 69bd9bc6b36fdd55e8e859667bcddddad6db7d06 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -15,6 +15,28 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
|
||||
msgid ""
|
||||
"<i class=\"fa fa-arrow-right\"/>\n"
|
||||
" Connect your Gmail account"
|
||||
msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
|
||||
msgid ""
|
||||
"<i class=\"fa fa-cog\"/>\n"
|
||||
" Edit Settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
|
||||
msgid ""
|
||||
"<span attrs=\"{'invisible': ['|', ('server_type', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}\" class=\"badge badge-success\">\n"
|
||||
" Gmail Token Valid\n"
|
||||
" </span>"
|
||||
msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: model:ir.model.fields,field_description:fetchmail_gmail.field_fetchmail_server__google_gmail_access_token
|
||||
msgid "Access Token"
|
||||
@@ -27,15 +49,9 @@ msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: model:ir.model.fields,field_description:fetchmail_gmail.field_fetchmail_server__google_gmail_authorization_code
|
||||
#: 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 "Get an Authorization Code"
|
||||
msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: model:ir.model.fields.selection,name:fetchmail_gmail.selection__fetchmail_server__server_type__gmail
|
||||
msgid "Gmail OAuth Authentication"
|
||||
@@ -58,7 +74,9 @@ msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:fetchmail_gmail.fetchmail_server_view_form
|
||||
msgid "Setup your Gmail API credentials to link a Gmail account."
|
||||
msgid ""
|
||||
"Setup your Gmail API credentials in the general settings to link a Gmail "
|
||||
"account."
|
||||
msgstr ""
|
||||
|
||||
#. module: fetchmail_gmail
|
||||
|
||||
@@ -7,18 +7,30 @@
|
||||
<field name="inherit_id" ref="fetchmail.view_email_server_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="user" position="after">
|
||||
<field string="Authorization Code" name="google_gmail_authorization_code" password="True"
|
||||
attrs="{'required': [('server_type', '=', 'gmail')], 'invisible': [('server_type', '!=', 'gmail')], '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': ['|', ('server_type', '!=', 'gmail'), ('google_gmail_uri', '=', False)]}"
|
||||
nolabel="1"/>
|
||||
<div class="alert alert-warning" role="alert"
|
||||
attrs="{'invisible': ['|', ('server_type', '!=', 'gmail'), ('google_gmail_uri', '!=', False)]}">
|
||||
Setup your Gmail API credentials to link a Gmail account.
|
||||
<field name="google_gmail_uri" invisible="1"/>
|
||||
<field name="google_gmail_refresh_token" invisible="1"/>
|
||||
<div></div>
|
||||
<div attrs="{'invisible': [('server_type', '!=', 'gmail')]}">
|
||||
<span attrs="{'invisible': ['|', ('server_type', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}"
|
||||
class="badge badge-success">
|
||||
Gmail Token Valid
|
||||
</span>
|
||||
<button type="object"
|
||||
name="open_google_gmail_uri" class="btn-link px-0"
|
||||
attrs="{'invisible': ['|', '|', ('google_gmail_uri', '=', False), ('server_type', '!=', 'gmail'), ('google_gmail_refresh_token', '!=', False)]}">
|
||||
<i class="fa fa-arrow-right"/>
|
||||
Connect your Gmail account
|
||||
</button>
|
||||
<button type="object"
|
||||
name="open_google_gmail_uri" class="btn-link px-0"
|
||||
attrs="{'invisible': ['|', '|', ('google_gmail_uri', '=', False), ('server_type', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}">
|
||||
<i class="fa fa-cog"/>
|
||||
Edit Settings
|
||||
</button>
|
||||
<div class="alert alert-warning" role="alert"
|
||||
attrs="{'invisible': ['|', ('server_type', '!=', 'gmail'), ('google_gmail_uri', '!=', False)]}">
|
||||
Setup your Gmail API credentials in the general settings to link a Gmail account.
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
<field name="password" position="attributes">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
@@ -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 werkzeug.urls import url_encode
|
||||
|
||||
from odoo import _, http
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.http import request
|
||||
from odoo.tools import consteq
|
||||
|
||||
_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 not request.env.user.has_group('base.group_system'):
|
||||
_logger.error('Google Gmail: non-system user trying to link an Gmail account.')
|
||||
raise Forbidden()
|
||||
|
||||
if error:
|
||||
return _('An error occur during the authentication process: %s.', error)
|
||||
|
||||
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()
|
||||
|
||||
model = request.env[model_name]
|
||||
|
||||
if not issubclass(type(model), request.env.registry['google.gmail.mixin']):
|
||||
# The model must inherits from the "google.gmail.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_gmail_csrf_token()):
|
||||
_logger.error('Google Gmail: Wrong CSRF token during Gmail authentication.')
|
||||
raise Forbidden()
|
||||
|
||||
try:
|
||||
refresh_token, access_token, expiration = record._fetch_gmail_refresh_token(code)
|
||||
except UserError as e:
|
||||
return _('An error occur during the authentication process: %s.', str(e.name))
|
||||
|
||||
record.write({
|
||||
'google_gmail_access_token': access_token,
|
||||
'google_gmail_access_token_expiration': expiration,
|
||||
'google_gmail_authorization_code': code,
|
||||
'google_gmail_refresh_token': refresh_token,
|
||||
})
|
||||
|
||||
url_params = {
|
||||
'id': rec_id,
|
||||
'model': model_name,
|
||||
'view_type': 'form'
|
||||
}
|
||||
url = '/web?#' + url_encode(url_params)
|
||||
return werkzeug.utils.redirect(url, 303)
|
||||
@@ -15,6 +15,28 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.ir_mail_server_view_form
|
||||
msgid ""
|
||||
"<i class=\"fa fa-arrow-right\"/>\n"
|
||||
" Connect your Gmail account"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.ir_mail_server_view_form
|
||||
msgid ""
|
||||
"<i class=\"fa fa-cog\"/>\n"
|
||||
" Edit Settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.ir_mail_server_view_form
|
||||
msgid ""
|
||||
"<span attrs=\"{'invisible': ['|', ('smtp_authentication', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}\" class=\"badge badge-success\">\n"
|
||||
" Gmail Token Valid\n"
|
||||
" </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
|
||||
@@ -27,6 +49,19 @@ msgstr ""
|
||||
msgid "Access Token Expiration Timestamp"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: code:addons/google_gmail/controllers/main.py:0
|
||||
#: code:addons/google_gmail/controllers/main.py:0
|
||||
#, python-format
|
||||
msgid "An error occur during the authentication process: %s."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: code:addons/google_gmail/models/google_gmail_mixin.py:0
|
||||
#, python-format
|
||||
msgid "An error occurred when fetching the access token."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_ir_mail_server__smtp_authentication
|
||||
msgid "Authenticate with"
|
||||
@@ -35,7 +70,6 @@ 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 ""
|
||||
|
||||
@@ -54,11 +88,6 @@ msgstr ""
|
||||
msgid "Config Settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.ir_mail_server_view_form
|
||||
msgid "Get an Authorization Code"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model:ir.model.fields,field_description:google_gmail.field_res_config_settings__google_gmail_client_identifier
|
||||
msgid "Gmail Client Id"
|
||||
@@ -84,6 +113,18 @@ msgstr ""
|
||||
msgid "Mail Server"
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: code:addons/google_gmail/models/google_gmail_mixin.py:0
|
||||
#, python-format
|
||||
msgid "Only the administrator can link a Gmail mail server."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: code:addons/google_gmail/models/google_gmail_mixin.py:0
|
||||
#, python-format
|
||||
msgid "Please configure your Gmail credentials."
|
||||
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
|
||||
@@ -92,7 +133,9 @@ msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
#: model_terms:ir.ui.view,arch_db:google_gmail.ir_mail_server_view_form
|
||||
msgid "Setup your Gmail API credentials to link a Gmail account."
|
||||
msgid ""
|
||||
"Setup your Gmail API credentials in the general settings to link a Gmail "
|
||||
"account."
|
||||
msgstr ""
|
||||
|
||||
#. module: google_gmail
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import requests
|
||||
|
||||
from odoo import api, fields, models
|
||||
from werkzeug.urls import url_encode, url_join
|
||||
|
||||
from odoo import _, api, fields, models, tools
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -27,38 +32,107 @@ class GoogleGmailMixin(models.AbstractModel):
|
||||
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')
|
||||
base_url = self.get_base_url()
|
||||
|
||||
redirect_uri = url_join(base_url, '/google_gmail/confirm')
|
||||
|
||||
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
|
||||
for record in self:
|
||||
google_gmail_uri = 'https://accounts.google.com/o/oauth2/v2/auth?%s' % url_encode({
|
||||
'client_id': google_gmail_client_id,
|
||||
'redirect_uri': redirect_uri,
|
||||
'response_type': 'code',
|
||||
'scope': self._SERVICE_SCOPE,
|
||||
# access_type and prompt needed to get a refresh token
|
||||
'access_type': 'offline',
|
||||
'prompt': 'consent',
|
||||
'state': json.dumps({
|
||||
'model': record._name,
|
||||
'id': record.id or False,
|
||||
'csrf_token': record._get_gmail_csrf_token() if record.id else False,
|
||||
})
|
||||
})
|
||||
record.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
|
||||
def open_google_gmail_uri(self):
|
||||
"""Open the URL to accept the Gmail permission.
|
||||
|
||||
return super(GoogleGmailMixin, self).create(vals_list)
|
||||
This is done with an action, so we can force the user the save the form.
|
||||
We need him to save the form so the current mail server record exist in DB, and
|
||||
we can include the record ID in the URL.
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
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
|
||||
if not self.env.user.has_group('base.group_system'):
|
||||
raise AccessError(_('Only the administrator can link a Gmail mail server.'))
|
||||
|
||||
return super(GoogleGmailMixin, self).write(values)
|
||||
if not self.google_gmail_uri:
|
||||
raise UserError(_('Please configure your Gmail credentials.'))
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': self.google_gmail_uri,
|
||||
}
|
||||
|
||||
def _fetch_gmail_refresh_token(self, authorization_code):
|
||||
"""Request the refresh token and the initial access token from the authorization code.
|
||||
|
||||
:return:
|
||||
refresh_token, access_token, access_token_expiration
|
||||
"""
|
||||
response = self._fetch_gmail_token('authorization_code', code=authorization_code)
|
||||
|
||||
return (
|
||||
response['refresh_token'],
|
||||
response['access_token'],
|
||||
int(time.time()) + response['expires_in'],
|
||||
)
|
||||
|
||||
def _fetch_gmail_access_token(self, refresh_token):
|
||||
"""Refresh the access token thanks to the refresh token.
|
||||
|
||||
:return:
|
||||
access_token, access_token_expiration
|
||||
"""
|
||||
response = self._fetch_gmail_token('refresh_token', refresh_token=refresh_token)
|
||||
|
||||
return (
|
||||
response['access_token'],
|
||||
int(time.time()) + response['expires_in'],
|
||||
)
|
||||
|
||||
def _fetch_gmail_token(self, grant_type, **values):
|
||||
"""Generic method to request an access token or a refresh token.
|
||||
|
||||
Return the JSON response of the GMail API and manage the errors which can occur.
|
||||
|
||||
:param grant_type: Depends the action we want to do (refresh_token or authorization_code)
|
||||
:param values: Additional parameters that will be given to the GMail endpoint
|
||||
"""
|
||||
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')
|
||||
base_url = self.get_base_url()
|
||||
redirect_uri = url_join(base_url, '/google_gmail/confirm')
|
||||
|
||||
response = requests.post(
|
||||
'https://oauth2.googleapis.com/token',
|
||||
data={
|
||||
'client_id': google_gmail_client_id,
|
||||
'client_secret': google_gmail_client_secret,
|
||||
'grant_type': grant_type,
|
||||
'redirect_uri': redirect_uri,
|
||||
**values,
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
raise UserError(_('An error occurred when fetching the access token.'))
|
||||
|
||||
return response.json()
|
||||
|
||||
def _generate_oauth2_string(self, user, refresh_token):
|
||||
"""Generate a OAuth2 string which can be used for authentication.
|
||||
@@ -73,14 +147,33 @@ class GoogleGmailMixin(models.AbstractModel):
|
||||
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)
|
||||
access_token, expiration = self._fetch_gmail_access_token(self.google_gmail_refresh_token)
|
||||
|
||||
self.write({
|
||||
'google_gmail_access_token': access_token,
|
||||
'google_gmail_access_token_expiration': expiration,
|
||||
})
|
||||
|
||||
_logger.info(
|
||||
'Google Gmail: fetch new access token. Expires in %i minutes',
|
||||
(self.google_gmail_access_token_expiration - now_timestamp) // 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)
|
||||
|
||||
def _get_gmail_csrf_token(self):
|
||||
"""Generate a CSRF token that will be verified in `google_gmail_callback`.
|
||||
|
||||
This will prevent a malicious person to make an admin user disconnect the mail servers.
|
||||
"""
|
||||
self.ensure_one()
|
||||
_logger.info('Google Gmail: generate CSRF token for %s #%i', self._name, self.id)
|
||||
return tools.misc.hmac(
|
||||
env=self.env(su=True),
|
||||
scope='google_gmail_oauth',
|
||||
message=(self._name, self.id),
|
||||
)
|
||||
|
||||
@@ -6,17 +6,30 @@
|
||||
<field name="inherit_id" ref="base.ir_mail_server_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="smtp_user" position="after">
|
||||
<field string="Authorization Code" name="google_gmail_authorization_code" password="True"
|
||||
attrs="{'required': [('smtp_authentication', '=', 'gmail')], 'invisible': [('smtp_authentication', '!=', 'gmail')]}"/>
|
||||
<field name="google_gmail_uri"
|
||||
class="fa fa-arrow-right oe_edit_only"
|
||||
widget="url"
|
||||
text=" Get an Authorization Code"
|
||||
attrs="{'invisible': ['|', ('smtp_authentication', '!=', 'gmail'), ('google_gmail_uri', '=', False)]}"
|
||||
nolabel="1"/>
|
||||
<div class="alert alert-warning" role="alert"
|
||||
attrs="{'invisible': ['|', ('smtp_authentication', '!=', 'gmail'), ('google_gmail_uri', '!=', False)]}">
|
||||
Setup your Gmail API credentials to link a Gmail account.
|
||||
<field name="google_gmail_uri" invisible="1"/>
|
||||
<field name="google_gmail_refresh_token" invisible="1"/>
|
||||
<div></div>
|
||||
<div attrs="{'invisible': [('smtp_authentication', '!=', 'gmail')]}">
|
||||
<span attrs="{'invisible': ['|', ('smtp_authentication', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}"
|
||||
class="badge badge-success">
|
||||
Gmail Token Valid
|
||||
</span>
|
||||
<button type="object"
|
||||
name="open_google_gmail_uri" class="btn-link px-0"
|
||||
attrs="{'invisible': ['|', '|', ('google_gmail_uri', '=', False), ('smtp_authentication', '!=', 'gmail'), ('google_gmail_refresh_token', '!=', False)]}">
|
||||
<i class="fa fa-arrow-right"/>
|
||||
Connect your Gmail account
|
||||
</button>
|
||||
<button type="object"
|
||||
name="open_google_gmail_uri" class="btn-link px-0"
|
||||
attrs="{'invisible': ['|', '|', ('google_gmail_uri', '=', False), ('smtp_authentication', '!=', 'gmail'), ('google_gmail_refresh_token', '=', False)]}">
|
||||
<i class="fa fa-cog"/>
|
||||
Edit Settings
|
||||
</button>
|
||||
<div class="alert alert-warning" role="alert"
|
||||
attrs="{'invisible': ['|', ('smtp_authentication', '!=', 'gmail'), ('google_gmail_uri', '!=', False)]}">
|
||||
Setup your Gmail API credentials in the general settings to link a Gmail account.
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
<field name="smtp_user" position="attributes">
|
||||
|
||||
Reference in New Issue
Block a user