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.

141 lines
4.4 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 the authenticator app instead?")
  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 the authenticator app instead?")
  26. def test_it_requires_unauthenticated_user(self):
  27. self.client.login(username="[email protected]", password="password")
  28. r = self.client.get(self.url)
  29. self.assertEqual(r.status_code, 400)
  30. def test_it_rejects_changed_email(self):
  31. session = self.client.session
  32. session["2fa_user"] = [self.alice.id, "[email protected]", int(time.time())]
  33. session.save()
  34. r = self.client.get(self.url)
  35. self.assertEqual(r.status_code, 400)
  36. def test_it_rejects_old_timestamp(self):
  37. session = self.client.session
  38. session["2fa_user"] = [self.alice.id, self.alice.email, int(time.time()) - 310]
  39. session.save()
  40. r = self.client.get(self.url)
  41. self.assertRedirects(r, "/accounts/login/")
  42. @override_settings(RP_ID=None)
  43. def test_it_requires_rp_id(self):
  44. r = self.client.get(self.url)
  45. self.assertEqual(r.status_code, 500)
  46. @patch("hc.accounts.views._check_credential")
  47. def test_it_logs_in(self, mock_check_credential):
  48. mock_check_credential.return_value = True
  49. session = self.client.session
  50. session["state"] = "dummy-state"
  51. session.save()
  52. payload = {
  53. "name": "My New Key",
  54. "credential_id": "e30=",
  55. "client_data_json": "e30=",
  56. "authenticator_data": "e30=",
  57. "signature": "e30=",
  58. }
  59. r = self.client.post(self.url, payload)
  60. self.assertRedirects(r, self.checks_url)
  61. self.assertNotIn("state", self.client.session)
  62. self.assertNotIn("2fa_user_id", self.client.session)
  63. @patch("hc.accounts.views._check_credential")
  64. def test_it_redirects_after_login(self, mock_check_credential):
  65. mock_check_credential.return_value = True
  66. session = self.client.session
  67. session["state"] = "dummy-state"
  68. session.save()
  69. payload = {
  70. "name": "My New Key",
  71. "credential_id": "e30=",
  72. "client_data_json": "e30=",
  73. "authenticator_data": "e30=",
  74. "signature": "e30=",
  75. }
  76. url = self.url + "?next=" + self.channels_url
  77. r = self.client.post(url, payload)
  78. self.assertRedirects(r, self.channels_url)
  79. @patch("hc.accounts.views._check_credential")
  80. def test_it_handles_bad_base64(self, mock_check_credential):
  81. mock_check_credential.return_value = None
  82. session = self.client.session
  83. session["state"] = "dummy-state"
  84. session.save()
  85. payload = {
  86. "name": "My New Key",
  87. "credential_id": "this is not base64 data",
  88. "client_data_json": "e30=",
  89. "authenticator_data": "e30=",
  90. "signature": "e30=",
  91. }
  92. r = self.client.post(self.url, payload)
  93. self.assertEqual(r.status_code, 400)
  94. @patch("hc.accounts.views._check_credential")
  95. def test_it_handles_authentication_failure(self, mock_check_credential):
  96. mock_check_credential.return_value = None
  97. session = self.client.session
  98. session["state"] = "dummy-state"
  99. session.save()
  100. payload = {
  101. "name": "My New Key",
  102. "credential_id": "e30=",
  103. "client_data_json": "e30=",
  104. "authenticator_data": "e30=",
  105. "signature": "e30=",
  106. }
  107. r = self.client.post(self.url, payload)
  108. self.assertEqual(r.status_code, 400)