diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/auth_totp/tests | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/auth_totp/tests')
| -rw-r--r-- | addons/auth_totp/tests/__init__.py | 1 | ||||
| -rw-r--r-- | addons/auth_totp/tests/test_totp.py | 83 |
2 files changed, 84 insertions, 0 deletions
diff --git a/addons/auth_totp/tests/__init__.py b/addons/auth_totp/tests/__init__.py new file mode 100644 index 00000000..31e2de2b --- /dev/null +++ b/addons/auth_totp/tests/__init__.py @@ -0,0 +1 @@ +from . import test_totp diff --git a/addons/auth_totp/tests/test_totp.py b/addons/auth_totp/tests/test_totp.py new file mode 100644 index 00000000..5b0e21b2 --- /dev/null +++ b/addons/auth_totp/tests/test_totp.py @@ -0,0 +1,83 @@ +import time +from xmlrpc.client import Fault + +from passlib.totp import TOTP + +from odoo import http +from odoo.exceptions import AccessDenied +from odoo.service import common as auth, model +from odoo.tests import tagged, HttpCase, get_db_name + +from ..controllers.home import Home + +@tagged('post_install', '-at_install') +class TestTOTP(HttpCase): + def setUp(self): + super().setUp() + + totp = None + # might be possible to do client-side using `crypto.subtle` instead of + # this horror show, but requires working on 64b integers, & BigInt is + # significantly less well supported than crypto + def totp_hook(self, secret=None): + nonlocal totp + if totp is None: + totp = TOTP(secret) + if secret: + return totp.generate().token + else: + # on check, take advantage of window because previous token has been + # "burned" so we can't generate the same, but tour is so fast + # we're pretty certainly within the same 30s + return totp.generate(time.time() + 30).token + # because not preprocessed by ControllerType metaclass + totp_hook.routing_type = 'json' + self.env['ir.http']._clear_routing_map() + # patch Home to add test endpoint + Home.totp_hook = http.route('/totphook', type='json', auth='none')(totp_hook) + # remove endpoint and destroy routing map + @self.addCleanup + def _cleanup(): + del Home.totp_hook + self.env['ir.http']._clear_routing_map() + + def test_totp(self): + # 1. Enable 2FA + self.start_tour('/web', 'totp_tour_setup', login='demo') + + # 2. Verify that RPC is blocked because 2FA is on. + self.assertFalse( + self.xmlrpc_common.authenticate(get_db_name(), 'demo', 'demo', {}), + "Should not have returned a uid" + ) + self.assertFalse( + self.xmlrpc_common.authenticate(get_db_name(), 'demo', 'demo', {'interactive': True}), + 'Trying to fake the auth type should not work' + ) + uid = self.env.ref('base.user_demo').id + with self.assertRaisesRegex(Fault, r'Access Denied'): + self.xmlrpc_object.execute_kw( + get_db_name(), uid, 'demo', + 'res.users', 'read', [uid, ['login']] + ) + + # 3. Check 2FA is required and disable it + self.start_tour('/', 'totp_login_enabled', login=None) + + # 4. Finally, check that 2FA is in fact disabled + self.start_tour('/', 'totp_login_disabled', login=None) + + # 5. Check that rpc is now re-allowed + uid = self.xmlrpc_common.authenticate(get_db_name(), 'demo', 'demo', {}) + self.assertEqual(uid, self.env.ref('base.user_demo').id) + [r] = self.xmlrpc_object.execute_kw( + get_db_name(), uid, 'demo', + 'res.users', 'read', [uid, ['login']] + ) + self.assertEqual(r['login'], 'demo') + + + def test_totp_administration(self): + self.start_tour('/web', 'totp_tour_setup', login='demo') + self.start_tour('/web', 'totp_admin_disables', login='admin') + self.start_tour('/', 'totp_login_disabled', login=None) |
