authentik.recovery.tests

recovery tests

  1"""recovery tests"""
  2
  3from datetime import timedelta
  4from io import StringIO
  5
  6from django.core.management import call_command
  7from django.test import TestCase
  8from django.urls import reverse
  9from django.utils.timezone import now
 10from django_tenants.utils import get_public_schema_name
 11
 12from authentik.core.models import Token, TokenIntents, User
 13
 14
 15class TestRecovery(TestCase):
 16    """recovery tests"""
 17
 18    def setUp(self):
 19        self.user: User = User.objects.create_user(username="recovery-test-user")
 20
 21    def test_create_key(self):
 22        """Test creation of a new key"""
 23        out = StringIO()
 24        self.assertEqual(len(Token.objects.filter(intent=TokenIntents.INTENT_RECOVERY)), 0)
 25        call_command(
 26            "create_recovery_key",
 27            "5",
 28            self.user.username,
 29            schema=get_public_schema_name(),
 30            stdout=out,
 31        )
 32        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
 33        self.assertIn(token.key, out.getvalue())
 34        self.assertIn("valid for 5\xa0minutes", out.getvalue())
 35        self.assertEqual(len(Token.objects.filter(intent=TokenIntents.INTENT_RECOVERY)), 1)
 36
 37    def test_create_key_invalid(self):
 38        """Test creation of a new key (invalid)"""
 39        out = StringIO()
 40        self.assertEqual(len(Token.objects.filter(intent=TokenIntents.INTENT_RECOVERY)), 0)
 41        call_command("create_recovery_key", "5", "foo", schema=get_public_schema_name(), stderr=out)
 42        self.assertIn("not found", out.getvalue())
 43
 44    def test_recovery_view(self):
 45        """Test recovery view"""
 46        out = StringIO()
 47        call_command(
 48            "create_recovery_key",
 49            "10",
 50            self.user.username,
 51            schema=get_public_schema_name(),
 52            stdout=out,
 53        )
 54        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
 55        self.client.get(reverse("authentik_recovery:use-token", kwargs={"key": token.key}))
 56        self.assertEqual(self.client.session["authenticatedsession"].user.pk, token.user.pk)
 57
 58    def test_recovery_view_invalid(self):
 59        """Test recovery view with invalid token"""
 60        response = self.client.get(reverse("authentik_recovery:use-token", kwargs={"key": "abc"}))
 61        self.assertEqual(response.status_code, 404)
 62
 63    def test_recovery_admin_group_invalid(self):
 64        """Test creation of admin group"""
 65        out = StringIO()
 66        call_command("create_admin_group", "1", schema=get_public_schema_name(), stderr=out)
 67        self.assertIn("not found", out.getvalue())
 68
 69    def test_recovery_admin_group(self):
 70        """Test creation of admin group"""
 71        out = StringIO()
 72        call_command(
 73            "create_admin_group", self.user.username, schema=get_public_schema_name(), stdout=out
 74        )
 75        self.assertIn("successfully added to", out.getvalue())
 76        self.assertTrue(self.user.is_superuser)
 77
 78    def test_create_key_default_duration(self):
 79        """Test creation of a new key with default duration (60 minutes)"""
 80        out = StringIO()
 81        before_creation = now()
 82        call_command(
 83            "create_recovery_key",
 84            self.user.username,
 85            schema=get_public_schema_name(),
 86            stdout=out,
 87        )
 88        after_creation = now()
 89
 90        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
 91        self.assertIn(token.key, out.getvalue())
 92        self.assertIn("valid for 1\xa0hour", out.getvalue())
 93
 94        # Verify the token expires in approximately 60 minutes (default)
 95        expected_expiry_min = before_creation + timedelta(minutes=60)
 96        expected_expiry_max = after_creation + timedelta(minutes=60)
 97        self.assertGreaterEqual(token.expires, expected_expiry_min)
 98        self.assertLessEqual(token.expires, expected_expiry_max)
 99
100    def test_create_key_custom_duration(self):
101        """Test creation of a new key with custom duration"""
102        out = StringIO()
103        custom_duration = 120  # 2 hours
104        before_creation = now()
105
106        call_command(
107            "create_recovery_key",
108            str(custom_duration),
109            self.user.username,
110            schema=get_public_schema_name(),
111            stdout=out,
112        )
113        after_creation = now()
114
115        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
116        self.assertIn(token.key, out.getvalue())
117        self.assertIn("valid for 2\xa0hours", out.getvalue())
118
119        # Verify the token expires in approximately the custom duration
120        expected_expiry_min = before_creation + timedelta(minutes=custom_duration)
121        expected_expiry_max = after_creation + timedelta(minutes=custom_duration)
122        self.assertGreaterEqual(token.expires, expected_expiry_min)
123        self.assertLessEqual(token.expires, expected_expiry_max)
124
125    def test_create_key_short_duration(self):
126        """Test creation of a new key with very short duration (1 minute)"""
127        out = StringIO()
128        short_duration = 1
129        before_creation = now()
130
131        call_command(
132            "create_recovery_key",
133            str(short_duration),
134            self.user.username,
135            schema=get_public_schema_name(),
136            stdout=out,
137        )
138        after_creation = now()
139
140        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
141        self.assertIn(token.key, out.getvalue())
142        self.assertIn("valid for 1\xa0minute", out.getvalue())
143
144        # Verify the token expires in approximately 1 minute
145        expected_expiry_min = before_creation + timedelta(minutes=short_duration)
146        expected_expiry_max = after_creation + timedelta(minutes=short_duration)
147        self.assertGreaterEqual(token.expires, expected_expiry_min)
148        self.assertLessEqual(token.expires, expected_expiry_max)
149
150    def test_create_key_duration_validation(self):
151        """Test that the duration is correctly converted to minutes"""
152        # Test various durations to ensure they're calculated correctly
153        test_cases = [1, 5, 30, 60, 120, 1440]  # 1min, 5min, 30min, 1hr, 2hr, 24hr
154
155        for duration in test_cases:
156            with self.subTest(duration=duration):
157                out = StringIO()
158                before_creation = now()
159
160                call_command(
161                    "create_recovery_key",
162                    str(duration),
163                    self.user.username,
164                    schema=get_public_schema_name(),
165                    stdout=out,
166                )
167                after_creation = now()
168
169                token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
170
171                # Verify the token expires in approximately the specified duration
172                expected_expiry_min = before_creation + timedelta(minutes=duration)
173                expected_expiry_max = after_creation + timedelta(minutes=duration)
174                self.assertGreaterEqual(token.expires, expected_expiry_min)
175                self.assertLessEqual(token.expires, expected_expiry_max)
176
177                # Clean up for next iteration
178                token.delete()
179
180    def test_create_key_help_text(self):
181        """Test that the help text correctly indicates minutes"""
182        from authentik.recovery.management.commands.create_recovery_key import Command
183
184        command = Command()
185        # Check that the help text mentions minutes
186        parser = command.create_parser("test", "create_recovery_key")
187        help_text = parser.format_help()
188        self.assertIn("minutes", help_text.lower())
189        self.assertNotIn("years", help_text.lower())
class TestRecovery(django.test.testcases.TestCase):
 16class TestRecovery(TestCase):
 17    """recovery tests"""
 18
 19    def setUp(self):
 20        self.user: User = User.objects.create_user(username="recovery-test-user")
 21
 22    def test_create_key(self):
 23        """Test creation of a new key"""
 24        out = StringIO()
 25        self.assertEqual(len(Token.objects.filter(intent=TokenIntents.INTENT_RECOVERY)), 0)
 26        call_command(
 27            "create_recovery_key",
 28            "5",
 29            self.user.username,
 30            schema=get_public_schema_name(),
 31            stdout=out,
 32        )
 33        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
 34        self.assertIn(token.key, out.getvalue())
 35        self.assertIn("valid for 5\xa0minutes", out.getvalue())
 36        self.assertEqual(len(Token.objects.filter(intent=TokenIntents.INTENT_RECOVERY)), 1)
 37
 38    def test_create_key_invalid(self):
 39        """Test creation of a new key (invalid)"""
 40        out = StringIO()
 41        self.assertEqual(len(Token.objects.filter(intent=TokenIntents.INTENT_RECOVERY)), 0)
 42        call_command("create_recovery_key", "5", "foo", schema=get_public_schema_name(), stderr=out)
 43        self.assertIn("not found", out.getvalue())
 44
 45    def test_recovery_view(self):
 46        """Test recovery view"""
 47        out = StringIO()
 48        call_command(
 49            "create_recovery_key",
 50            "10",
 51            self.user.username,
 52            schema=get_public_schema_name(),
 53            stdout=out,
 54        )
 55        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
 56        self.client.get(reverse("authentik_recovery:use-token", kwargs={"key": token.key}))
 57        self.assertEqual(self.client.session["authenticatedsession"].user.pk, token.user.pk)
 58
 59    def test_recovery_view_invalid(self):
 60        """Test recovery view with invalid token"""
 61        response = self.client.get(reverse("authentik_recovery:use-token", kwargs={"key": "abc"}))
 62        self.assertEqual(response.status_code, 404)
 63
 64    def test_recovery_admin_group_invalid(self):
 65        """Test creation of admin group"""
 66        out = StringIO()
 67        call_command("create_admin_group", "1", schema=get_public_schema_name(), stderr=out)
 68        self.assertIn("not found", out.getvalue())
 69
 70    def test_recovery_admin_group(self):
 71        """Test creation of admin group"""
 72        out = StringIO()
 73        call_command(
 74            "create_admin_group", self.user.username, schema=get_public_schema_name(), stdout=out
 75        )
 76        self.assertIn("successfully added to", out.getvalue())
 77        self.assertTrue(self.user.is_superuser)
 78
 79    def test_create_key_default_duration(self):
 80        """Test creation of a new key with default duration (60 minutes)"""
 81        out = StringIO()
 82        before_creation = now()
 83        call_command(
 84            "create_recovery_key",
 85            self.user.username,
 86            schema=get_public_schema_name(),
 87            stdout=out,
 88        )
 89        after_creation = now()
 90
 91        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
 92        self.assertIn(token.key, out.getvalue())
 93        self.assertIn("valid for 1\xa0hour", out.getvalue())
 94
 95        # Verify the token expires in approximately 60 minutes (default)
 96        expected_expiry_min = before_creation + timedelta(minutes=60)
 97        expected_expiry_max = after_creation + timedelta(minutes=60)
 98        self.assertGreaterEqual(token.expires, expected_expiry_min)
 99        self.assertLessEqual(token.expires, expected_expiry_max)
100
101    def test_create_key_custom_duration(self):
102        """Test creation of a new key with custom duration"""
103        out = StringIO()
104        custom_duration = 120  # 2 hours
105        before_creation = now()
106
107        call_command(
108            "create_recovery_key",
109            str(custom_duration),
110            self.user.username,
111            schema=get_public_schema_name(),
112            stdout=out,
113        )
114        after_creation = now()
115
116        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
117        self.assertIn(token.key, out.getvalue())
118        self.assertIn("valid for 2\xa0hours", out.getvalue())
119
120        # Verify the token expires in approximately the custom duration
121        expected_expiry_min = before_creation + timedelta(minutes=custom_duration)
122        expected_expiry_max = after_creation + timedelta(minutes=custom_duration)
123        self.assertGreaterEqual(token.expires, expected_expiry_min)
124        self.assertLessEqual(token.expires, expected_expiry_max)
125
126    def test_create_key_short_duration(self):
127        """Test creation of a new key with very short duration (1 minute)"""
128        out = StringIO()
129        short_duration = 1
130        before_creation = now()
131
132        call_command(
133            "create_recovery_key",
134            str(short_duration),
135            self.user.username,
136            schema=get_public_schema_name(),
137            stdout=out,
138        )
139        after_creation = now()
140
141        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
142        self.assertIn(token.key, out.getvalue())
143        self.assertIn("valid for 1\xa0minute", out.getvalue())
144
145        # Verify the token expires in approximately 1 minute
146        expected_expiry_min = before_creation + timedelta(minutes=short_duration)
147        expected_expiry_max = after_creation + timedelta(minutes=short_duration)
148        self.assertGreaterEqual(token.expires, expected_expiry_min)
149        self.assertLessEqual(token.expires, expected_expiry_max)
150
151    def test_create_key_duration_validation(self):
152        """Test that the duration is correctly converted to minutes"""
153        # Test various durations to ensure they're calculated correctly
154        test_cases = [1, 5, 30, 60, 120, 1440]  # 1min, 5min, 30min, 1hr, 2hr, 24hr
155
156        for duration in test_cases:
157            with self.subTest(duration=duration):
158                out = StringIO()
159                before_creation = now()
160
161                call_command(
162                    "create_recovery_key",
163                    str(duration),
164                    self.user.username,
165                    schema=get_public_schema_name(),
166                    stdout=out,
167                )
168                after_creation = now()
169
170                token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
171
172                # Verify the token expires in approximately the specified duration
173                expected_expiry_min = before_creation + timedelta(minutes=duration)
174                expected_expiry_max = after_creation + timedelta(minutes=duration)
175                self.assertGreaterEqual(token.expires, expected_expiry_min)
176                self.assertLessEqual(token.expires, expected_expiry_max)
177
178                # Clean up for next iteration
179                token.delete()
180
181    def test_create_key_help_text(self):
182        """Test that the help text correctly indicates minutes"""
183        from authentik.recovery.management.commands.create_recovery_key import Command
184
185        command = Command()
186        # Check that the help text mentions minutes
187        parser = command.create_parser("test", "create_recovery_key")
188        help_text = parser.format_help()
189        self.assertIn("minutes", help_text.lower())
190        self.assertNotIn("years", help_text.lower())

recovery tests

def setUp(self):
19    def setUp(self):
20        self.user: User = User.objects.create_user(username="recovery-test-user")

Hook method for setting up the test fixture before exercising it.

def test_create_key(self):
22    def test_create_key(self):
23        """Test creation of a new key"""
24        out = StringIO()
25        self.assertEqual(len(Token.objects.filter(intent=TokenIntents.INTENT_RECOVERY)), 0)
26        call_command(
27            "create_recovery_key",
28            "5",
29            self.user.username,
30            schema=get_public_schema_name(),
31            stdout=out,
32        )
33        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
34        self.assertIn(token.key, out.getvalue())
35        self.assertIn("valid for 5\xa0minutes", out.getvalue())
36        self.assertEqual(len(Token.objects.filter(intent=TokenIntents.INTENT_RECOVERY)), 1)

Test creation of a new key

def test_create_key_invalid(self):
38    def test_create_key_invalid(self):
39        """Test creation of a new key (invalid)"""
40        out = StringIO()
41        self.assertEqual(len(Token.objects.filter(intent=TokenIntents.INTENT_RECOVERY)), 0)
42        call_command("create_recovery_key", "5", "foo", schema=get_public_schema_name(), stderr=out)
43        self.assertIn("not found", out.getvalue())

Test creation of a new key (invalid)

def test_recovery_view(self):
45    def test_recovery_view(self):
46        """Test recovery view"""
47        out = StringIO()
48        call_command(
49            "create_recovery_key",
50            "10",
51            self.user.username,
52            schema=get_public_schema_name(),
53            stdout=out,
54        )
55        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
56        self.client.get(reverse("authentik_recovery:use-token", kwargs={"key": token.key}))
57        self.assertEqual(self.client.session["authenticatedsession"].user.pk, token.user.pk)

Test recovery view

def test_recovery_view_invalid(self):
59    def test_recovery_view_invalid(self):
60        """Test recovery view with invalid token"""
61        response = self.client.get(reverse("authentik_recovery:use-token", kwargs={"key": "abc"}))
62        self.assertEqual(response.status_code, 404)

Test recovery view with invalid token

def test_recovery_admin_group_invalid(self):
64    def test_recovery_admin_group_invalid(self):
65        """Test creation of admin group"""
66        out = StringIO()
67        call_command("create_admin_group", "1", schema=get_public_schema_name(), stderr=out)
68        self.assertIn("not found", out.getvalue())

Test creation of admin group

def test_recovery_admin_group(self):
70    def test_recovery_admin_group(self):
71        """Test creation of admin group"""
72        out = StringIO()
73        call_command(
74            "create_admin_group", self.user.username, schema=get_public_schema_name(), stdout=out
75        )
76        self.assertIn("successfully added to", out.getvalue())
77        self.assertTrue(self.user.is_superuser)

Test creation of admin group

def test_create_key_default_duration(self):
79    def test_create_key_default_duration(self):
80        """Test creation of a new key with default duration (60 minutes)"""
81        out = StringIO()
82        before_creation = now()
83        call_command(
84            "create_recovery_key",
85            self.user.username,
86            schema=get_public_schema_name(),
87            stdout=out,
88        )
89        after_creation = now()
90
91        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
92        self.assertIn(token.key, out.getvalue())
93        self.assertIn("valid for 1\xa0hour", out.getvalue())
94
95        # Verify the token expires in approximately 60 minutes (default)
96        expected_expiry_min = before_creation + timedelta(minutes=60)
97        expected_expiry_max = after_creation + timedelta(minutes=60)
98        self.assertGreaterEqual(token.expires, expected_expiry_min)
99        self.assertLessEqual(token.expires, expected_expiry_max)

Test creation of a new key with default duration (60 minutes)

def test_create_key_custom_duration(self):
101    def test_create_key_custom_duration(self):
102        """Test creation of a new key with custom duration"""
103        out = StringIO()
104        custom_duration = 120  # 2 hours
105        before_creation = now()
106
107        call_command(
108            "create_recovery_key",
109            str(custom_duration),
110            self.user.username,
111            schema=get_public_schema_name(),
112            stdout=out,
113        )
114        after_creation = now()
115
116        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
117        self.assertIn(token.key, out.getvalue())
118        self.assertIn("valid for 2\xa0hours", out.getvalue())
119
120        # Verify the token expires in approximately the custom duration
121        expected_expiry_min = before_creation + timedelta(minutes=custom_duration)
122        expected_expiry_max = after_creation + timedelta(minutes=custom_duration)
123        self.assertGreaterEqual(token.expires, expected_expiry_min)
124        self.assertLessEqual(token.expires, expected_expiry_max)

Test creation of a new key with custom duration

def test_create_key_short_duration(self):
126    def test_create_key_short_duration(self):
127        """Test creation of a new key with very short duration (1 minute)"""
128        out = StringIO()
129        short_duration = 1
130        before_creation = now()
131
132        call_command(
133            "create_recovery_key",
134            str(short_duration),
135            self.user.username,
136            schema=get_public_schema_name(),
137            stdout=out,
138        )
139        after_creation = now()
140
141        token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
142        self.assertIn(token.key, out.getvalue())
143        self.assertIn("valid for 1\xa0minute", out.getvalue())
144
145        # Verify the token expires in approximately 1 minute
146        expected_expiry_min = before_creation + timedelta(minutes=short_duration)
147        expected_expiry_max = after_creation + timedelta(minutes=short_duration)
148        self.assertGreaterEqual(token.expires, expected_expiry_min)
149        self.assertLessEqual(token.expires, expected_expiry_max)

Test creation of a new key with very short duration (1 minute)

def test_create_key_duration_validation(self):
151    def test_create_key_duration_validation(self):
152        """Test that the duration is correctly converted to minutes"""
153        # Test various durations to ensure they're calculated correctly
154        test_cases = [1, 5, 30, 60, 120, 1440]  # 1min, 5min, 30min, 1hr, 2hr, 24hr
155
156        for duration in test_cases:
157            with self.subTest(duration=duration):
158                out = StringIO()
159                before_creation = now()
160
161                call_command(
162                    "create_recovery_key",
163                    str(duration),
164                    self.user.username,
165                    schema=get_public_schema_name(),
166                    stdout=out,
167                )
168                after_creation = now()
169
170                token = Token.objects.get(intent=TokenIntents.INTENT_RECOVERY, user=self.user)
171
172                # Verify the token expires in approximately the specified duration
173                expected_expiry_min = before_creation + timedelta(minutes=duration)
174                expected_expiry_max = after_creation + timedelta(minutes=duration)
175                self.assertGreaterEqual(token.expires, expected_expiry_min)
176                self.assertLessEqual(token.expires, expected_expiry_max)
177
178                # Clean up for next iteration
179                token.delete()

Test that the duration is correctly converted to minutes

def test_create_key_help_text(self):
181    def test_create_key_help_text(self):
182        """Test that the help text correctly indicates minutes"""
183        from authentik.recovery.management.commands.create_recovery_key import Command
184
185        command = Command()
186        # Check that the help text mentions minutes
187        parser = command.create_parser("test", "create_recovery_key")
188        help_text = parser.format_help()
189        self.assertIn("minutes", help_text.lower())
190        self.assertNotIn("years", help_text.lower())

Test that the help text correctly indicates minutes