Browse Source

[9.0][FIX][IMP] Backport of auth_totp bug fixes and improvements from v10 PR (#898)

* [FIX] auth_totp: Permissions fix and other tweaks
* Slightly reword README
* Replace LasLabs logo with OCA one
* Overload _build_model in res.users model to add two MFA fields to the model
class's list of self-writeable fields, allowing these fields to be edited by
users without admin permissions for their own record
* Update view_users_form_simple_modif and the unit tests in the module based
on the self-writeable field change

* [IMP] auth_totp: Admin support
* Add MFA fields to normal res.users form view for admin access
* Update record rules to give admins read/unlink access to MFA authenticators

* [FIX] auth_totp: User deletion
* Add ondelete='cascade' to the res.users.authenticator.create wizard model to
properly support deletion of users who have just created an MFA authenticator

* [FIX] auth_totp: Website compatibility
* Add website compatibility by modifying the decorator on one of the routes and
updating the login_success request parameter as needed
pull/918/head
Oleg Bulkin 7 years ago
committed by Dave Lasley
parent
commit
0b6208a0f7
  1. 6
      auth_totp/README.rst
  2. 10
      auth_totp/controllers/main.py
  3. 10
      auth_totp/models/res_users.py
  4. 17
      auth_totp/security/res_users_authenticator_security.xml
  5. BIN
      auth_totp/static/description/icon.png
  6. 6
      auth_totp/tests/test_res_users.py
  7. 28
      auth_totp/views/res_users.xml
  8. 1
      auth_totp/wizards/res_users_authenticator_create.py

6
auth_totp/README.rst

@ -2,9 +2,9 @@
:target: http://www.gnu.org/licenses/lgpl.html
:alt: License: LGPL-3
===========
MFA Support
===========
====================
MFA Support via TOTP
====================
This module adds support for MFA using TOTP (time-based, one-time passwords).
It allows users to enable/disable MFA and manage authentication apps/devices

10
auth_totp/controllers/main.py

@ -55,6 +55,7 @@ class AuthTotp(Home):
user.generate_mfa_login_token()
request.session.logout(keep_db=True)
request.params['login_success'] = False
return http.local_redirect(
'/auth_totp/login',
query={
@ -64,7 +65,13 @@ class AuthTotp(Home):
keep_hash=True,
)
@http.route('/auth_totp/login', type='http', auth='none', methods=['GET'])
@http.route(
'/auth_totp/login',
type='http',
auth='public',
methods=['GET'],
website=True,
)
def mfa_login_get(self, *args, **kwargs):
return request.render('auth_totp.mfa_login', qcontext=request.params)
@ -127,6 +134,7 @@ class AuthTotp(Home):
temp_user.generate_mfa_login_token(60 * 24 * 30)
token = temp_user.mfa_login_token
request.session.authenticate(request.db, user.login, token, user.id)
request.params['login_success'] = True
redirect = request.params.get('redirect')
if not redirect:

10
auth_totp/models/res_users.py

@ -13,6 +13,13 @@ from ..exceptions import MfaTokenInvalidError, MfaTokenExpiredError
class ResUsers(models.Model):
_inherit = 'res.users'
@classmethod
def _build_model(cls, pool, cr):
model = super(ResUsers, cls)._build_model(pool, cr)
ModelCls = type(model)
ModelCls.SELF_WRITEABLE_FIELDS += ['mfa_enabled', 'authenticator_ids']
return model
mfa_enabled = fields.Boolean(string='MFA Enabled?')
authenticator_ids = fields.One2many(
comodel_name='res.users.authenticator',
@ -20,7 +27,8 @@ class ResUsers(models.Model):
string='Authentication Apps/Devices',
help='To delete an authentication app, remove it from this list. To'
' add a new authentication app, please use the button to the'
' right.',
' right. If the button is not present, you do not have the'
' permissions to do this.',
)
mfa_login_token = fields.Char()
mfa_login_token_exp = fields.Datetime()

17
auth_totp/security/res_users_authenticator_security.xml

@ -6,13 +6,24 @@
-->
<odoo>
<record id="auth_access_owners_only" model="ir.rule">
<field name="name">MFA Authenticators - Owner Only</field>
<record id="auth_access_owners" model="ir.rule">
<field name="name">MFA Authenticators - Owner Access</field>
<field name="model_id" ref="model_res_users_authenticator"/>
<field name="domain_force">[('user_id', '=?', user.id)]</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="perm_unlink" eval="True"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="auth_access_admins" model="ir.rule">
<field name="name">MFA Authenticators - Admin Read/Unlink</field>
<field name="model_id" ref="model_res_users_authenticator"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="True"/>
<field name="groups" eval="[(4, ref('base.group_erp_manager'))]"/>
</record>
</odoo>

BIN
auth_totp/static/description/icon.png

Before

Width: 600  |  Height: 518  |  Size: 10 KiB

After

Width: 128  |  Height: 128  |  Size: 9.2 KiB

6
auth_totp/tests/test_res_users.py

@ -27,6 +27,12 @@ class TestResUsers(TransactionCase):
self.test_user.authenticator_ids = False
self.env.uid = self.test_user.id
def test_build_model_mfa_fields_in_self_writeable_list(self):
'''Should add MFA fields to list of fields users can modify for self'''
ResUsersClass = type(self.test_user)
self.assertIn('mfa_enabled', ResUsersClass.SELF_WRITEABLE_FIELDS)
self.assertIn('authenticator_ids', ResUsersClass.SELF_WRITEABLE_FIELDS)
def test_check_enabled_with_authenticator_mfa_no_auth(self):
'''Should raise correct error if MFA enabled without authenticators'''
with self.assertRaisesRegexp(ValidationError, 'locked out'):

28
auth_totp/views/res_users.xml

@ -6,6 +6,23 @@
-->
<odoo>
<record id="view_users_form" model="ir.ui.view">
<field name="name">User Form - MFA Settings</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='messaging']" position="after">
<group string="MFA Settings" name="mfa_settings" col="8">
<p colspan="8">Note: Please have user add at least one authentication app/device before enabling MFA.</p>
<label for="mfa_enabled" colspan="3"/>
<field name="mfa_enabled" colspan="5" nolabel="1"/>
<label for="authenticator_ids" colspan="3"/>
<field name="authenticator_ids" widget="many2many_tags" options="{'no_create': True}" domain="[('user_id', '=', id)]" colspan="5" nolabel="1"/>
</group>
</xpath>
</field>
</record>
<record id="view_users_form_simple_modif" model="ir.ui.view">
<field name="name">Change My Preferences - MFA Settings</field>
<field name="model">res.users</field>
@ -13,12 +30,11 @@
<field name="arch" type="xml">
<xpath expr="//footer" position="before">
<group string="MFA Settings" name="mfa_settings" col="8">
<div colspan="8" class="oe_mb8">
<span>Note: Please add at least one authentication app/device before enabling MFA.</span>
</div>
<field name="mfa_enabled"/>
<newline/>
<field name="authenticator_ids" widget="many2many_tags" options="{'no_create': True}" colspan="7"/>
<p colspan="8">Note: Please add at least one authentication app/device before enabling MFA.</p>
<label for="mfa_enabled" colspan="3"/>
<field name="mfa_enabled" readonly="0" colspan="5" nolabel="1"/>
<label for="authenticator_ids" colspan="3"/>
<field name="authenticator_ids" widget="many2many_tags" options="{'no_create': True}" colspan="4" readonly="0" nolabel="1"/>
<button string="Add New App/Device" type="action" name="%(res_users_authenticator_create_action)d" colspan="1"/>
</group>
</xpath>

1
auth_totp/wizards/res_users_authenticator_create.py

@ -47,6 +47,7 @@ class ResUsersAuthenticatorCreate(models.TransientModel):
' will be tied to',
readonly=True,
index=True,
ondelete='cascade',
)
confirmation_code = fields.Char(
string='Confirmation Code',

Loading…
Cancel
Save