You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
11 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 LasLabs Inc.
  3. # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
  4. import mock
  5. from contextlib import contextmanager
  6. from odoo.tests.common import TransactionCase
  7. from odoo.http import Response
  8. from ..controllers import main
  9. IMPORT = 'odoo.addons.password_security.controllers.main'
  10. class EndTestException(Exception):
  11. """ It allows for isolation of resources by raise """
  12. class MockResponse(object):
  13. def __new__(cls):
  14. return mock.Mock(spec=Response)
  15. class MockPassError(main.PassError):
  16. def __init__(self):
  17. super(MockPassError, self).__init__('Message')
  18. class TestPasswordSecurityHome(TransactionCase):
  19. def setUp(self):
  20. super(TestPasswordSecurityHome, self).setUp()
  21. self.PasswordSecurityHome = main.PasswordSecurityHome
  22. self.password_security_home = self.PasswordSecurityHome()
  23. self.passwd = 'I am a password!'
  24. self.qcontext = {
  25. 'password': self.passwd,
  26. }
  27. @contextmanager
  28. def mock_assets(self):
  29. """ It mocks and returns assets used by this controller """
  30. methods = ['do_signup', 'web_login', 'web_auth_signup',
  31. 'web_auth_reset_password',
  32. ]
  33. with mock.patch.multiple(
  34. main.AuthSignupHome, **{m: mock.DEFAULT for m in methods}
  35. ) as _super:
  36. mocks = {}
  37. for method in methods:
  38. mocks[method] = _super[method]
  39. mocks[method].return_value = MockResponse()
  40. with mock.patch('%s.request' % IMPORT) as request:
  41. with mock.patch('%s.ensure_db' % IMPORT) as ensure:
  42. with mock.patch('%s.http' % IMPORT) as http:
  43. http.redirect_with_hash.return_value = \
  44. MockResponse()
  45. mocks.update({
  46. 'request': request,
  47. 'ensure_db': ensure,
  48. 'http': http,
  49. })
  50. yield mocks
  51. def test_do_signup_check(self):
  52. """ It should check password on user """
  53. with self.mock_assets() as assets:
  54. check_password = assets['request'].env.user.check_password
  55. check_password.side_effect = EndTestException
  56. with self.assertRaises(EndTestException):
  57. self.password_security_home.do_signup(self.qcontext)
  58. check_password.assert_called_once_with(
  59. self.passwd,
  60. )
  61. def test_do_signup_return(self):
  62. """ It should return result of super """
  63. with self.mock_assets() as assets:
  64. res = self.password_security_home.do_signup(self.qcontext)
  65. self.assertEqual(assets['do_signup'](), res)
  66. def test_web_login_ensure_db(self):
  67. """ It should verify available db """
  68. with self.mock_assets() as assets:
  69. assets['ensure_db'].side_effect = EndTestException
  70. with self.assertRaises(EndTestException):
  71. self.password_security_home.web_login()
  72. def test_web_login_super(self):
  73. """ It should call superclass w/ proper args """
  74. expect_list = [1, 2, 3]
  75. expect_dict = {'test1': 'good1', 'test2': 'good2'}
  76. with self.mock_assets() as assets:
  77. assets['web_login'].side_effect = EndTestException
  78. with self.assertRaises(EndTestException):
  79. self.password_security_home.web_login(
  80. *expect_list, **expect_dict
  81. )
  82. assets['web_login'].assert_called_once_with(
  83. *expect_list, **expect_dict
  84. )
  85. def test_web_login_no_post(self):
  86. """ It should return immediate result of super when not POST """
  87. with self.mock_assets() as assets:
  88. assets['request'].httprequest.method = 'GET'
  89. assets['request'].session.authenticate.side_effect = \
  90. EndTestException
  91. res = self.password_security_home.web_login()
  92. self.assertEqual(
  93. assets['web_login'](), res,
  94. )
  95. def test_web_login_authenticate(self):
  96. """ It should attempt authentication to obtain uid """
  97. with self.mock_assets() as assets:
  98. assets['request'].httprequest.method = 'POST'
  99. authenticate = assets['request'].session.authenticate
  100. request = assets['request']
  101. authenticate.side_effect = EndTestException
  102. with self.assertRaises(EndTestException):
  103. self.password_security_home.web_login()
  104. authenticate.assert_called_once_with(
  105. request.session.db,
  106. request.params['login'],
  107. request.params['password'],
  108. )
  109. def test_web_login_authenticate_fail(self):
  110. """ It should return super result if failed auth """
  111. with self.mock_assets() as assets:
  112. authenticate = assets['request'].session.authenticate
  113. request = assets['request']
  114. request.httprequest.method = 'POST'
  115. request.env['res.users'].sudo.side_effect = EndTestException
  116. authenticate.return_value = False
  117. res = self.password_security_home.web_login()
  118. self.assertEqual(
  119. assets['web_login'](), res,
  120. )
  121. def test_web_login_get_user(self):
  122. """ It should get the proper user as sudo """
  123. with self.mock_assets() as assets:
  124. request = assets['request']
  125. request.httprequest.method = 'POST'
  126. sudo = request.env['res.users'].sudo()
  127. sudo.browse.side_effect = EndTestException
  128. with self.assertRaises(EndTestException):
  129. self.password_security_home.web_login()
  130. sudo.browse.assert_called_once_with(
  131. request.uid
  132. )
  133. def test_web_login_valid_pass(self):
  134. """ It should return parent result if pass isn't expired """
  135. with self.mock_assets() as assets:
  136. request = assets['request']
  137. request.httprequest.method = 'POST'
  138. user = request.env['res.users'].sudo().browse()
  139. user.action_expire_password.side_effect = EndTestException
  140. user._password_has_expired.return_value = False
  141. res = self.password_security_home.web_login()
  142. self.assertEqual(
  143. assets['web_login'](), res,
  144. )
  145. def test_web_login_expire_pass(self):
  146. """ It should expire password if necessary """
  147. with self.mock_assets() as assets:
  148. request = assets['request']
  149. request.httprequest.method = 'POST'
  150. user = request.env['res.users'].sudo().browse()
  151. user.action_expire_password.side_effect = EndTestException
  152. user._password_has_expired.return_value = True
  153. with self.assertRaises(EndTestException):
  154. self.password_security_home.web_login()
  155. def test_web_login_redirect(self):
  156. """ It should redirect w/ hash to reset after expiration """
  157. with self.mock_assets() as assets:
  158. request = assets['request']
  159. request.httprequest.method = 'POST'
  160. user = request.env['res.users'].sudo().browse()
  161. user._password_has_expired.return_value = True
  162. res = self.password_security_home.web_login()
  163. self.assertEqual(
  164. assets['http'].redirect_with_hash(), res,
  165. )
  166. def test_web_auth_signup_valid(self):
  167. """ It should return super if no errors """
  168. with self.mock_assets() as assets:
  169. res = self.password_security_home.web_auth_signup()
  170. self.assertEqual(
  171. assets['web_auth_signup'](), res,
  172. )
  173. def test_web_auth_signup_invalid_qcontext(self):
  174. """ It should catch PassError and get signup qcontext """
  175. with self.mock_assets() as assets:
  176. with mock.patch.object(
  177. main.AuthSignupHome, 'get_auth_signup_qcontext',
  178. ) as qcontext:
  179. assets['web_auth_signup'].side_effect = MockPassError
  180. qcontext.side_effect = EndTestException
  181. with self.assertRaises(EndTestException):
  182. self.password_security_home.web_auth_signup()
  183. def test_web_auth_signup_invalid_render(self):
  184. """ It should render & return signup form on invalid """
  185. with self.mock_assets() as assets:
  186. with mock.patch.object(
  187. main.AuthSignupHome, 'get_auth_signup_qcontext', spec=dict
  188. ) as qcontext:
  189. assets['web_auth_signup'].side_effect = MockPassError
  190. res = self.password_security_home.web_auth_signup()
  191. assets['request'].render.assert_called_once_with(
  192. 'auth_signup.signup', qcontext(),
  193. )
  194. self.assertEqual(
  195. assets['request'].render(), res,
  196. )
  197. def test_web_auth_reset_password_fail_login(self):
  198. """ It should raise from failed _validate_pass_reset by login """
  199. with self.mock_assets() as assets:
  200. with mock.patch.object(
  201. main.AuthSignupHome, 'get_auth_signup_qcontext', spec=dict
  202. ) as qcontext:
  203. qcontext['login'] = 'login'
  204. search = assets['request'].env.sudo().search
  205. assets['request'].httprequest.method = 'POST'
  206. user = mock.MagicMock()
  207. user._validate_pass_reset.side_effect = MockPassError
  208. search.return_value = user
  209. with self.assertRaises(MockPassError):
  210. self.password_security_home.web_auth_reset_password()
  211. def test_web_auth_reset_password_fail_email(self):
  212. """ It should raise from failed _validate_pass_reset by email """
  213. with self.mock_assets() as assets:
  214. with mock.patch.object(
  215. main.AuthSignupHome, 'get_auth_signup_qcontext', spec=dict
  216. ) as qcontext:
  217. qcontext['login'] = 'login'
  218. search = assets['request'].env.sudo().search
  219. assets['request'].httprequest.method = 'POST'
  220. user = mock.MagicMock()
  221. user._validate_pass_reset.side_effect = MockPassError
  222. search.side_effect = [[], user]
  223. with self.assertRaises(MockPassError):
  224. self.password_security_home.web_auth_reset_password()
  225. def test_web_auth_reset_password_success(self):
  226. """ It should return parent response on no validate errors """
  227. with self.mock_assets() as assets:
  228. with mock.patch.object(
  229. main.AuthSignupHome, 'get_auth_signup_qcontext', spec=dict
  230. ) as qcontext:
  231. qcontext['login'] = 'login'
  232. assets['request'].httprequest.method = 'POST'
  233. res = self.password_security_home.web_auth_reset_password()
  234. self.assertEqual(
  235. assets['web_auth_reset_password'](), res,
  236. )