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 OTP_TOTP_THROTTLE_FACTOR=0, 67) 68class TOTPTest(TOTPDeviceMixin, TestCase): 69 """TOTP tests""" 70 71 def test_default_key(self): 72 """Ensure default_key is valid""" 73 device = self.alice.totpdevice_set.create() 74 75 # Make sure we can decode the key. 76 _ = device.bin_key 77 78 def test_single(self): 79 """Test single token""" 80 results = [self.device.verify_token(token) for token in self.tokens] 81 82 self.assertEqual(results, [False] * 3 + [True] + [False] * 6) 83 84 def test_tolerance(self): 85 """Test tolerance""" 86 self.device.tolerance = 1 87 results = [self.device.verify_token(token) for token in self.tokens] 88 89 self.assertEqual(results, [False] * 2 + [True] * 3 + [False] * 5) 90 91 def test_drift(self): 92 """Test drift""" 93 self.device.tolerance = 1 94 self.device.drift = -1 95 results = [self.device.verify_token(token) for token in self.tokens] 96 97 self.assertEqual(results, [False] * 1 + [True] * 3 + [False] * 6) 98 99 def test_sync_drift(self): 100 """Test sync drift""" 101 self.device.tolerance = 2 102 with self.settings(OTP_TOTP_SYNC=True): 103 valid = self.device.verify_token(self.tokens[5]) 104 105 self.assertTrue(valid) 106 self.assertEqual(self.device.drift, 2) 107 108 def test_no_reuse(self): 109 """Test reuse""" 110 verified1 = self.device.verify_token(self.tokens[3]) 111 verified2 = self.device.verify_token(self.tokens[3]) 112 113 self.assertEqual(self.device.last_t, 3) 114 self.assertTrue(verified1) 115 self.assertFalse(verified2) 116 117 def test_config_url(self): 118 """Test config_url""" 119 with override_settings(OTP_TOTP_ISSUER=None): 120 url = self.device.config_url 121 122 parsed = urlsplit(url) 123 params = parse_qs(parsed.query) 124 125 self.assertEqual(parsed.scheme, "otpauth") 126 self.assertEqual(parsed.netloc, "totp") 127 self.assertEqual(parsed.path, "/alice") 128 self.assertIn("secret", params) 129 self.assertNotIn("issuer", params) 130 131 def test_config_url_issuer(self): 132 """Test config_url issuer""" 133 with override_settings(OTP_TOTP_ISSUER="example.com"): 134 url = self.device.config_url 135 136 parsed = urlsplit(url) 137 params = parse_qs(parsed.query) 138 139 self.assertEqual(parsed.scheme, "otpauth") 140 self.assertEqual(parsed.netloc, "totp") 141 self.assertEqual(parsed.path, "/example.com%3Aalice") 142 self.assertIn("secret", params) 143 self.assertIn("issuer", params) 144 self.assertEqual(params["issuer"][0], "example.com") 145 146 def test_config_url_issuer_spaces(self): 147 """Test config_url issuer with spaces""" 148 with override_settings(OTP_TOTP_ISSUER="Very Trustworthy Source"): 149 url = self.device.config_url 150 151 parsed = urlsplit(url) 152 params = parse_qs(parsed.query) 153 154 self.assertEqual(parsed.scheme, "otpauth") 155 self.assertEqual(parsed.netloc, "totp") 156 self.assertEqual(parsed.path, "/Very%20Trustworthy%20Source%3Aalice") 157 self.assertIn("secret", params) 158 self.assertIn("issuer", params) 159 self.assertEqual(params["issuer"][0], "Very Trustworthy Source") 160 161 def test_config_url_issuer_method(self): 162 """Test config_url issuer method""" 163 with override_settings(OTP_TOTP_ISSUER=lambda d: d.user.email): 164 url = self.device.config_url 165 166 parsed = urlsplit(url) 167 params = parse_qs(parsed.query) 168 169 self.assertEqual(parsed.scheme, "otpauth") 170 self.assertEqual(parsed.netloc, "totp") 171 self.assertEqual(parsed.path, "/alice%40example.com%3Aalice") 172 self.assertIn("secret", params) 173 self.assertIn("issuer", params) 174 self.assertEqual(params["issuer"][0], "alice@example.com") 175 176 def test_config_url_image(self): 177 """Test config_url with image""" 178 image_url = "https://test.invalid/square.png" 179 180 with override_settings(OTP_TOTP_ISSUER=None, OTP_TOTP_IMAGE=image_url): 181 url = self.device.config_url 182 183 parsed = urlsplit(url) 184 params = parse_qs(parsed.query) 185 186 self.assertEqual(parsed.scheme, "otpauth") 187 self.assertEqual(parsed.netloc, "totp") 188 self.assertEqual(parsed.path, "/alice") 189 self.assertIn("secret", params) 190 self.assertEqual(params["image"][0], image_url) 191 192 193@override_settings( 194 OTP_TOTP_THROTTLE_FACTOR=1, 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, OTP_TOTP_THROTTLE_FACTOR=0)
class
TOTPTest65@override_settings( 66 OTP_TOTP_SYNC=False, 67 OTP_TOTP_THROTTLE_FACTOR=0, 68) 69class TOTPTest(TOTPDeviceMixin, TestCase): 70 """TOTP tests""" 71 72 def test_default_key(self): 73 """Ensure default_key is valid""" 74 device = self.alice.totpdevice_set.create() 75 76 # Make sure we can decode the key. 77 _ = device.bin_key 78 79 def test_single(self): 80 """Test single token""" 81 results = [self.device.verify_token(token) for token in self.tokens] 82 83 self.assertEqual(results, [False] * 3 + [True] + [False] * 6) 84 85 def test_tolerance(self): 86 """Test tolerance""" 87 self.device.tolerance = 1 88 results = [self.device.verify_token(token) for token in self.tokens] 89 90 self.assertEqual(results, [False] * 2 + [True] * 3 + [False] * 5) 91 92 def test_drift(self): 93 """Test drift""" 94 self.device.tolerance = 1 95 self.device.drift = -1 96 results = [self.device.verify_token(token) for token in self.tokens] 97 98 self.assertEqual(results, [False] * 1 + [True] * 3 + [False] * 6) 99 100 def test_sync_drift(self): 101 """Test sync drift""" 102 self.device.tolerance = 2 103 with self.settings(OTP_TOTP_SYNC=True): 104 valid = self.device.verify_token(self.tokens[5]) 105 106 self.assertTrue(valid) 107 self.assertEqual(self.device.drift, 2) 108 109 def test_no_reuse(self): 110 """Test reuse""" 111 verified1 = self.device.verify_token(self.tokens[3]) 112 verified2 = self.device.verify_token(self.tokens[3]) 113 114 self.assertEqual(self.device.last_t, 3) 115 self.assertTrue(verified1) 116 self.assertFalse(verified2) 117 118 def test_config_url(self): 119 """Test config_url""" 120 with override_settings(OTP_TOTP_ISSUER=None): 121 url = self.device.config_url 122 123 parsed = urlsplit(url) 124 params = parse_qs(parsed.query) 125 126 self.assertEqual(parsed.scheme, "otpauth") 127 self.assertEqual(parsed.netloc, "totp") 128 self.assertEqual(parsed.path, "/alice") 129 self.assertIn("secret", params) 130 self.assertNotIn("issuer", params) 131 132 def test_config_url_issuer(self): 133 """Test config_url issuer""" 134 with override_settings(OTP_TOTP_ISSUER="example.com"): 135 url = self.device.config_url 136 137 parsed = urlsplit(url) 138 params = parse_qs(parsed.query) 139 140 self.assertEqual(parsed.scheme, "otpauth") 141 self.assertEqual(parsed.netloc, "totp") 142 self.assertEqual(parsed.path, "/example.com%3Aalice") 143 self.assertIn("secret", params) 144 self.assertIn("issuer", params) 145 self.assertEqual(params["issuer"][0], "example.com") 146 147 def test_config_url_issuer_spaces(self): 148 """Test config_url issuer with spaces""" 149 with override_settings(OTP_TOTP_ISSUER="Very Trustworthy Source"): 150 url = self.device.config_url 151 152 parsed = urlsplit(url) 153 params = parse_qs(parsed.query) 154 155 self.assertEqual(parsed.scheme, "otpauth") 156 self.assertEqual(parsed.netloc, "totp") 157 self.assertEqual(parsed.path, "/Very%20Trustworthy%20Source%3Aalice") 158 self.assertIn("secret", params) 159 self.assertIn("issuer", params) 160 self.assertEqual(params["issuer"][0], "Very Trustworthy Source") 161 162 def test_config_url_issuer_method(self): 163 """Test config_url issuer method""" 164 with override_settings(OTP_TOTP_ISSUER=lambda d: d.user.email): 165 url = self.device.config_url 166 167 parsed = urlsplit(url) 168 params = parse_qs(parsed.query) 169 170 self.assertEqual(parsed.scheme, "otpauth") 171 self.assertEqual(parsed.netloc, "totp") 172 self.assertEqual(parsed.path, "/alice%40example.com%3Aalice") 173 self.assertIn("secret", params) 174 self.assertIn("issuer", params) 175 self.assertEqual(params["issuer"][0], "alice@example.com") 176 177 def test_config_url_image(self): 178 """Test config_url with image""" 179 image_url = "https://test.invalid/square.png" 180 181 with override_settings(OTP_TOTP_ISSUER=None, OTP_TOTP_IMAGE=image_url): 182 url = self.device.config_url 183 184 parsed = urlsplit(url) 185 params = parse_qs(parsed.query) 186 187 self.assertEqual(parsed.scheme, "otpauth") 188 self.assertEqual(parsed.netloc, "totp") 189 self.assertEqual(parsed.path, "/alice") 190 self.assertIn("secret", params) 191 self.assertEqual(params["image"][0], image_url)
TOTP tests
def
test_default_key(self):
72 def test_default_key(self): 73 """Ensure default_key is valid""" 74 device = self.alice.totpdevice_set.create() 75 76 # Make sure we can decode the key. 77 _ = device.bin_key
Ensure default_key is valid
def
test_single(self):
79 def test_single(self): 80 """Test single token""" 81 results = [self.device.verify_token(token) for token in self.tokens] 82 83 self.assertEqual(results, [False] * 3 + [True] + [False] * 6)
Test single token
def
test_tolerance(self):
85 def test_tolerance(self): 86 """Test tolerance""" 87 self.device.tolerance = 1 88 results = [self.device.verify_token(token) for token in self.tokens] 89 90 self.assertEqual(results, [False] * 2 + [True] * 3 + [False] * 5)
Test tolerance
def
test_drift(self):
92 def test_drift(self): 93 """Test drift""" 94 self.device.tolerance = 1 95 self.device.drift = -1 96 results = [self.device.verify_token(token) for token in self.tokens] 97 98 self.assertEqual(results, [False] * 1 + [True] * 3 + [False] * 6)
Test drift
def
test_sync_drift(self):
100 def test_sync_drift(self): 101 """Test sync drift""" 102 self.device.tolerance = 2 103 with self.settings(OTP_TOTP_SYNC=True): 104 valid = self.device.verify_token(self.tokens[5]) 105 106 self.assertTrue(valid) 107 self.assertEqual(self.device.drift, 2)
Test sync drift
def
test_no_reuse(self):
109 def test_no_reuse(self): 110 """Test reuse""" 111 verified1 = self.device.verify_token(self.tokens[3]) 112 verified2 = self.device.verify_token(self.tokens[3]) 113 114 self.assertEqual(self.device.last_t, 3) 115 self.assertTrue(verified1) 116 self.assertFalse(verified2)
Test reuse
def
test_config_url(self):
118 def test_config_url(self): 119 """Test config_url""" 120 with override_settings(OTP_TOTP_ISSUER=None): 121 url = self.device.config_url 122 123 parsed = urlsplit(url) 124 params = parse_qs(parsed.query) 125 126 self.assertEqual(parsed.scheme, "otpauth") 127 self.assertEqual(parsed.netloc, "totp") 128 self.assertEqual(parsed.path, "/alice") 129 self.assertIn("secret", params) 130 self.assertNotIn("issuer", params)
Test config_url
def
test_config_url_issuer(self):
132 def test_config_url_issuer(self): 133 """Test config_url issuer""" 134 with override_settings(OTP_TOTP_ISSUER="example.com"): 135 url = self.device.config_url 136 137 parsed = urlsplit(url) 138 params = parse_qs(parsed.query) 139 140 self.assertEqual(parsed.scheme, "otpauth") 141 self.assertEqual(parsed.netloc, "totp") 142 self.assertEqual(parsed.path, "/example.com%3Aalice") 143 self.assertIn("secret", params) 144 self.assertIn("issuer", params) 145 self.assertEqual(params["issuer"][0], "example.com")
Test config_url issuer
def
test_config_url_issuer_spaces(self):
147 def test_config_url_issuer_spaces(self): 148 """Test config_url issuer with spaces""" 149 with override_settings(OTP_TOTP_ISSUER="Very Trustworthy Source"): 150 url = self.device.config_url 151 152 parsed = urlsplit(url) 153 params = parse_qs(parsed.query) 154 155 self.assertEqual(parsed.scheme, "otpauth") 156 self.assertEqual(parsed.netloc, "totp") 157 self.assertEqual(parsed.path, "/Very%20Trustworthy%20Source%3Aalice") 158 self.assertIn("secret", params) 159 self.assertIn("issuer", params) 160 self.assertEqual(params["issuer"][0], "Very Trustworthy Source")
Test config_url issuer with spaces
def
test_config_url_issuer_method(self):
162 def test_config_url_issuer_method(self): 163 """Test config_url issuer method""" 164 with override_settings(OTP_TOTP_ISSUER=lambda d: d.user.email): 165 url = self.device.config_url 166 167 parsed = urlsplit(url) 168 params = parse_qs(parsed.query) 169 170 self.assertEqual(parsed.scheme, "otpauth") 171 self.assertEqual(parsed.netloc, "totp") 172 self.assertEqual(parsed.path, "/alice%40example.com%3Aalice") 173 self.assertIn("secret", params) 174 self.assertIn("issuer", params) 175 self.assertEqual(params["issuer"][0], "alice@example.com")
Test config_url issuer method
def
test_config_url_image(self):
177 def test_config_url_image(self): 178 """Test config_url with image""" 179 image_url = "https://test.invalid/square.png" 180 181 with override_settings(OTP_TOTP_ISSUER=None, OTP_TOTP_IMAGE=image_url): 182 url = self.device.config_url 183 184 parsed = urlsplit(url) 185 params = parse_qs(parsed.query) 186 187 self.assertEqual(parsed.scheme, "otpauth") 188 self.assertEqual(parsed.netloc, "totp") 189 self.assertEqual(parsed.path, "/alice") 190 self.assertIn("secret", params) 191 self.assertEqual(params["image"][0], image_url)
Test config_url with image
Inherited Members
@override_settings(OTP_TOTP_THROTTLE_FACTOR=1)
class
ThrottlingTestCase194@override_settings( 195 OTP_TOTP_THROTTLE_FACTOR=1, 196) 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