diff --git a/auth_totp/README.rst b/auth_totp/README.rst index 85a18c141..1046cf8ee 100644 --- a/auth_totp/README.rst +++ b/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 diff --git a/auth_totp/controllers/main.py b/auth_totp/controllers/main.py index 52638d6dd..2262469be 100644 --- a/auth_totp/controllers/main.py +++ b/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: diff --git a/auth_totp/models/res_users.py b/auth_totp/models/res_users.py index 91c9a05a7..1d413ae8e 100644 --- a/auth_totp/models/res_users.py +++ b/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() diff --git a/auth_totp/security/res_users_authenticator_security.xml b/auth_totp/security/res_users_authenticator_security.xml index a96b9cc9a..5e9adc1fb 100644 --- a/auth_totp/security/res_users_authenticator_security.xml +++ b/auth_totp/security/res_users_authenticator_security.xml @@ -6,13 +6,24 @@ --> - - MFA Authenticators - Owner Only + + MFA Authenticators - Owner Access [('user_id', '=?', user.id)] - + + + + + + MFA Authenticators - Admin Read/Unlink + + + + + + diff --git a/auth_totp/static/description/icon.png b/auth_totp/static/description/icon.png index bb990930a..3a0328b51 100644 Binary files a/auth_totp/static/description/icon.png and b/auth_totp/static/description/icon.png differ diff --git a/auth_totp/tests/test_res_users.py b/auth_totp/tests/test_res_users.py index a0b030593..b6098eb68 100644 --- a/auth_totp/tests/test_res_users.py +++ b/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'): diff --git a/auth_totp/views/res_users.xml b/auth_totp/views/res_users.xml index c4aa01518..6a584c4f0 100644 --- a/auth_totp/views/res_users.xml +++ b/auth_totp/views/res_users.xml @@ -6,6 +6,23 @@ --> + + User Form - MFA Settings + res.users + + + + + Note: Please have user add at least one authentication app/device before enabling MFA. + + + + + + + + + Change My Preferences - MFA Settings res.users @@ -13,12 +30,11 @@ - - Note: Please add at least one authentication app/device before enabling MFA. - - - - + Note: Please add at least one authentication app/device before enabling MFA. + + + + diff --git a/auth_totp/wizards/res_users_authenticator_create.py b/auth_totp/wizards/res_users_authenticator_create.py index 48b7fcab2..e7c1203c3 100644 --- a/auth_totp/wizards/res_users_authenticator_create.py +++ b/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',
Note: Please have user add at least one authentication app/device before enabling MFA.
Note: Please add at least one authentication app/device before enabling MFA.