[ADD] auth_totp_mail: 2FA using code sent by email

Add the possibility to force the two-factor authentication for all users,
using a two-factor authentication by email
when the 2FA using an Authenticator app is not configured for the user.

Two possibilities:
 - Force the 2FA only for employee users using the system parameter `auth_totp.policy=employee_required`
 - Force the 2FA for all users, employees and portals, using the system parameter `auth_totp.policy=all_required`

closes odoo/odoo#83750

Signed-off-by: Denis Ledoux (dle) <dle@odoo.com>
This commit is contained in:
Denis Ledoux
2022-02-01 17:15:30 +00:00
parent 8b5f613cc0
commit a08a0b6454
19 changed files with 626 additions and 9 deletions
+5
View File
@@ -97,6 +97,11 @@ file_filter = addons/auth_totp_mail/i18n/<lang>.po
source_file = addons/auth_totp_mail/i18n/auth_totp_mail.pot
source_lang = en
[odoo-master.auth_totp_mail_enforce]
file_filter = addons/auth_totp_mail_enforce/i18n/<lang>.po
source_file = addons/auth_totp_mail_enforce/i18n/auth_totp_mail_enforce.pot
source_lang = en
[odoo-master.auth_totp_portal]
file_filter = addons/auth_totp_portal/i18n/<lang>.po
source_file = addons/auth_totp_portal/i18n/auth_totp_portal.pot
+4 -3
View File
@@ -35,12 +35,12 @@ class Home(odoo.addons.web.controllers.main.Home):
request.session.finalize()
return request.redirect(self._login_redirect(request.session.uid, redirect=redirect))
elif user and request.httprequest.method == 'POST':
elif user and request.httprequest.method == 'POST' and kwargs.get('totp_token'):
try:
with user._assert_can_auth():
user._totp_check(int(re.sub(r'\s', '', kwargs['totp_token'])))
except AccessDenied:
error = _("Verification failed, please double-check the 6-digit code")
except AccessDenied as e:
error = str(e)
except ValueError:
error = _("Invalid authentication code format.")
else:
@@ -66,6 +66,7 @@ class Home(odoo.addons.web.controllers.main.Home):
return response
return request.render('auth_totp.auth_totp_form', {
'user': user,
'error': error,
'redirect': redirect,
})
+1 -1
View File
@@ -372,7 +372,7 @@ msgid "Verification Code"
msgstr ""
#. module: auth_totp
#: code:addons/auth_totp/controllers/home.py:0
#: code:addons/auth_totp/models/res_users.py:0
#: code:addons/auth_totp/wizard/auth_totp_wizard.py:0
#, python-format
msgid "Verification failed, please double-check the 6-digit code"
+8 -2
View File
@@ -28,12 +28,18 @@ class Users(models.Model):
def SELF_READABLE_FIELDS(self):
return super().SELF_READABLE_FIELDS + ['totp_enabled', 'totp_trusted_device_ids']
def _mfa_type(self):
r = super()._mfa_type()
if r is not None:
return r
if self.totp_enabled:
return 'totp'
def _mfa_url(self):
r = super()._mfa_url()
if r is not None:
return r
if self.totp_enabled:
if self._mfa_type() == 'totp':
return '/web/login/totp'
@api.depends('totp_secret')
@@ -55,7 +61,7 @@ class Users(models.Model):
match = TOTP(key).match(code)
if match is None:
_logger.info("2FA check: FAIL for %s %r", self, self.login)
raise AccessDenied()
raise AccessDenied(_("Verification failed, please double-check the 6-digit code"))
_logger.info("2FA check: SUCCESS for %s %r", self, self.login)
def _totp_try_setting(self, secret, code):
+3 -3
View File
@@ -20,7 +20,7 @@ class TOTP:
def __init__(self, key):
self._key = key
def match(self, code, t=None, window=TIMESTEP):
def match(self, code, t=None, window=TIMESTEP, timestep=TIMESTEP):
"""
:param code: authenticator code to check against this key
:param int t: current timestamp (seconds)
@@ -32,8 +32,8 @@ class TOTP:
if t is None:
t = time.time()
low = int((t - window) / TIMESTEP)
high = int((t + window) / TIMESTEP) + 1
low = int((t - window) / timestep)
high = int((t + window) / timestep) + 1
return next((
counter for counter in range(low, high)
@@ -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
@@ -0,0 +1,20 @@
{
'name': '2FA by mail',
'description': """
2FA by mail
===============
Two-Factor authentication by sending a code to the user email inbox
when the 2FA using an authenticator app is not configured.
To enforce users to use a two-factor authentication by default,
and encourage users to configure their 2FA using an authenticator app.
""",
'depends': ['auth_totp', 'mail'],
'category': 'Extra Tools',
'data': [
'data/mail_template_data.xml',
'security/ir.model.access.csv',
'views/res_config_settings_views.xml',
'views/templates.xml',
],
'license': 'LGPL-3',
}
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import home
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
import odoo.addons.auth_totp.controllers.home
from odoo import http
from odoo.exceptions import AccessDenied, UserError
from odoo.http import request
class Home(odoo.addons.auth_totp.controllers.home.Home):
@http.route()
def web_totp(self, redirect=None, **kwargs):
response = super().web_totp(redirect=redirect, **kwargs)
if response.status_code != 200 or response.qcontext['user']._mfa_type() != 'totp_mail':
# In case the response from the super is a redirection
# or the user has another TOTP method, we return the response from the call to super.
return response
assert request.session.pre_uid and not request.session.uid, \
"The user must still be in the pre-authentication phase"
# Send the email containing the code to the user inbox
try:
response.qcontext['user']._send_totp_mail_code()
except (AccessDenied, UserError) as e:
response.qcontext['error'] = str(e)
return response
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="mail_template_totp_mail_code" model="mail.template">
<field name="name">TOTP for users: Authentication by email</field>
<field name="model_id" ref="base.model_res_users" />
<field name="subject">Your two-factor authentication code</field>
<field name="email_to">{{ object.email_formatted }}</field>
<field name="email_from">"{{ object.company_id.name }}" &lt;{{ (object.company_id.email or user.email) }}&gt;</field>
<field name="lang">{{ object.partner_id.lang }}</field>
<field name="auto_delete" eval="True"/>
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px; font-size: 13px;">
Dear <t t-out="object.partner_id.name or ''"></t><br/><br/>
<p>Someone is trying to log in into your account with a new device.</p>
<ul>
<t t-set="not_available">N/A</t>
<li>Location: <t t-out="ctx.get('location') or not_available"/></li>
<li>Device: <t t-out="ctx.get('device') or not_available"/></li>
<li>Browser: <t t-out="ctx.get('browser') or not_available"/></li>
<li>IP address: <t t-out="ctx.get('ip') or not_available"/></li>
</ul>
<p>If this is you, please enter the following code to complete the login:</p>
<t t-set="code_expiration" t-value="object._get_totp_mail_code()"/>
<t t-set="code" t-value="code_expiration[0]"/>
<t t-set="expiration" t-value="code_expiration[1]"/>
<div style="margin: 16px 0px 16px 0px; text-align: center;">
<span t-out="code" style="background-color:#faf9fa; border: 1px solid #dad8de; padding: 8px 16px 8px 16px; font-size: 24px; color: #875A7B; border-radius: 5px;"/>
</div>
<small>Please note that this code expires in <t t-out="expiration"/>.</small>
<p style="margin: 16px 0px 16px 0px;">
If you did NOT initiate this log-in,
you should immediately change your password to ensure account security.
</p>
<p style="margin: 16px 0px 16px 0px;">
We also strongly recommend enabling the two-factor authentication using an authenticator app to help secure your account.
</p>
<p style="margin: 16px 0px 16px 0px; text-align: center;">
<a t-att-href="object.get_totp_invite_url()"
style="background-color:#875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px;">
Activate my two-factor authentication
</a>
</p>
</div>
</field>
</record>
</data>
</odoo>
@@ -0,0 +1,229 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auth_totp_mail_enforce
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-02-01 12:13+0000\n"
"PO-Revision-Date: 2022-02-01 12:13+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: auth_totp_mail_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_mail_enforce.auth_totp_mail_form
msgid ""
".\n"
" <br/>"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:mail.template,body_html:auth_totp_mail_enforce.mail_template_totp_mail_code
msgid ""
"<div style=\"margin: 0px; padding: 0px; font-size: 13px;\">\n"
" Dear <t t-out=\"object.partner_id.name or ''\"/><br/><br/>\n"
" <p>Someone is trying to log in into your account with a new device.</p>\n"
" <ul>\n"
" <t t-set=\"not_available\">N/A</t>\n"
" <li>Location: <t t-out=\"ctx.get('location') or not_available\"/></li>\n"
" <li>Device: <t t-out=\"ctx.get('device') or not_available\"/></li>\n"
" <li>Browser: <t t-out=\"ctx.get('browser') or not_available\"/></li>\n"
" <li>IP address: <t t-out=\"ctx.get('ip') or not_available\"/></li>\n"
" </ul>\n"
" <p>If this is you, please enter the following code to complete the login:</p>\n"
" <t t-set=\"code_expiration\" t-value=\"object._get_totp_mail_code()\"/>\n"
" <t t-set=\"code\" t-value=\"code_expiration[0]\"/>\n"
" <t t-set=\"expiration\" t-value=\"code_expiration[1]\"/>\n"
" <div style=\"margin: 16px 0px 16px 0px; text-align: center;\">\n"
" <span t-out=\"code\" style=\"background-color:#faf9fa; border: 1px solid #dad8de; padding: 8px 16px 8px 16px; font-size: 24px; color: #875A7B; border-radius: 5px;\"/>\n"
" </div>\n"
" <small>Please note that this code expires in <t t-out=\"expiration\"/>.</small>\n"
"\n"
" <p style=\"margin: 16px 0px 16px 0px;\">\n"
" If you did NOT initiate this log-in,\n"
" you should immediately change your password to ensure account security.\n"
" </p>\n"
"\n"
" <p style=\"margin: 16px 0px 16px 0px;\">\n"
" We also strongly recommend enabling the two-factor authentication using an authenticator app to help secure your account.\n"
" </p>\n"
"\n"
" <p style=\"margin: 16px 0px 16px 0px; text-align: center;\">\n"
" <a t-att-href=\"object.get_totp_invite_url()\" style=\"background-color:#875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px;\">\n"
" Activate my two-factor authentication\n"
" </a>\n"
" </p>\n"
"</div>\n"
" "
msgstr ""
#. module: auth_totp_mail_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_mail_enforce.auth_totp_mail_form
msgid ""
"<i class=\"fa fa-envelope-o\"/>\n"
" To login, enter below the six-digit authentication code just sent via email to"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields.selection,name:auth_totp_mail_enforce.selection__res_config_settings__auth_totp_policy__all_required
msgid "All users"
msgstr ""
#. module: auth_totp_mail_enforce
#: code:addons/auth_totp_mail_enforce/models/res_users.py:0
#, python-format
msgid "Cannot send email: user %s has no email address."
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields.selection,name:auth_totp_mail_enforce.selection__auth_totp_rate_limit_log__limit_type__code_check
msgid "Code Checking"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model,name:auth_totp_mail_enforce.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log__create_uid
msgid "Created by"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log__create_date
msgid "Created on"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log__display_name
msgid "Display Name"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields.selection,name:auth_totp_mail_enforce.selection__res_config_settings__auth_totp_policy__employee_required
msgid "Employees only"
msgstr ""
#. module: auth_totp_mail_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_mail_enforce.res_config_settings_view_form
msgid ""
"Enforce the two-factor authentication by email for employees or for all users\n"
" (including portal users) if they didn't enable any other two-factor authentication\n"
" method."
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_res_config_settings__auth_totp_enforce
msgid "Enforce two-factor authentication"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log__id
msgid "ID"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log__ip
msgid "Ip"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log____last_update
msgid "Last Modified on"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log__write_uid
msgid "Last Updated by"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log__write_date
msgid "Last Updated on"
msgstr ""
#. module: auth_totp_mail_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_mail_enforce.auth_totp_mail_form
msgid "Learn More"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log__limit_type
msgid "Limit Type"
msgstr ""
#. module: auth_totp_mail_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_mail_enforce.auth_totp_mail_form
msgid "Re-send email"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields.selection,name:auth_totp_mail_enforce.selection__auth_totp_rate_limit_log__limit_type__send_email
msgid "Send Email"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log__source
msgid "Source"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:mail.template,name:auth_totp_mail_enforce.mail_template_totp_mail_code
msgid "TOTP for users: Authentication by email"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model,name:auth_totp_mail_enforce.model_auth_totp_rate_limit_log
msgid "TOTP rate limit logs"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_res_config_settings__auth_totp_policy
msgid "Two-factor authentication enforcing policy"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model.fields,field_description:auth_totp_mail_enforce.field_auth_totp_rate_limit_log__user_id
msgid "User"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:ir.model,name:auth_totp_mail_enforce.model_res_users
msgid "Users"
msgstr ""
#. module: auth_totp_mail_enforce
#: code:addons/auth_totp_mail_enforce/models/res_users.py:0
#, python-format
msgid "Verification failed, please double-check the 6-digit code"
msgstr ""
#. module: auth_totp_mail_enforce
#: model_terms:ir.ui.view,arch_db:auth_totp_mail_enforce.auth_totp_mail_form
msgid ""
"We strongly recommend enabling the two-factor authentication using an authenticator app to help secure your account.\n"
" <br/>"
msgstr ""
#. module: auth_totp_mail_enforce
#: code:addons/auth_totp_mail_enforce/models/res_users.py:0
#, python-format
msgid "You reached the limit of authentication mails sent for your account"
msgstr ""
#. module: auth_totp_mail_enforce
#: code:addons/auth_totp_mail_enforce/models/res_users.py:0
#, python-format
msgid "You reached the limit of code verifications for your account"
msgstr ""
#. module: auth_totp_mail_enforce
#: model:mail.template,subject:auth_totp_mail_enforce.mail_template_totp_mail_code
msgid "Your two-factor authentication code"
msgstr ""
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import auth_totp_rate_limit_log
from . import res_config_settings
from . import res_users
@@ -0,0 +1,20 @@
from odoo import fields, models
class AuthTotpRateLimitLog(models.TransientModel):
_name = 'auth.totp.rate.limit.log'
_description = 'TOTP rate limit logs'
def init(self):
self.env.cr.execute("""
CREATE INDEX IF NOT EXISTS auth_totp_rate_limit_log_user_id_limit_type_create_date_idx
ON auth_totp_rate_limit_log(user_id, limit_type, create_date);
""")
user_id = fields.Many2one('res.users', required=True, readonly=True)
scope = fields.Char(readonly=True)
ip = fields.Char(readonly=True)
limit_type = fields.Selection([
('send_email', 'Send Email'),
('code_check', 'Code Checking'),
], readonly=True)
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
auth_totp_enforce = fields.Boolean(
string="Enforce two-factor authentication",
)
auth_totp_policy = fields.Selection([
('employee_required', 'Employees only'),
('all_required', 'All users')
],
string="Two-factor authentication enforcing policy",
config_parameter='auth_totp.policy',
)
@api.onchange('auth_totp_enforce')
def _onchange_auth_totp_enforce(self):
if self.auth_totp_enforce:
self.auth_totp_policy = 'employee_required'
else:
self.auth_totp_policy = False
@api.model
def get_values(self):
res = super(ResConfigSettings, self).get_values()
res['auth_totp_enforce'] = bool(self.env['ir.config_parameter'].sudo().get_param('auth_totp.policy'))
return res
@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import babel.dates
import logging
from datetime import datetime, timedelta
from odoo import _, models
from odoo.exceptions import AccessDenied, UserError
from odoo.http import request
from odoo.tools.misc import babel_locale_parse, hmac
from odoo.addons.auth_totp.models.totp import hotp, TOTP
_logger = logging.getLogger(__name__)
TOTP_RATE_LIMITS = {
'send_email': (10, 3600),
'code_check': (10, 3600),
}
class Users(models.Model):
_inherit = 'res.users'
def _mfa_type(self):
r = super()._mfa_type()
if r is not None:
return r
ICP = self.env['ir.config_parameter'].sudo()
otp_required = False
if ICP.get_param('auth_totp.policy') == 'all_required':
otp_required = True
elif ICP.get_param('auth_totp.policy') == 'employee_required' and self.has_group('base.group_user'):
otp_required = True
if otp_required:
return 'totp_mail'
def _mfa_url(self):
r = super()._mfa_url()
if r is not None:
return r
if self._mfa_type() == 'totp_mail':
return '/web/login/totp'
def _totp_check(self, code):
self._totp_rate_limit('code_check')
user = self.sudo()
if user._mfa_type() != 'totp_mail':
return super()._totp_check(code)
key = self._get_totp_mail_key()
match = TOTP(key).match(code, window=3600, timestep=3600)
if match is None:
_logger.info("2FA check (mail): FAIL for %s %r", self, self.login)
raise AccessDenied(_("Verification failed, please double-check the 6-digit code"))
_logger.info("2FA check(mail): SUCCESS for %s %r", self, self.login)
self._totp_rate_limit_purge('code_check')
self._totp_rate_limit_purge('send_email')
return True
def _get_totp_mail_key(self):
self.ensure_one()
return hmac(self.env(su=True), 'auth_totp_mail-code', (self.id, self.login, self.login_date)).encode()
def _get_totp_mail_code(self):
self.ensure_one()
key = self._get_totp_mail_key()
now = datetime.now()
counter = int(datetime.timestamp(now) / 3600)
code = hotp(key, counter)
expiration = timedelta(seconds=3600)
lang = babel_locale_parse(self.env.context.get('lang') or self.lang)
expiration = babel.dates.format_timedelta(expiration, lang)
return str(code).zfill(6), expiration
def _send_totp_mail_code(self):
self.ensure_one()
self._totp_rate_limit('send_email')
if not self.email:
raise UserError(_("Cannot send email: user %s has no email address.", self.name))
template = self.env.ref('auth_totp_mail_enforce.mail_template_totp_mail_code').sudo()
context = {}
if request:
geoip = request.session.geoip
context.update({
'location': f"{geoip['city']}, {geoip['country_name']}" if geoip else None,
'device': request.httprequest.user_agent.platform.capitalize(),
'browser': request.httprequest.user_agent.browser.capitalize(),
'ip': request.httprequest.environ['REMOTE_ADDR'],
})
email_values = {
'email_to': self.email,
'email_cc': False,
'auto_delete': True,
'recipient_ids': [],
'partner_ids': [],
'scheduled_date': False,
}
with self.env.cr.savepoint():
template.with_context(**context).send_mail(
self.id, force_send=True, raise_exception=True, email_values=email_values, email_layout_xmlid='mail.mail_notification_light'
)
def _totp_rate_limit(self, limit_type):
self.ensure_one()
assert request, "A request is required to be able to rate limit TOTP related actions"
limit, interval = TOTP_RATE_LIMITS.get(limit_type)
RateLimitLog = self.env['auth.totp.rate.limit.log'].sudo()
ip = request.httprequest.environ['REMOTE_ADDR']
domain = [
('user_id', '=', self.id),
('create_date', '>=', datetime.now() - timedelta(seconds=interval)),
('limit_type', '=', limit_type),
('ip', '=', ip),
]
count = RateLimitLog.search_count(domain)
if count >= limit:
descriptions = {
'send_email': _('You reached the limit of authentication mails sent for your account'),
'code_check': _('You reached the limit of code verifications for your account'),
}
description = descriptions.get(limit_type)
raise AccessDenied(description)
RateLimitLog.create({
'user_id': self.id,
'ip': ip,
'limit_type': limit_type,
})
def _totp_rate_limit_purge(self, limit_type):
self.ensure_one()
assert request, "A request is required to be able to rate limit TOTP related actions"
ip = request.httprequest.environ['REMOTE_ADDR']
RateLimitLog = self.env['auth.totp.rate.limit.log'].sudo()
RateLimitLog.search([
('user_id', '=', self.id),
('limit_type', '=', limit_type),
('ip', '=', ip),
]).unlink()
@@ -0,0 +1,2 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_auth_totp_rate_limit_log","access_auth_totp_rate_limit_log","model_auth_totp_rate_limit_log","base.group_user",0,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_auth_totp_rate_limit_log access_auth_totp_rate_limit_log model_auth_totp_rate_limit_log base.group_user 0 0 0 0
@@ -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.auth_totp_mail_enforce</field>
<field name="model">res.config.settings</field>
<field name="priority" eval="40"/>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='allow_import']" position="before">
<div class="col-12 col-lg-6 o_setting_box" id="auth_totp_policy">
<div class="o_setting_left_pane">
<field name="auth_totp_enforce" />
</div>
<div class="o_setting_right_pane">
<label for="auth_totp_policy"/>
<div class="text-muted">
Enforce the two-factor authentication by email for employees or for all users
(including portal users) if they didn't enable any other two-factor authentication
method.
</div>
<div class="mt16" attrs="{'invisible': [('auth_totp_enforce', '=', False)]}">
<field name="auth_totp_policy" class="o_light_label" widget="radio"/>
</div>
</div>
</div>
</xpath>
</field>
</record>
</data>
</odoo>
@@ -0,0 +1,28 @@
<odoo>
<template id="auth_totp_mail_form" inherit_id="auth_totp.auth_totp_form">
<xpath expr="//form/div[1]" position="attributes">
<attribute name="t-if">user._mfa_type() == 'totp'</attribute>
</xpath>
<xpath expr="//form/div[1]" position="after">
<div t-if="user._mfa_type() == 'totp_mail'" class="mb-2 mt-2 text-muted">
<i class="fa fa-envelope-o"/>
To login, enter below the six-digit authentication code just sent via email to <t t-out="user.email"/>.
<br/>
</div>
</xpath>
<xpath expr="//form[1]" position="after">
<form method="POST" t-if="user._mfa_type() == 'totp_mail'">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="send_email" value="1"/>
<button type="submit" class="btn btn-secondary btn-block">Re-send email</button>
</form>
</xpath>
<xpath expr="//div[hasclass('border-top')]" position="before">
<div class="mb-2" t-if="user._mfa_type() == 'totp_mail'">
We strongly recommend enabling the two-factor authentication using an authenticator app to help secure your account.
<br/>
<a href="https://www.odoo.com/documentation/15.0/applications/general/auth/2fa.html" title="Learn More" target="_blank">Learn More</a>
</div>
</xpath>
</template>
</odoo>
+3
View File
@@ -1034,6 +1034,9 @@ class Users(models.Model):
if hasattr(self, 'check_credentials'):
_logger.warning("The check_credentials method of res.users has been renamed _check_credentials. One of your installed modules defines one, but it will not be called anymore.")
def _mfa_type(self):
""" If an MFA method is enabled, returns its type as a string. """
return
def _mfa_url(self):
""" If an MFA method is enabled, returns the URL for its second step. """