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.

149 lines
4.7 KiB

  1. import time
  2. from unittest.mock import patch
  3. from django.test.utils import override_settings
  4. from hc.test import BaseTestCase
  5. @override_settings(RP_ID="testserver")
  6. class LoginWebAuthnTestCase(BaseTestCase):
  7. def setUp(self):
  8. super().setUp()
  9. # This is the user we're trying to authenticate
  10. session = self.client.session
  11. session["2fa_user"] = [self.alice.id, self.alice.email, (time.time()) + 300]
  12. session.save()
  13. self.url = "/accounts/login/two_factor/"
  14. self.checks_url = f"/projects/{self.project.code}/checks/"
  15. def test_it_shows_form(self):
  16. r = self.client.get(self.url)
  17. self.assertContains(r, "Waiting for security key")
  18. self.assertNotContains(r, "Use authenticator app")
  19. # It should put a "state" key in the session:
  20. self.assertIn("state", self.client.session)
  21. def test_it_shows_totp_option(self):
  22. self.profile.totp = "0" * 32
  23. self.profile.save()
  24. r = self.client.get(self.url)
  25. self.assertContains(r, "Use authenticator app")
  26. def test_it_preserves_next_parameter_in_totp_url(self):
  27. self.profile.totp = "0" * 32
  28. self.profile.save()
  29. url = self.url + "?next=" + self.channels_url
  30. r = self.client.get(url)
  31. self.assertContains(r, "/login/two_factor/totp/?next=" + self.channels_url)
  32. def test_it_requires_unauthenticated_user(self):
  33. self.client.login(username="[email protected]", password="password")
  34. r = self.client.get(self.url)
  35. self.assertEqual(r.status_code, 400)
  36. def test_it_rejects_changed_email(self):
  37. session = self.client.session
  38. session["2fa_user"] = [self.alice.id, "[email protected]", int(time.time())]
  39. session.save()
  40. r = self.client.get(self.url)
  41. self.assertEqual(r.status_code, 400)
  42. def test_it_rejects_old_timestamp(self):
  43. session = self.client.session
  44. session["2fa_user"] = [self.alice.id, self.alice.email, int(time.time()) - 310]
  45. session.save()
  46. r = self.client.get(self.url)
  47. self.assertRedirects(r, "/accounts/login/")
  48. @override_settings(RP_ID=None)
  49. def test_it_requires_rp_id(self):
  50. r = self.client.get(self.url)
  51. self.assertEqual(r.status_code, 500)
  52. @patch("hc.accounts.views._check_credential")
  53. def test_it_logs_in(self, mock_check_credential):
  54. mock_check_credential.return_value = True
  55. session = self.client.session
  56. session["state"] = "dummy-state"
  57. session.save()
  58. payload = {
  59. "name": "My New Key",
  60. "credential_id": "e30=",
  61. "client_data_json": "e30=",
  62. "authenticator_data": "e30=",
  63. "signature": "e30=",
  64. }
  65. r = self.client.post(self.url, payload)
  66. self.assertRedirects(r, self.checks_url)
  67. self.assertNotIn("state", self.client.session)
  68. self.assertNotIn("2fa_user_id", self.client.session)
  69. @patch("hc.accounts.views._check_credential")
  70. def test_it_redirects_after_login(self, mock_check_credential):
  71. mock_check_credential.return_value = True
  72. session = self.client.session
  73. session["state"] = "dummy-state"
  74. session.save()
  75. payload = {
  76. "name": "My New Key",
  77. "credential_id": "e30=",
  78. "client_data_json": "e30=",
  79. "authenticator_data": "e30=",
  80. "signature": "e30=",
  81. }
  82. url = self.url + "?next=" + self.channels_url
  83. r = self.client.post(url, payload)
  84. self.assertRedirects(r, self.channels_url)
  85. @patch("hc.accounts.views._check_credential")
  86. def test_it_handles_bad_base64(self, mock_check_credential):
  87. mock_check_credential.return_value = None
  88. session = self.client.session
  89. session["state"] = "dummy-state"
  90. session.save()
  91. payload = {
  92. "name": "My New Key",
  93. "credential_id": "this is not base64 data",
  94. "client_data_json": "e30=",
  95. "authenticator_data": "e30=",
  96. "signature": "e30=",
  97. }
  98. r = self.client.post(self.url, payload)
  99. self.assertEqual(r.status_code, 400)
  100. @patch("hc.accounts.views._check_credential")
  101. def test_it_handles_authentication_failure(self, mock_check_credential):
  102. mock_check_credential.return_value = None
  103. session = self.client.session
  104. session["state"] = "dummy-state"
  105. session.save()
  106. payload = {
  107. "name": "My New Key",
  108. "credential_id": "e30=",
  109. "client_data_json": "e30=",
  110. "authenticator_data": "e30=",
  111. "signature": "e30=",
  112. }
  113. r = self.client.post(self.url, payload)
  114. self.assertEqual(r.status_code, 400)