|
@ -3,7 +3,7 @@ |
|
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
|
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). |
|
|
|
|
|
|
|
|
from datetime import datetime |
|
|
from datetime import datetime |
|
|
import mock |
|
|
|
|
|
|
|
|
from mock import MagicMock, patch |
|
|
from odoo.http import Response |
|
|
from odoo.http import Response |
|
|
from odoo.tests.common import TransactionCase |
|
|
from odoo.tests.common import TransactionCase |
|
|
from ..controllers.main import AuthTotp |
|
|
from ..controllers.main import AuthTotp |
|
@ -12,18 +12,19 @@ CONTROLLER_PATH = 'odoo.addons.auth_totp.controllers.main' |
|
|
REQUEST_PATH = CONTROLLER_PATH + '.request' |
|
|
REQUEST_PATH = CONTROLLER_PATH + '.request' |
|
|
SUPER_PATH = CONTROLLER_PATH + '.Home.web_login' |
|
|
SUPER_PATH = CONTROLLER_PATH + '.Home.web_login' |
|
|
JSON_PATH = CONTROLLER_PATH + '.JsonSecureCookie' |
|
|
JSON_PATH = CONTROLLER_PATH + '.JsonSecureCookie' |
|
|
ENVIRONMENT_PATH = CONTROLLER_PATH + '.Environment' |
|
|
|
|
|
RESPONSE_PATH = CONTROLLER_PATH + '.Response' |
|
|
RESPONSE_PATH = CONTROLLER_PATH + '.Response' |
|
|
DATETIME_PATH = CONTROLLER_PATH + '.datetime' |
|
|
DATETIME_PATH = CONTROLLER_PATH + '.datetime' |
|
|
REDIRECT_PATH = CONTROLLER_PATH + '.http.redirect_with_hash' |
|
|
REDIRECT_PATH = CONTROLLER_PATH + '.http.redirect_with_hash' |
|
|
TRANSLATE_PATH_CONT = CONTROLLER_PATH + '._' |
|
|
TRANSLATE_PATH_CONT = CONTROLLER_PATH + '._' |
|
|
MODEL_PATH = 'odoo.addons.auth_totp.models.res_users' |
|
|
MODEL_PATH = 'odoo.addons.auth_totp.models.res_users' |
|
|
GENERATE_PATH = MODEL_PATH + '.ResUsers.generate_mfa_login_token' |
|
|
|
|
|
VALIDATE_PATH = MODEL_PATH + '.ResUsers.validate_mfa_confirmation_code' |
|
|
VALIDATE_PATH = MODEL_PATH + '.ResUsers.validate_mfa_confirmation_code' |
|
|
TRANSLATE_PATH_MOD = MODEL_PATH + '._' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mock.patch(REQUEST_PATH) |
|
|
|
|
|
|
|
|
class AssignableDict(dict): |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@patch(REQUEST_PATH) |
|
|
class TestAuthTotp(TransactionCase): |
|
|
class TestAuthTotp(TransactionCase): |
|
|
|
|
|
|
|
|
def setUp(self): |
|
|
def setUp(self): |
|
@ -32,104 +33,40 @@ class TestAuthTotp(TransactionCase): |
|
|
self.test_controller = AuthTotp() |
|
|
self.test_controller = AuthTotp() |
|
|
|
|
|
|
|
|
self.test_user = self.env.ref('base.user_root') |
|
|
self.test_user = self.env.ref('base.user_root') |
|
|
|
|
|
self.test_user.mfa_enabled = False |
|
|
|
|
|
self.test_user.authenticator_ids = False |
|
|
self.env['res.users.authenticator'].create({ |
|
|
self.env['res.users.authenticator'].create({ |
|
|
'name': 'Test Authenticator', |
|
|
'name': 'Test Authenticator', |
|
|
'secret_key': 'iamatestsecretyo', |
|
|
'secret_key': 'iamatestsecretyo', |
|
|
'user_id': self.test_user.id, |
|
|
'user_id': self.test_user.id, |
|
|
}) |
|
|
}) |
|
|
self.test_user.mfa_enabled = True |
|
|
self.test_user.mfa_enabled = True |
|
|
self.test_user.generate_mfa_login_token() |
|
|
|
|
|
self.test_user.trusted_device_ids = None |
|
|
|
|
|
|
|
|
|
|
|
# Needed when tests are run with no prior requests (e.g. on a new DB) |
|
|
# Needed when tests are run with no prior requests (e.g. on a new DB) |
|
|
patcher = mock.patch('odoo.http.request') |
|
|
|
|
|
|
|
|
patcher = patch('odoo.http.request') |
|
|
self.addCleanup(patcher.stop) |
|
|
self.addCleanup(patcher.stop) |
|
|
patcher.start() |
|
|
patcher.start() |
|
|
|
|
|
|
|
|
@mock.patch(SUPER_PATH) |
|
|
|
|
|
def test_web_login_no_password_login(self, super_mock, request_mock): |
|
|
|
|
|
'''Should return wrapped result of super if no password log in''' |
|
|
|
|
|
test_response = 'Test Response' |
|
|
|
|
|
super_mock.return_value = test_response |
|
|
|
|
|
request_mock.params = {} |
|
|
|
|
|
|
|
|
|
|
|
self.assertEqual(self.test_controller.web_login().data, test_response) |
|
|
|
|
|
|
|
|
|
|
|
@mock.patch(SUPER_PATH) |
|
|
|
|
|
def test_web_login_user_no_mfa(self, super_mock, request_mock): |
|
|
|
|
|
'''Should return wrapped result of super if user did not enable MFA''' |
|
|
|
|
|
test_response = 'Test Response' |
|
|
|
|
|
super_mock.return_value = test_response |
|
|
|
|
|
request_mock.params = {'login_success': True} |
|
|
|
|
|
request_mock.env = self.env |
|
|
|
|
|
request_mock.uid = self.test_user.id |
|
|
|
|
|
self.test_user.mfa_enabled = False |
|
|
|
|
|
|
|
|
@patch(SUPER_PATH) |
|
|
|
|
|
def test_web_login_mfa_needed(self, super_mock, request_mock): |
|
|
|
|
|
'''Should update session and redirect correctly if MFA login needed''' |
|
|
|
|
|
request_mock.session = {'mfa_login_needed': True} |
|
|
|
|
|
request_mock.params = {'redirect': 'Test Redir'} |
|
|
|
|
|
|
|
|
self.assertEqual(self.test_controller.web_login().data, test_response) |
|
|
|
|
|
|
|
|
test_result = self.test_controller.web_login() |
|
|
|
|
|
super_mock.assert_called_once() |
|
|
|
|
|
self.assertIn('/auth_totp/login?redirect=Test+Redir', test_result.data) |
|
|
|
|
|
self.assertFalse(request_mock.session['mfa_login_needed']) |
|
|
|
|
|
|
|
|
@mock.patch(JSON_PATH) |
|
|
|
|
|
@mock.patch(SUPER_PATH) |
|
|
|
|
|
def test_web_login_valid_cookie(self, super_mock, json_mock, request_mock): |
|
|
|
|
|
'''Should return wrapped result of super if valid device cookie''' |
|
|
|
|
|
|
|
|
@patch(SUPER_PATH) |
|
|
|
|
|
def test_web_login_mfa_not_needed(self, super_mock, request_mock): |
|
|
|
|
|
'''Should return result of calling super if MFA login not needed''' |
|
|
test_response = 'Test Response' |
|
|
test_response = 'Test Response' |
|
|
super_mock.return_value = test_response |
|
|
super_mock.return_value = test_response |
|
|
request_mock.params = {'login_success': True} |
|
|
|
|
|
request_mock.env = self.env |
|
|
|
|
|
request_mock.uid = self.test_user.id |
|
|
|
|
|
|
|
|
|
|
|
device_model = self.env['res.users.device'] |
|
|
|
|
|
test_device = device_model.create({'user_id': self.test_user.id}) |
|
|
|
|
|
json_mock.unserialize().get.return_value = test_device.id |
|
|
|
|
|
|
|
|
request_mock.session = {} |
|
|
|
|
|
|
|
|
self.assertEqual(self.test_controller.web_login().data, test_response) |
|
|
self.assertEqual(self.test_controller.web_login().data, test_response) |
|
|
|
|
|
|
|
|
@mock.patch(SUPER_PATH) |
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
def test_web_login_no_cookie(self, gen_mock, super_mock, request_mock): |
|
|
|
|
|
'''Should respond correctly if no device cookie with expected key''' |
|
|
|
|
|
request_mock.env = self.env |
|
|
|
|
|
request_mock.uid = self.test_user.id |
|
|
|
|
|
request_mock.params = { |
|
|
|
|
|
'login_success': True, |
|
|
|
|
|
'redirect': 'Test Redir', |
|
|
|
|
|
} |
|
|
|
|
|
self.test_user.mfa_login_token = 'Test Token' |
|
|
|
|
|
request_mock.httprequest.cookies = {} |
|
|
|
|
|
request_mock.reset_mock() |
|
|
|
|
|
|
|
|
|
|
|
test_result = self.test_controller.web_login() |
|
|
|
|
|
gen_mock.assert_called_once_with() |
|
|
|
|
|
request_mock.session.logout.assert_called_once_with(keep_db=True) |
|
|
|
|
|
self.assertIn( |
|
|
|
|
|
'/auth_totp/login?redirect=Test+Redir&mfa_login_token=Test+Token', |
|
|
|
|
|
test_result.data, |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
@mock.patch(SUPER_PATH) |
|
|
|
|
|
@mock.patch(JSON_PATH) |
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
def test_web_login_bad_device_id( |
|
|
|
|
|
self, gen_mock, json_mock, super_mock, request_mock |
|
|
|
|
|
): |
|
|
|
|
|
'''Should respond correctly if invalid device_id in device cookie''' |
|
|
|
|
|
request_mock.env = self.env |
|
|
|
|
|
request_mock.uid = self.test_user.id |
|
|
|
|
|
request_mock.params = { |
|
|
|
|
|
'login_success': True, |
|
|
|
|
|
'redirect': 'Test Redir', |
|
|
|
|
|
} |
|
|
|
|
|
self.test_user.mfa_login_token = 'Test Token' |
|
|
|
|
|
json_mock.unserialize.return_value = {'device_id': 1} |
|
|
|
|
|
request_mock.reset_mock() |
|
|
|
|
|
|
|
|
|
|
|
test_result = self.test_controller.web_login() |
|
|
|
|
|
gen_mock.assert_called_once_with() |
|
|
|
|
|
request_mock.session.logout.assert_called_once_with(keep_db=True) |
|
|
|
|
|
self.assertIn( |
|
|
|
|
|
'/auth_totp/login?redirect=Test+Redir&mfa_login_token=Test+Token', |
|
|
|
|
|
test_result.data, |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
def test_mfa_login_get(self, request_mock): |
|
|
def test_mfa_login_get(self, request_mock): |
|
|
'''Should render mfa_login template with correct context''' |
|
|
'''Should render mfa_login template with correct context''' |
|
|
request_mock.render.return_value = 'Test Value' |
|
|
request_mock.render.return_value = 'Test Value' |
|
@ -141,51 +78,40 @@ class TestAuthTotp(TransactionCase): |
|
|
qcontext=request_mock.params, |
|
|
qcontext=request_mock.params, |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
@mock.patch(TRANSLATE_PATH_MOD) |
|
|
|
|
|
def test_mfa_login_post_invalid_token(self, tl_mock, request_mock): |
|
|
|
|
|
'''Should return correct redirect if login token invalid''' |
|
|
|
|
|
|
|
|
@patch(TRANSLATE_PATH_CONT) |
|
|
|
|
|
def test_mfa_login_post_no_login(self, tl_mock, request_mock): |
|
|
|
|
|
'''Should redirect correctly if login missing from session''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
request_mock.params = { |
|
|
|
|
|
'mfa_login_token': 'Invalid Token', |
|
|
|
|
|
'redirect': 'Test Redir', |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
request_mock.session = {} |
|
|
|
|
|
request_mock.params = {'redirect': 'Test Redir'} |
|
|
tl_mock.side_effect = lambda arg: arg |
|
|
tl_mock.side_effect = lambda arg: arg |
|
|
tl_mock.reset_mock() |
|
|
tl_mock.reset_mock() |
|
|
|
|
|
|
|
|
test_result = self.test_controller.mfa_login_post() |
|
|
test_result = self.test_controller.mfa_login_post() |
|
|
tl_mock.assert_called_once() |
|
|
tl_mock.assert_called_once() |
|
|
self.assertIn('/web/login?redirect=Test+Redir', test_result.data) |
|
|
self.assertIn('/web/login?redirect=Test+Redir', test_result.data) |
|
|
self.assertIn( |
|
|
|
|
|
'&error=Your+MFA+login+token+is+not+valid.', |
|
|
|
|
|
test_result.data, |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
self.assertIn('&error=You+must+log+in', test_result.data) |
|
|
|
|
|
|
|
|
@mock.patch(TRANSLATE_PATH_MOD) |
|
|
|
|
|
def test_mfa_login_post_expired_token(self, tl_mock, request_mock): |
|
|
|
|
|
'''Should return correct redirect if login token expired''' |
|
|
|
|
|
|
|
|
@patch(TRANSLATE_PATH_CONT) |
|
|
|
|
|
def test_mfa_login_post_invalid_login(self, tl_mock, request_mock): |
|
|
|
|
|
'''Should redirect correctly if invalid login in session''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
self.test_user.generate_mfa_login_token(-1) |
|
|
|
|
|
request_mock.params = { |
|
|
|
|
|
'mfa_login_token': self.test_user.mfa_login_token, |
|
|
|
|
|
'redirect': 'Test Redir', |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
request_mock.session = {'login': 'Invalid Login'} |
|
|
|
|
|
request_mock.params = {'redirect': 'Test Redir'} |
|
|
tl_mock.side_effect = lambda arg: arg |
|
|
tl_mock.side_effect = lambda arg: arg |
|
|
tl_mock.reset_mock() |
|
|
tl_mock.reset_mock() |
|
|
|
|
|
|
|
|
test_result = self.test_controller.mfa_login_post() |
|
|
test_result = self.test_controller.mfa_login_post() |
|
|
tl_mock.assert_called_once() |
|
|
tl_mock.assert_called_once() |
|
|
self.assertIn('/web/login?redirect=Test+Redir', test_result.data) |
|
|
self.assertIn('/web/login?redirect=Test+Redir', test_result.data) |
|
|
self.assertIn( |
|
|
|
|
|
'&error=Your+MFA+login+token+has+expired.', |
|
|
|
|
|
test_result.data, |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
self.assertIn('&error=You+must+log+in', test_result.data) |
|
|
|
|
|
|
|
|
@mock.patch(TRANSLATE_PATH_CONT) |
|
|
|
|
|
|
|
|
@patch(TRANSLATE_PATH_CONT) |
|
|
def test_mfa_login_post_invalid_conf_code(self, tl_mock, request_mock): |
|
|
def test_mfa_login_post_invalid_conf_code(self, tl_mock, request_mock): |
|
|
'''Should return correct redirect if confirmation code is invalid''' |
|
|
'''Should return correct redirect if confirmation code is invalid''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
|
|
|
request_mock.session = {'login': self.test_user.login} |
|
|
request_mock.params = { |
|
|
request_mock.params = { |
|
|
'mfa_login_token': self.test_user.mfa_login_token, |
|
|
|
|
|
'redirect': 'Test Redir', |
|
|
'redirect': 'Test Redir', |
|
|
'confirmation_code': 'Invalid Code', |
|
|
'confirmation_code': 'Invalid Code', |
|
|
} |
|
|
} |
|
@ -199,135 +125,119 @@ class TestAuthTotp(TransactionCase): |
|
|
'&error=Your+confirmation+code+is+not+correct.', |
|
|
'&error=Your+confirmation+code+is+not+correct.', |
|
|
test_result.data, |
|
|
test_result.data, |
|
|
) |
|
|
) |
|
|
self.assertIn( |
|
|
|
|
|
'&mfa_login_token=%s' % self.test_user.mfa_login_token, |
|
|
|
|
|
test_result.data, |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
@mock.patch(VALIDATE_PATH) |
|
|
|
|
|
def test_mfa_login_post_new_token(self, val_mock, gen_mock, request_mock): |
|
|
|
|
|
'''Should refresh user's login token w/right lifetime if info valid''' |
|
|
|
|
|
|
|
|
@patch(VALIDATE_PATH) |
|
|
|
|
|
def test_mfa_login_post_valid_conf_code(self, val_mock, request_mock): |
|
|
|
|
|
'''Should correctly update session if confirmation code is valid''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
request_mock.db = self.registry.db_name |
|
|
|
|
|
test_token = self.test_user.mfa_login_token |
|
|
|
|
|
request_mock.params = {'mfa_login_token': test_token} |
|
|
|
|
|
|
|
|
request_mock.session = AssignableDict(login=self.test_user.login) |
|
|
|
|
|
request_mock.session.authenticate = MagicMock() |
|
|
|
|
|
test_conf_code = 'Test Code' |
|
|
|
|
|
request_mock.params = {'confirmation_code': test_conf_code} |
|
|
val_mock.return_value = True |
|
|
val_mock.return_value = True |
|
|
gen_mock.reset_mock() |
|
|
|
|
|
self.test_controller.mfa_login_post() |
|
|
self.test_controller.mfa_login_post() |
|
|
|
|
|
|
|
|
gen_mock.assert_called_once_with(60 * 24 * 30) |
|
|
|
|
|
|
|
|
val_mock.assert_called_once_with(test_conf_code) |
|
|
|
|
|
resulting_flag = request_mock.session['mfa_login_active'] |
|
|
|
|
|
self.assertEqual(resulting_flag, self.test_user.id) |
|
|
|
|
|
|
|
|
@mock.patch(ENVIRONMENT_PATH) |
|
|
|
|
|
@mock.patch(VALIDATE_PATH) |
|
|
|
|
|
def test_mfa_login_post_session(self, val_mock, env_mock, request_mock): |
|
|
|
|
|
'''Should log user in with new token as password if info valid''' |
|
|
|
|
|
|
|
|
@patch(VALIDATE_PATH) |
|
|
|
|
|
def test_mfa_login_post_pass_auth_fail(self, val_mock, request_mock): |
|
|
|
|
|
'''Should not set success param if password auth fails''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
request_mock.db = self.registry.db_name |
|
|
|
|
|
old_test_token = self.test_user.mfa_login_token |
|
|
|
|
|
request_mock.params = {'mfa_login_token': old_test_token} |
|
|
|
|
|
|
|
|
request_mock.db = test_db = 'Test DB' |
|
|
|
|
|
test_password = 'Test Password' |
|
|
|
|
|
request_mock.session = AssignableDict( |
|
|
|
|
|
login=self.test_user.login, password=test_password, |
|
|
|
|
|
) |
|
|
|
|
|
request_mock.session.authenticate = MagicMock(return_value=False) |
|
|
|
|
|
request_mock.params = {} |
|
|
val_mock.return_value = True |
|
|
val_mock.return_value = True |
|
|
env_mock.return_value = self.env |
|
|
|
|
|
request_mock.reset_mock() |
|
|
|
|
|
self.test_controller.mfa_login_post() |
|
|
self.test_controller.mfa_login_post() |
|
|
|
|
|
|
|
|
new_test_token = self.test_user.mfa_login_token |
|
|
|
|
|
request_mock.session.authenticate.assert_called_once_with( |
|
|
request_mock.session.authenticate.assert_called_once_with( |
|
|
request_mock.db, |
|
|
|
|
|
self.test_user.login, |
|
|
|
|
|
new_test_token, |
|
|
|
|
|
self.test_user.id, |
|
|
|
|
|
|
|
|
test_db, self.test_user.login, test_password, |
|
|
) |
|
|
) |
|
|
|
|
|
self.assertFalse(request_mock.params.get('login_success')) |
|
|
|
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
@mock.patch(VALIDATE_PATH) |
|
|
|
|
|
def test_mfa_login_post_redirect(self, val_mock, gen_mock, request_mock): |
|
|
|
|
|
|
|
|
@patch(VALIDATE_PATH) |
|
|
|
|
|
def test_mfa_login_post_pass_auth_success(self, val_mock, request_mock): |
|
|
|
|
|
'''Should set success param if password auth succeeds''' |
|
|
|
|
|
request_mock.env = self.env |
|
|
|
|
|
request_mock.db = test_db = 'Test DB' |
|
|
|
|
|
test_password = 'Test Password' |
|
|
|
|
|
request_mock.session = AssignableDict( |
|
|
|
|
|
login=self.test_user.login, password=test_password, |
|
|
|
|
|
) |
|
|
|
|
|
request_mock.session.authenticate = MagicMock(return_value=True) |
|
|
|
|
|
request_mock.params = {} |
|
|
|
|
|
val_mock.return_value = True |
|
|
|
|
|
self.test_controller.mfa_login_post() |
|
|
|
|
|
|
|
|
|
|
|
request_mock.session.authenticate.assert_called_once_with( |
|
|
|
|
|
test_db, self.test_user.login, test_password, |
|
|
|
|
|
) |
|
|
|
|
|
self.assertTrue(request_mock.params.get('login_success')) |
|
|
|
|
|
|
|
|
|
|
|
@patch(VALIDATE_PATH) |
|
|
|
|
|
def test_mfa_login_post_redirect(self, val_mock, request_mock): |
|
|
'''Should return correct redirect if info valid and redirect present''' |
|
|
'''Should return correct redirect if info valid and redirect present''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
request_mock.db = self.registry.db_name |
|
|
|
|
|
|
|
|
request_mock.session = AssignableDict(login=self.test_user.login) |
|
|
|
|
|
request_mock.session.authenticate = MagicMock(return_value=True) |
|
|
test_redir = 'Test Redir' |
|
|
test_redir = 'Test Redir' |
|
|
request_mock.params = { |
|
|
|
|
|
'mfa_login_token': self.test_user.mfa_login_token, |
|
|
|
|
|
'redirect': test_redir, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
request_mock.params = {'redirect': test_redir} |
|
|
val_mock.return_value = True |
|
|
val_mock.return_value = True |
|
|
|
|
|
|
|
|
test_result = self.test_controller.mfa_login_post() |
|
|
test_result = self.test_controller.mfa_login_post() |
|
|
self.assertIn("window.location = '%s'" % test_redir, test_result.data) |
|
|
self.assertIn("window.location = '%s'" % test_redir, test_result.data) |
|
|
|
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
@mock.patch(VALIDATE_PATH) |
|
|
|
|
|
def test_mfa_login_post_redir_def(self, val_mock, gen_mock, request_mock): |
|
|
|
|
|
|
|
|
@patch(VALIDATE_PATH) |
|
|
|
|
|
def test_mfa_login_post_redir_def(self, val_mock, request_mock): |
|
|
'''Should return redirect to /web if info valid and no redirect''' |
|
|
'''Should return redirect to /web if info valid and no redirect''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
request_mock.db = self.registry.db_name |
|
|
|
|
|
test_token = self.test_user.mfa_login_token |
|
|
|
|
|
request_mock.params = {'mfa_login_token': test_token} |
|
|
|
|
|
|
|
|
request_mock.session = AssignableDict(login=self.test_user.login) |
|
|
|
|
|
request_mock.session.authenticate = MagicMock(return_value=True) |
|
|
|
|
|
request_mock.params = {} |
|
|
val_mock.return_value = True |
|
|
val_mock.return_value = True |
|
|
|
|
|
|
|
|
test_result = self.test_controller.mfa_login_post() |
|
|
test_result = self.test_controller.mfa_login_post() |
|
|
self.assertIn("window.location = '/web'", test_result.data) |
|
|
self.assertIn("window.location = '/web'", test_result.data) |
|
|
|
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
@mock.patch(VALIDATE_PATH) |
|
|
|
|
|
def test_mfa_login_post_device(self, val_mock, gen_mock, request_mock): |
|
|
|
|
|
'''Should add trusted device to user if remember flag set''' |
|
|
|
|
|
request_mock.env = self.env |
|
|
|
|
|
request_mock.db = self.registry.db_name |
|
|
|
|
|
test_token = self.test_user.mfa_login_token |
|
|
|
|
|
request_mock.params = { |
|
|
|
|
|
'mfa_login_token': test_token, |
|
|
|
|
|
'remember_device': True, |
|
|
|
|
|
} |
|
|
|
|
|
val_mock.return_value = True |
|
|
|
|
|
self.test_controller.mfa_login_post() |
|
|
|
|
|
|
|
|
|
|
|
self.assertEqual(len(self.test_user.trusted_device_ids), 1) |
|
|
|
|
|
|
|
|
|
|
|
@mock.patch(RESPONSE_PATH) |
|
|
|
|
|
@mock.patch(JSON_PATH) |
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
@mock.patch(VALIDATE_PATH) |
|
|
|
|
|
|
|
|
@patch(RESPONSE_PATH) |
|
|
|
|
|
@patch(JSON_PATH) |
|
|
|
|
|
@patch(VALIDATE_PATH) |
|
|
def test_mfa_login_post_cookie_werkzeug_cookie( |
|
|
def test_mfa_login_post_cookie_werkzeug_cookie( |
|
|
self, val_mock, gen_mock, json_mock, resp_mock, request_mock |
|
|
|
|
|
|
|
|
self, val_mock, json_mock, resp_mock, request_mock |
|
|
): |
|
|
): |
|
|
'''Should create Werkzeug cookie w/right info if remember flag set''' |
|
|
'''Should create Werkzeug cookie w/right info if remember flag set''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
request_mock.db = self.registry.db_name |
|
|
|
|
|
test_token = self.test_user.mfa_login_token |
|
|
|
|
|
request_mock.params = { |
|
|
|
|
|
'mfa_login_token': test_token, |
|
|
|
|
|
'remember_device': True, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
request_mock.session = AssignableDict(login=self.test_user.login) |
|
|
|
|
|
request_mock.session.authenticate = MagicMock(return_value=True) |
|
|
|
|
|
request_mock.params = {'remember_device': True} |
|
|
val_mock.return_value = True |
|
|
val_mock.return_value = True |
|
|
resp_mock().__class__ = Response |
|
|
resp_mock().__class__ = Response |
|
|
json_mock.reset_mock() |
|
|
json_mock.reset_mock() |
|
|
self.test_controller.mfa_login_post() |
|
|
self.test_controller.mfa_login_post() |
|
|
|
|
|
|
|
|
test_device = self.test_user.trusted_device_ids |
|
|
|
|
|
config_model = self.env['ir.config_parameter'] |
|
|
|
|
|
test_secret = config_model.get_param('database.secret') |
|
|
|
|
|
|
|
|
test_secret = self.test_user.trusted_device_cookie_key |
|
|
json_mock.assert_called_once_with( |
|
|
json_mock.assert_called_once_with( |
|
|
{'device_id': test_device.id}, |
|
|
|
|
|
|
|
|
{'user_id': self.test_user.id}, |
|
|
test_secret, |
|
|
test_secret, |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
@mock.patch(DATETIME_PATH) |
|
|
|
|
|
@mock.patch(RESPONSE_PATH) |
|
|
|
|
|
@mock.patch(JSON_PATH) |
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
@mock.patch(VALIDATE_PATH) |
|
|
|
|
|
|
|
|
@patch(DATETIME_PATH) |
|
|
|
|
|
@patch(RESPONSE_PATH) |
|
|
|
|
|
@patch(JSON_PATH) |
|
|
|
|
|
@patch(VALIDATE_PATH) |
|
|
def test_mfa_login_post_cookie_werkzeug_cookie_exp( |
|
|
def test_mfa_login_post_cookie_werkzeug_cookie_exp( |
|
|
self, val_mock, gen_mock, json_mock, resp_mock, dt_mock, request_mock |
|
|
|
|
|
|
|
|
self, val_mock, json_mock, resp_mock, dt_mock, request_mock |
|
|
): |
|
|
): |
|
|
'''Should serialize Werkzeug cookie w/right exp if remember flag set''' |
|
|
'''Should serialize Werkzeug cookie w/right exp if remember flag set''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
request_mock.db = self.registry.db_name |
|
|
|
|
|
test_token = self.test_user.mfa_login_token |
|
|
|
|
|
request_mock.params = { |
|
|
|
|
|
'mfa_login_token': test_token, |
|
|
|
|
|
'remember_device': True, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
request_mock.session = AssignableDict(login=self.test_user.login) |
|
|
|
|
|
request_mock.session.authenticate = MagicMock(return_value=True) |
|
|
|
|
|
request_mock.params = {'remember_device': True} |
|
|
val_mock.return_value = True |
|
|
val_mock.return_value = True |
|
|
dt_mock.utcnow.return_value = datetime(2016, 12, 1) |
|
|
dt_mock.utcnow.return_value = datetime(2016, 12, 1) |
|
|
resp_mock().__class__ = Response |
|
|
resp_mock().__class__ = Response |
|
@ -336,22 +246,18 @@ class TestAuthTotp(TransactionCase): |
|
|
|
|
|
|
|
|
json_mock().serialize.assert_called_once_with(datetime(2016, 12, 31)) |
|
|
json_mock().serialize.assert_called_once_with(datetime(2016, 12, 31)) |
|
|
|
|
|
|
|
|
@mock.patch(DATETIME_PATH) |
|
|
|
|
|
@mock.patch(RESPONSE_PATH) |
|
|
|
|
|
@mock.patch(JSON_PATH) |
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
@mock.patch(VALIDATE_PATH) |
|
|
|
|
|
|
|
|
@patch(DATETIME_PATH) |
|
|
|
|
|
@patch(RESPONSE_PATH) |
|
|
|
|
|
@patch(JSON_PATH) |
|
|
|
|
|
@patch(VALIDATE_PATH) |
|
|
def test_mfa_login_post_cookie_final_cookie( |
|
|
def test_mfa_login_post_cookie_final_cookie( |
|
|
self, val_mock, gen_mock, json_mock, resp_mock, dt_mock, request_mock |
|
|
|
|
|
|
|
|
self, val_mock, json_mock, resp_mock, dt_mock, request_mock |
|
|
): |
|
|
): |
|
|
'''Should add correct cookie to response if remember flag set''' |
|
|
'''Should add correct cookie to response if remember flag set''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
request_mock.db = self.registry.db_name |
|
|
|
|
|
test_token = self.test_user.mfa_login_token |
|
|
|
|
|
request_mock.params = { |
|
|
|
|
|
'mfa_login_token': test_token, |
|
|
|
|
|
'remember_device': True, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
request_mock.session = AssignableDict(login=self.test_user.login) |
|
|
|
|
|
request_mock.session.authenticate = MagicMock(return_value=True) |
|
|
|
|
|
request_mock.params = {'remember_device': True} |
|
|
val_mock.return_value = True |
|
|
val_mock.return_value = True |
|
|
dt_mock.utcnow.return_value = datetime(2016, 12, 1) |
|
|
dt_mock.utcnow.return_value = datetime(2016, 12, 1) |
|
|
config_model = self.env['ir.config_parameter'] |
|
|
config_model = self.env['ir.config_parameter'] |
|
@ -369,20 +275,16 @@ class TestAuthTotp(TransactionCase): |
|
|
secure=False, |
|
|
secure=False, |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
@mock.patch(RESPONSE_PATH) |
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
@mock.patch(VALIDATE_PATH) |
|
|
|
|
|
|
|
|
@patch(RESPONSE_PATH) |
|
|
|
|
|
@patch(VALIDATE_PATH) |
|
|
def test_mfa_login_post_cookie_final_cookie_secure( |
|
|
def test_mfa_login_post_cookie_final_cookie_secure( |
|
|
self, val_mock, gen_mock, resp_mock, request_mock |
|
|
|
|
|
|
|
|
self, val_mock, resp_mock, request_mock |
|
|
): |
|
|
): |
|
|
'''Should set secure cookie if config parameter set accordingly''' |
|
|
'''Should set secure cookie if config parameter set accordingly''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
request_mock.db = self.registry.db_name |
|
|
|
|
|
test_token = self.test_user.mfa_login_token |
|
|
|
|
|
request_mock.params = { |
|
|
|
|
|
'mfa_login_token': test_token, |
|
|
|
|
|
'remember_device': True, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
request_mock.session = AssignableDict(login=self.test_user.login) |
|
|
|
|
|
request_mock.session.authenticate = MagicMock(return_value=True) |
|
|
|
|
|
request_mock.params = {'remember_device': True} |
|
|
val_mock.return_value = True |
|
|
val_mock.return_value = True |
|
|
config_model = self.env['ir.config_parameter'] |
|
|
config_model = self.env['ir.config_parameter'] |
|
|
config_model.set_param('auth_totp.secure_cookie', '1') |
|
|
config_model.set_param('auth_totp.secure_cookie', '1') |
|
@ -393,18 +295,16 @@ class TestAuthTotp(TransactionCase): |
|
|
new_test_security = resp_mock().set_cookie.mock_calls[0][2]['secure'] |
|
|
new_test_security = resp_mock().set_cookie.mock_calls[0][2]['secure'] |
|
|
self.assertIs(new_test_security, True) |
|
|
self.assertIs(new_test_security, True) |
|
|
|
|
|
|
|
|
@mock.patch(REDIRECT_PATH) |
|
|
|
|
|
@mock.patch(GENERATE_PATH) |
|
|
|
|
|
@mock.patch(VALIDATE_PATH) |
|
|
|
|
|
|
|
|
@patch(REDIRECT_PATH) |
|
|
|
|
|
@patch(VALIDATE_PATH) |
|
|
def test_mfa_login_post_firefox_response_returned( |
|
|
def test_mfa_login_post_firefox_response_returned( |
|
|
self, val_mock, gen_mock, redirect_mock, request_mock |
|
|
|
|
|
|
|
|
self, val_mock, redirect_mock, request_mock |
|
|
): |
|
|
): |
|
|
'''Should behave well if redirect returns Response (Firefox case)''' |
|
|
'''Should behave well if redirect returns Response (Firefox case)''' |
|
|
request_mock.env = self.env |
|
|
request_mock.env = self.env |
|
|
request_mock.db = self.registry.db_name |
|
|
|
|
|
|
|
|
request_mock.session = AssignableDict(login=self.test_user.login) |
|
|
|
|
|
request_mock.session.authenticate = MagicMock(return_value=True) |
|
|
redirect_mock.return_value = Response('Test Response') |
|
|
redirect_mock.return_value = Response('Test Response') |
|
|
test_token = self.test_user.mfa_login_token |
|
|
|
|
|
request_mock.params = {'mfa_login_token': test_token} |
|
|
|
|
|
val_mock.return_value = True |
|
|
val_mock.return_value = True |
|
|
|
|
|
|
|
|
test_result = self.test_controller.mfa_login_post() |
|
|
test_result = self.test_controller.mfa_login_post() |
|
|