authentik.stages.authenticator_totp.tests
Test TOTP API
1"""Test TOTP API""" 2 3from time import time 4from urllib.parse import parse_qs, urlsplit 5 6from django.test.utils import override_settings 7from django.urls import reverse 8from rest_framework.test import APITestCase 9 10from authentik.core.models import User 11from authentik.core.tests.utils import create_test_admin_user 12from authentik.stages.authenticator.tests import TestCase, ThrottlingTestMixin 13from authentik.stages.authenticator_totp.models import TOTPDevice 14 15 16class AuthenticatorTOTPStage(APITestCase): 17 """Test TOTP API""" 18 19 def test_api_delete(self): 20 """Test api delete""" 21 user = User.objects.create(username="foo") 22 self.client.force_login(user) 23 dev = TOTPDevice.objects.create(user=user) 24 response = self.client.delete( 25 reverse("authentik_api:totpdevice-detail", kwargs={"pk": dev.pk}) 26 ) 27 self.assertEqual(response.status_code, 204) 28 29 30class TOTPDeviceMixin: 31 """ 32 A TestCase helper that gives us a TOTPDevice to work with. 33 """ 34 35 # The next ten tokens 36 tokens = [ 37 179225, 38 656163, 39 839400, 40 154567, 41 346912, 42 471576, 43 45675, 44 101397, 45 491039, 46 784503, 47 ] 48 49 def setUp(self): 50 """ 51 Create a device at the fourth time step. The current token is 154567. 52 """ 53 self.alice = create_test_admin_user("alice", email="alice@example.com") 54 self.device = self.alice.totpdevice_set.create( 55 key="2a2bbba1092ffdd25a328ad1a0a5f5d61d7aacc4", 56 step=30, 57 t0=int(time() - (30 * 3)), 58 digits=6, 59 tolerance=0, 60 drift=0, 61 ) 62 63 64@override_settings( 65 OTP_TOTP_SYNC=False, 66) 67class TOTPTest(TOTPDeviceMixin, TestCase): 68 """TOTP tests""" 69 70 def setUp(self): 71 super().setUp() 72 self.device.set_throttle_factor(0) 73 74 def test_default_key(self): 75 """Ensure default_key is valid""" 76 device = self.alice.totpdevice_set.create() 77 78 # Make sure we can decode the key. 79 _ = device.bin_key 80 81 def test_single(self): 82 """Test single token""" 83 results = [self.device.verify_token(token) for token in self.tokens] 84 85 self.assertEqual(results, [False] * 3 + [True] + [False] * 6) 86 87 def test_tolerance(self): 88 """Test tolerance""" 89 self.device.tolerance = 1 90 results = [self.device.verify_token(token) for token in self.tokens] 91 92 self.assertEqual(results, [False] * 2 + [True] * 3 + [False] * 5) 93 94 def test_drift(self): 95 """Test drift""" 96 self.device.tolerance = 1 97 self.device.drift = -1 98 results = [self.device.verify_token(token) for token in self.tokens] 99 100 self.assertEqual(results, [False] * 1 + [True] * 3 + [False] * 6) 101 102 def test_sync_drift(self): 103 """Test sync drift""" 104 self.device.tolerance = 2 105 with self.settings(OTP_TOTP_SYNC=True): 106 valid = self.device.verify_token(self.tokens[5]) 107 108 self.assertTrue(valid) 109 self.assertEqual(self.device.drift, 2) 110 111 def test_no_reuse(self): 112 """Test reuse""" 113 verified1 = self.device.verify_token(self.tokens[3]) 114 verified2 = self.device.verify_token(self.tokens[3]) 115 116 self.assertEqual(self.device.last_t, 3) 117 self.assertTrue(verified1) 118 self.assertFalse(verified2) 119 120 def test_config_url(self): 121 """Test config_url""" 122 with override_settings(OTP_TOTP_ISSUER=None): 123 url = self.device.config_url 124 125 parsed = urlsplit(url) 126 params = parse_qs(parsed.query) 127 128 self.assertEqual(parsed.scheme, "otpauth") 129 self.assertEqual(parsed.netloc, "totp") 130 self.assertEqual(parsed.path, "/alice") 131 self.assertIn("secret", params) 132 self.assertNotIn("issuer", params) 133 134 def test_config_url_issuer(self): 135 """Test config_url issuer""" 136 with override_settings(OTP_TOTP_ISSUER="example.com"): 137 url = self.device.config_url 138 139 parsed = urlsplit(url) 140 params = parse_qs(parsed.query) 141 142 self.assertEqual(parsed.scheme, "otpauth") 143 self.assertEqual(parsed.netloc, "totp") 144 self.assertEqual(parsed.path, "/example.com%3Aalice") 145 self.assertIn("secret", params) 146 self.assertIn("issuer", params) 147 self.assertEqual(params["issuer"][0], "example.com") 148 149 def test_config_url_issuer_spaces(self): 150 """Test config_url issuer with spaces""" 151 with override_settings(OTP_TOTP_ISSUER="Very Trustworthy Source"): 152 url = self.device.config_url 153 154 parsed = urlsplit(url) 155 params = parse_qs(parsed.query) 156 157 self.assertEqual(parsed.scheme, "otpauth") 158 self.assertEqual(parsed.netloc, "totp") 159 self.assertEqual(parsed.path, "/Very%20Trustworthy%20Source%3Aalice") 160 self.assertIn("secret", params) 161 self.assertIn("issuer", params) 162 self.assertEqual(params["issuer"][0], "Very Trustworthy Source") 163 164 def test_config_url_issuer_method(self): 165 """Test config_url issuer method""" 166 with override_settings(OTP_TOTP_ISSUER=lambda d: d.user.email): 167 url = self.device.config_url 168 169 parsed = urlsplit(url) 170 params = parse_qs(parsed.query) 171 172 self.assertEqual(parsed.scheme, "otpauth") 173 self.assertEqual(parsed.netloc, "totp") 174 self.assertEqual(parsed.path, "/alice%40example.com%3Aalice") 175 self.assertIn("secret", params) 176 self.assertIn("issuer", params) 177 self.assertEqual(params["issuer"][0], "alice@example.com") 178 179 def test_config_url_image(self): 180 """Test config_url with image""" 181 image_url = "https://test.invalid/square.png" 182 183 with override_settings(OTP_TOTP_ISSUER=None, OTP_TOTP_IMAGE=image_url): 184 url = self.device.config_url 185 186 parsed = urlsplit(url) 187 params = parse_qs(parsed.query) 188 189 self.assertEqual(parsed.scheme, "otpauth") 190 self.assertEqual(parsed.netloc, "totp") 191 self.assertEqual(parsed.path, "/alice") 192 self.assertIn("secret", params) 193 self.assertEqual(params["image"][0], image_url) 194 195 196class ThrottlingTestCase(TOTPDeviceMixin, ThrottlingTestMixin, TestCase): 197 """Test TOTP Throttling""" 198 199 def valid_token(self): 200 return self.tokens[3] 201 202 def invalid_token(self): 203 return -1
class
AuthenticatorTOTPStage(rest_framework.test.APITestCase):
17class AuthenticatorTOTPStage(APITestCase): 18 """Test TOTP API""" 19 20 def test_api_delete(self): 21 """Test api delete""" 22 user = User.objects.create(username="foo") 23 self.client.force_login(user) 24 dev = TOTPDevice.objects.create(user=user) 25 response = self.client.delete( 26 reverse("authentik_api:totpdevice-detail", kwargs={"pk": dev.pk}) 27 ) 28 self.assertEqual(response.status_code, 204)
Test TOTP API
def
test_api_delete(self):
20 def test_api_delete(self): 21 """Test api delete""" 22 user = User.objects.create(username="foo") 23 self.client.force_login(user) 24 dev = TOTPDevice.objects.create(user=user) 25 response = self.client.delete( 26 reverse("authentik_api:totpdevice-detail", kwargs={"pk": dev.pk}) 27 ) 28 self.assertEqual(response.status_code, 204)
Test api delete
class
TOTPDeviceMixin:
31class TOTPDeviceMixin: 32 """ 33 A TestCase helper that gives us a TOTPDevice to work with. 34 """ 35 36 # The next ten tokens 37 tokens = [ 38 179225, 39 656163, 40 839400, 41 154567, 42 346912, 43 471576, 44 45675, 45 101397, 46 491039, 47 784503, 48 ] 49 50 def setUp(self): 51 """ 52 Create a device at the fourth time step. The current token is 154567. 53 """ 54 self.alice = create_test_admin_user("alice", email="alice@example.com") 55 self.device = self.alice.totpdevice_set.create( 56 key="2a2bbba1092ffdd25a328ad1a0a5f5d61d7aacc4", 57 step=30, 58 t0=int(time() - (30 * 3)), 59 digits=6, 60 tolerance=0, 61 drift=0, 62 )
A TestCase helper that gives us a TOTPDevice to work with.
def
setUp(self):
50 def setUp(self): 51 """ 52 Create a device at the fourth time step. The current token is 154567. 53 """ 54 self.alice = create_test_admin_user("alice", email="alice@example.com") 55 self.device = self.alice.totpdevice_set.create( 56 key="2a2bbba1092ffdd25a328ad1a0a5f5d61d7aacc4", 57 step=30, 58 t0=int(time() - (30 * 3)), 59 digits=6, 60 tolerance=0, 61 drift=0, 62 )
Create a device at the fourth time step. The current token is 154567.
@override_settings(OTP_TOTP_SYNC=False)
class
TOTPTest65@override_settings( 66 OTP_TOTP_SYNC=False, 67) 68class TOTPTest(TOTPDeviceMixin, TestCase): 69 """TOTP tests""" 70 71 def setUp(self): 72 super().setUp() 73 self.device.set_throttle_factor(0) 74 75 def test_default_key(self): 76 """Ensure default_key is valid""" 77 device = self.alice.totpdevice_set.create() 78 79 # Make sure we can decode the key. 80 _ = device.bin_key 81 82 def test_single(self): 83 """Test single token""" 84 results = [self.device.verify_token(token) for token in self.tokens] 85 86 self.assertEqual(results, [False] * 3 + [True] + [False] * 6) 87 88 def test_tolerance(self): 89 """Test tolerance""" 90 self.device.tolerance = 1 91 results = [self.device.verify_token(token) for token in self.tokens] 92 93 self.assertEqual(results, [False] * 2 + [True] * 3 + [False] * 5) 94 95 def test_drift(self): 96 """Test drift""" 97 self.device.tolerance = 1 98 self.device.drift = -1 99 results = [self.device.verify_token(token) for token in self.tokens] 100 101 self.assertEqual(results, [False] * 1 + [True] * 3 + [False] * 6) 102 103 def test_sync_drift(self): 104 """Test sync drift""" 105 self.device.tolerance = 2 106 with self.settings(OTP_TOTP_SYNC=True): 107 valid = self.device.verify_token(self.tokens[5]) 108 109 self.assertTrue(valid) 110 self.assertEqual(self.device.drift, 2) 111 112 def test_no_reuse(self): 113 """Test reuse""" 114 verified1 = self.device.verify_token(self.tokens[3]) 115 verified2 = self.device.verify_token(self.tokens[3]) 116 117 self.assertEqual(self.device.last_t, 3) 118 self.assertTrue(verified1) 119 self.assertFalse(verified2) 120 121 def test_config_url(self): 122 """Test config_url""" 123 with override_settings(OTP_TOTP_ISSUER=None): 124 url = self.device.config_url 125 126 parsed = urlsplit(url) 127 params = parse_qs(parsed.query) 128 129 self.assertEqual(parsed.scheme, "otpauth") 130 self.assertEqual(parsed.netloc, "totp") 131 self.assertEqual(parsed.path, "/alice") 132 self.assertIn("secret", params) 133 self.assertNotIn("issuer", params) 134 135 def test_config_url_issuer(self): 136 """Test config_url issuer""" 137 with override_settings(OTP_TOTP_ISSUER="example.com"): 138 url = self.device.config_url 139 140 parsed = urlsplit(url) 141 params = parse_qs(parsed.query) 142 143 self.assertEqual(parsed.scheme, "otpauth") 144 self.assertEqual(parsed.netloc, "totp") 145 self.assertEqual(parsed.path, "/example.com%3Aalice") 146 self.assertIn("secret", params) 147 self.assertIn("issuer", params) 148 self.assertEqual(params["issuer"][0], "example.com") 149 150 def test_config_url_issuer_spaces(self): 151 """Test config_url issuer with spaces""" 152 with override_settings(OTP_TOTP_ISSUER="Very Trustworthy Source"): 153 url = self.device.config_url 154 155 parsed = urlsplit(url) 156 params = parse_qs(parsed.query) 157 158 self.assertEqual(parsed.scheme, "otpauth") 159 self.assertEqual(parsed.netloc, "totp") 160 self.assertEqual(parsed.path, "/Very%20Trustworthy%20Source%3Aalice") 161 self.assertIn("secret", params) 162 self.assertIn("issuer", params) 163 self.assertEqual(params["issuer"][0], "Very Trustworthy Source") 164 165 def test_config_url_issuer_method(self): 166 """Test config_url issuer method""" 167 with override_settings(OTP_TOTP_ISSUER=lambda d: d.user.email): 168 url = self.device.config_url 169 170 parsed = urlsplit(url) 171 params = parse_qs(parsed.query) 172 173 self.assertEqual(parsed.scheme, "otpauth") 174 self.assertEqual(parsed.netloc, "totp") 175 self.assertEqual(parsed.path, "/alice%40example.com%3Aalice") 176 self.assertIn("secret", params) 177 self.assertIn("issuer", params) 178 self.assertEqual(params["issuer"][0], "alice@example.com") 179 180 def test_config_url_image(self): 181 """Test config_url with image""" 182 image_url = "https://test.invalid/square.png" 183 184 with override_settings(OTP_TOTP_ISSUER=None, OTP_TOTP_IMAGE=image_url): 185 url = self.device.config_url 186 187 parsed = urlsplit(url) 188 params = parse_qs(parsed.query) 189 190 self.assertEqual(parsed.scheme, "otpauth") 191 self.assertEqual(parsed.netloc, "totp") 192 self.assertEqual(parsed.path, "/alice") 193 self.assertIn("secret", params) 194 self.assertEqual(params["image"][0], image_url)
TOTP tests
def
test_default_key(self):
75 def test_default_key(self): 76 """Ensure default_key is valid""" 77 device = self.alice.totpdevice_set.create() 78 79 # Make sure we can decode the key. 80 _ = device.bin_key
Ensure default_key is valid
def
test_single(self):
82 def test_single(self): 83 """Test single token""" 84 results = [self.device.verify_token(token) for token in self.tokens] 85 86 self.assertEqual(results, [False] * 3 + [True] + [False] * 6)
Test single token
def
test_tolerance(self):
88 def test_tolerance(self): 89 """Test tolerance""" 90 self.device.tolerance = 1 91 results = [self.device.verify_token(token) for token in self.tokens] 92 93 self.assertEqual(results, [False] * 2 + [True] * 3 + [False] * 5)
Test tolerance
def
test_drift(self):
95 def test_drift(self): 96 """Test drift""" 97 self.device.tolerance = 1 98 self.device.drift = -1 99 results = [self.device.verify_token(token) for token in self.tokens] 100 101 self.assertEqual(results, [False] * 1 + [True] * 3 + [False] * 6)
Test drift
def
test_sync_drift(self):
103 def test_sync_drift(self): 104 """Test sync drift""" 105 self.device.tolerance = 2 106 with self.settings(OTP_TOTP_SYNC=True): 107 valid = self.device.verify_token(self.tokens[5]) 108 109 self.assertTrue(valid) 110 self.assertEqual(self.device.drift, 2)
Test sync drift
def
test_no_reuse(self):
112 def test_no_reuse(self): 113 """Test reuse""" 114 verified1 = self.device.verify_token(self.tokens[3]) 115 verified2 = self.device.verify_token(self.tokens[3]) 116 117 self.assertEqual(self.device.last_t, 3) 118 self.assertTrue(verified1) 119 self.assertFalse(verified2)
Test reuse
def
test_config_url(self):
121 def test_config_url(self): 122 """Test config_url""" 123 with override_settings(OTP_TOTP_ISSUER=None): 124 url = self.device.config_url 125 126 parsed = urlsplit(url) 127 params = parse_qs(parsed.query) 128 129 self.assertEqual(parsed.scheme, "otpauth") 130 self.assertEqual(parsed.netloc, "totp") 131 self.assertEqual(parsed.path, "/alice") 132 self.assertIn("secret", params) 133 self.assertNotIn("issuer", params)
Test config_url
def
test_config_url_issuer(self):
135 def test_config_url_issuer(self): 136 """Test config_url issuer""" 137 with override_settings(OTP_TOTP_ISSUER="example.com"): 138 url = self.device.config_url 139 140 parsed = urlsplit(url) 141 params = parse_qs(parsed.query) 142 143 self.assertEqual(parsed.scheme, "otpauth") 144 self.assertEqual(parsed.netloc, "totp") 145 self.assertEqual(parsed.path, "/example.com%3Aalice") 146 self.assertIn("secret", params) 147 self.assertIn("issuer", params) 148 self.assertEqual(params["issuer"][0], "example.com")
Test config_url issuer
def
test_config_url_issuer_spaces(self):
150 def test_config_url_issuer_spaces(self): 151 """Test config_url issuer with spaces""" 152 with override_settings(OTP_TOTP_ISSUER="Very Trustworthy Source"): 153 url = self.device.config_url 154 155 parsed = urlsplit(url) 156 params = parse_qs(parsed.query) 157 158 self.assertEqual(parsed.scheme, "otpauth") 159 self.assertEqual(parsed.netloc, "totp") 160 self.assertEqual(parsed.path, "/Very%20Trustworthy%20Source%3Aalice") 161 self.assertIn("secret", params) 162 self.assertIn("issuer", params) 163 self.assertEqual(params["issuer"][0], "Very Trustworthy Source")
Test config_url issuer with spaces
def
test_config_url_issuer_method(self):
165 def test_config_url_issuer_method(self): 166 """Test config_url issuer method""" 167 with override_settings(OTP_TOTP_ISSUER=lambda d: d.user.email): 168 url = self.device.config_url 169 170 parsed = urlsplit(url) 171 params = parse_qs(parsed.query) 172 173 self.assertEqual(parsed.scheme, "otpauth") 174 self.assertEqual(parsed.netloc, "totp") 175 self.assertEqual(parsed.path, "/alice%40example.com%3Aalice") 176 self.assertIn("secret", params) 177 self.assertIn("issuer", params) 178 self.assertEqual(params["issuer"][0], "alice@example.com")
Test config_url issuer method
def
test_config_url_image(self):
180 def test_config_url_image(self): 181 """Test config_url with image""" 182 image_url = "https://test.invalid/square.png" 183 184 with override_settings(OTP_TOTP_ISSUER=None, OTP_TOTP_IMAGE=image_url): 185 url = self.device.config_url 186 187 parsed = urlsplit(url) 188 params = parse_qs(parsed.query) 189 190 self.assertEqual(parsed.scheme, "otpauth") 191 self.assertEqual(parsed.netloc, "totp") 192 self.assertEqual(parsed.path, "/alice") 193 self.assertIn("secret", params) 194 self.assertEqual(params["image"][0], image_url)
Test config_url with image
Inherited Members
class
ThrottlingTestCase(TOTPDeviceMixin, authentik.stages.authenticator.tests.ThrottlingTestMixin, django.test.testcases.TestCase):
197class ThrottlingTestCase(TOTPDeviceMixin, ThrottlingTestMixin, TestCase): 198 """Test TOTP Throttling""" 199 200 def valid_token(self): 201 return self.tokens[3] 202 203 def invalid_token(self): 204 return -1
Test TOTP Throttling