summaryrefslogtreecommitdiff
path: root/addons/auth_totp/tests
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/auth_totp/tests
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/auth_totp/tests')
-rw-r--r--addons/auth_totp/tests/__init__.py1
-rw-r--r--addons/auth_totp/tests/test_totp.py83
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)