import time 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 from ..controllers.home import Home @tagged('post_install', '-at_install') class TestTOTP(HttpCase): def setUp(self): super().setUp() totp = None # test endpoint as doing totp on the client side is not really an option # (needs sha1 and hmac + BE packing of 64b integers) 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. Check 2FA is required self.start_tour('/', 'totp_login_enabled', login=None) # 3. TODO: verify that RPC is blocked because 2FA is on. # 4. Finally, disable 2FA otherwise we can't re-login self.start_tour('/', 'totp_login_disabled', login=None) 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)