authentik.core.tests.test_setup

  1from http import HTTPStatus
  2from os import environ
  3
  4from django.contrib.auth.hashers import make_password
  5from django.urls import reverse
  6
  7from authentik.blueprints.tests import apply_blueprint
  8from authentik.core.apps import Setup
  9from authentik.core.models import Token, TokenIntents, User
 10from authentik.flows.models import Flow
 11from authentik.flows.tests import FlowTestCase
 12from authentik.lib.generators import generate_id
 13from authentik.root.signals import post_startup, pre_startup
 14from authentik.tenants.flags import patch_flag
 15
 16
 17class TestSetup(FlowTestCase):
 18    def tearDown(self):
 19        environ.pop("AUTHENTIK_BOOTSTRAP_PASSWORD", None)
 20        environ.pop("AUTHENTIK_BOOTSTRAP_PASSWORD_HASH", None)
 21        environ.pop("AUTHENTIK_BOOTSTRAP_TOKEN", None)
 22
 23    @patch_flag(Setup, True)
 24    def test_setup(self):
 25        """Test existing instance"""
 26        res = self.client.get(reverse("authentik_core:root-redirect"))
 27        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 28        self.assertRedirects(
 29            res,
 30            reverse("authentik_flows:default-authentication") + "?next=/",
 31            fetch_redirect_response=False,
 32        )
 33
 34        res = self.client.head(reverse("authentik_core:setup"))
 35        self.assertEqual(res.status_code, HTTPStatus.SERVICE_UNAVAILABLE)
 36
 37        res = self.client.get(reverse("authentik_core:setup"))
 38        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 39        self.assertRedirects(
 40            res,
 41            reverse("authentik_core:root-redirect"),
 42            fetch_redirect_response=False,
 43        )
 44
 45    @patch_flag(Setup, False)
 46    def test_not_setup_no_flow(self):
 47        """Test case on initial startup; setup flag is not set and oobe flow does
 48        not exist yet"""
 49        Flow.objects.filter(slug="initial-setup").delete()
 50        res = self.client.get(reverse("authentik_core:root-redirect"))
 51        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 52        self.assertRedirects(res, reverse("authentik_core:setup"), fetch_redirect_response=False)
 53        # Flow does not exist, hence 503
 54        res = self.client.get(reverse("authentik_core:setup"))
 55        self.assertEqual(res.status_code, HTTPStatus.SERVICE_UNAVAILABLE)
 56        res = self.client.head(reverse("authentik_core:setup"))
 57        self.assertEqual(res.status_code, HTTPStatus.SERVICE_UNAVAILABLE)
 58
 59    @patch_flag(Setup, False)
 60    @apply_blueprint("default/flow-oobe.yaml")
 61    def test_not_setup(self):
 62        """Test case for when worker comes up, and has created flow"""
 63        res = self.client.get(reverse("authentik_core:root-redirect"))
 64        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 65        self.assertRedirects(res, reverse("authentik_core:setup"), fetch_redirect_response=False)
 66        # Flow does not exist, hence 503
 67        res = self.client.head(reverse("authentik_core:setup"))
 68        self.assertEqual(res.status_code, HTTPStatus.OK)
 69        res = self.client.get(reverse("authentik_core:setup"))
 70        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 71        self.assertRedirects(
 72            res,
 73            reverse("authentik_core:if-flow", kwargs={"flow_slug": "initial-setup"}),
 74            fetch_redirect_response=False,
 75        )
 76
 77    @apply_blueprint("default/flow-oobe.yaml")
 78    @apply_blueprint("system/bootstrap.yaml")
 79    def test_setup_flow_full(self):
 80        """Test full setup flow"""
 81        Setup.set(False)
 82
 83        res = self.client.get(reverse("authentik_core:setup"))
 84        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 85        self.assertRedirects(
 86            res,
 87            reverse("authentik_core:if-flow", kwargs={"flow_slug": "initial-setup"}),
 88            fetch_redirect_response=False,
 89        )
 90
 91        res = self.client.get(
 92            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
 93        )
 94        self.assertEqual(res.status_code, HTTPStatus.OK)
 95        self.assertStageResponse(res, component="ak-stage-prompt")
 96
 97        pw = generate_id()
 98        res = self.client.post(
 99            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
100            {
101                "email": f"{generate_id()}@t.goauthentik.io",
102                "password": pw,
103                "password_repeat": pw,
104                "component": "ak-stage-prompt",
105            },
106        )
107        self.assertEqual(res.status_code, HTTPStatus.FOUND)
108
109        res = self.client.get(
110            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
111        )
112        self.assertEqual(res.status_code, HTTPStatus.FOUND)
113
114        res = self.client.get(
115            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
116        )
117        self.assertEqual(res.status_code, HTTPStatus.FOUND)
118
119        res = self.client.get(
120            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
121        )
122        self.assertEqual(res.status_code, HTTPStatus.OK)
123
124        self.assertTrue(Setup.get())
125        user = User.objects.get(username="akadmin")
126        self.assertTrue(user.check_password(pw))
127
128    @patch_flag(Setup, False)
129    @apply_blueprint("default/flow-oobe.yaml")
130    @apply_blueprint("system/bootstrap.yaml")
131    def test_setup_flow_direct(self):
132        """Test setup flow, directly accessing the flow"""
133        res = self.client.get(
134            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"})
135        )
136        self.assertStageResponse(
137            res,
138            component="ak-stage-access-denied",
139            error_message="Access the authentik setup by navigating to http://testserver/",
140        )
141
142    def test_setup_bootstrap_env(self):
143        """Test setup with env vars"""
144        User.objects.filter(username="akadmin").delete()
145        Setup.set(False)
146
147        environ["AUTHENTIK_BOOTSTRAP_PASSWORD"] = generate_id()
148        environ["AUTHENTIK_BOOTSTRAP_TOKEN"] = generate_id()
149        pre_startup.send(sender=self)
150        post_startup.send(sender=self)
151
152        self.assertTrue(Setup.get())
153        user = User.objects.get(username="akadmin")
154        self.assertTrue(user.check_password(environ["AUTHENTIK_BOOTSTRAP_PASSWORD"]))
155
156        token = Token.objects.filter(identifier="authentik-bootstrap-token").first()
157        self.assertEqual(token.intent, TokenIntents.INTENT_API)
158        self.assertEqual(token.key, environ["AUTHENTIK_BOOTSTRAP_TOKEN"])
159
160    def test_setup_bootstrap_env_password_hash(self):
161        """Test setup with password hash env var"""
162        User.objects.filter(username="akadmin").delete()
163        Setup.set(False)
164
165        password = generate_id()
166        password_hash = make_password(password)
167        environ["AUTHENTIK_BOOTSTRAP_PASSWORD_HASH"] = password_hash
168        pre_startup.send(sender=self)
169        post_startup.send(sender=self)
170
171        self.assertTrue(Setup.get())
172        user = User.objects.get(username="akadmin")
173        self.assertEqual(user.password, password_hash)
174        self.assertTrue(user.check_password(password))
class TestSetup(authentik.flows.tests.FlowTestCase):
 18class TestSetup(FlowTestCase):
 19    def tearDown(self):
 20        environ.pop("AUTHENTIK_BOOTSTRAP_PASSWORD", None)
 21        environ.pop("AUTHENTIK_BOOTSTRAP_PASSWORD_HASH", None)
 22        environ.pop("AUTHENTIK_BOOTSTRAP_TOKEN", None)
 23
 24    @patch_flag(Setup, True)
 25    def test_setup(self):
 26        """Test existing instance"""
 27        res = self.client.get(reverse("authentik_core:root-redirect"))
 28        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 29        self.assertRedirects(
 30            res,
 31            reverse("authentik_flows:default-authentication") + "?next=/",
 32            fetch_redirect_response=False,
 33        )
 34
 35        res = self.client.head(reverse("authentik_core:setup"))
 36        self.assertEqual(res.status_code, HTTPStatus.SERVICE_UNAVAILABLE)
 37
 38        res = self.client.get(reverse("authentik_core:setup"))
 39        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 40        self.assertRedirects(
 41            res,
 42            reverse("authentik_core:root-redirect"),
 43            fetch_redirect_response=False,
 44        )
 45
 46    @patch_flag(Setup, False)
 47    def test_not_setup_no_flow(self):
 48        """Test case on initial startup; setup flag is not set and oobe flow does
 49        not exist yet"""
 50        Flow.objects.filter(slug="initial-setup").delete()
 51        res = self.client.get(reverse("authentik_core:root-redirect"))
 52        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 53        self.assertRedirects(res, reverse("authentik_core:setup"), fetch_redirect_response=False)
 54        # Flow does not exist, hence 503
 55        res = self.client.get(reverse("authentik_core:setup"))
 56        self.assertEqual(res.status_code, HTTPStatus.SERVICE_UNAVAILABLE)
 57        res = self.client.head(reverse("authentik_core:setup"))
 58        self.assertEqual(res.status_code, HTTPStatus.SERVICE_UNAVAILABLE)
 59
 60    @patch_flag(Setup, False)
 61    @apply_blueprint("default/flow-oobe.yaml")
 62    def test_not_setup(self):
 63        """Test case for when worker comes up, and has created flow"""
 64        res = self.client.get(reverse("authentik_core:root-redirect"))
 65        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 66        self.assertRedirects(res, reverse("authentik_core:setup"), fetch_redirect_response=False)
 67        # Flow does not exist, hence 503
 68        res = self.client.head(reverse("authentik_core:setup"))
 69        self.assertEqual(res.status_code, HTTPStatus.OK)
 70        res = self.client.get(reverse("authentik_core:setup"))
 71        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 72        self.assertRedirects(
 73            res,
 74            reverse("authentik_core:if-flow", kwargs={"flow_slug": "initial-setup"}),
 75            fetch_redirect_response=False,
 76        )
 77
 78    @apply_blueprint("default/flow-oobe.yaml")
 79    @apply_blueprint("system/bootstrap.yaml")
 80    def test_setup_flow_full(self):
 81        """Test full setup flow"""
 82        Setup.set(False)
 83
 84        res = self.client.get(reverse("authentik_core:setup"))
 85        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 86        self.assertRedirects(
 87            res,
 88            reverse("authentik_core:if-flow", kwargs={"flow_slug": "initial-setup"}),
 89            fetch_redirect_response=False,
 90        )
 91
 92        res = self.client.get(
 93            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
 94        )
 95        self.assertEqual(res.status_code, HTTPStatus.OK)
 96        self.assertStageResponse(res, component="ak-stage-prompt")
 97
 98        pw = generate_id()
 99        res = self.client.post(
100            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
101            {
102                "email": f"{generate_id()}@t.goauthentik.io",
103                "password": pw,
104                "password_repeat": pw,
105                "component": "ak-stage-prompt",
106            },
107        )
108        self.assertEqual(res.status_code, HTTPStatus.FOUND)
109
110        res = self.client.get(
111            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
112        )
113        self.assertEqual(res.status_code, HTTPStatus.FOUND)
114
115        res = self.client.get(
116            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
117        )
118        self.assertEqual(res.status_code, HTTPStatus.FOUND)
119
120        res = self.client.get(
121            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
122        )
123        self.assertEqual(res.status_code, HTTPStatus.OK)
124
125        self.assertTrue(Setup.get())
126        user = User.objects.get(username="akadmin")
127        self.assertTrue(user.check_password(pw))
128
129    @patch_flag(Setup, False)
130    @apply_blueprint("default/flow-oobe.yaml")
131    @apply_blueprint("system/bootstrap.yaml")
132    def test_setup_flow_direct(self):
133        """Test setup flow, directly accessing the flow"""
134        res = self.client.get(
135            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"})
136        )
137        self.assertStageResponse(
138            res,
139            component="ak-stage-access-denied",
140            error_message="Access the authentik setup by navigating to http://testserver/",
141        )
142
143    def test_setup_bootstrap_env(self):
144        """Test setup with env vars"""
145        User.objects.filter(username="akadmin").delete()
146        Setup.set(False)
147
148        environ["AUTHENTIK_BOOTSTRAP_PASSWORD"] = generate_id()
149        environ["AUTHENTIK_BOOTSTRAP_TOKEN"] = generate_id()
150        pre_startup.send(sender=self)
151        post_startup.send(sender=self)
152
153        self.assertTrue(Setup.get())
154        user = User.objects.get(username="akadmin")
155        self.assertTrue(user.check_password(environ["AUTHENTIK_BOOTSTRAP_PASSWORD"]))
156
157        token = Token.objects.filter(identifier="authentik-bootstrap-token").first()
158        self.assertEqual(token.intent, TokenIntents.INTENT_API)
159        self.assertEqual(token.key, environ["AUTHENTIK_BOOTSTRAP_TOKEN"])
160
161    def test_setup_bootstrap_env_password_hash(self):
162        """Test setup with password hash env var"""
163        User.objects.filter(username="akadmin").delete()
164        Setup.set(False)
165
166        password = generate_id()
167        password_hash = make_password(password)
168        environ["AUTHENTIK_BOOTSTRAP_PASSWORD_HASH"] = password_hash
169        pre_startup.send(sender=self)
170        post_startup.send(sender=self)
171
172        self.assertTrue(Setup.get())
173        user = User.objects.get(username="akadmin")
174        self.assertEqual(user.password, password_hash)
175        self.assertTrue(user.check_password(password))

Helpers for testing flows and stages.

def tearDown(self):
19    def tearDown(self):
20        environ.pop("AUTHENTIK_BOOTSTRAP_PASSWORD", None)
21        environ.pop("AUTHENTIK_BOOTSTRAP_PASSWORD_HASH", None)
22        environ.pop("AUTHENTIK_BOOTSTRAP_TOKEN", None)

Hook method for deconstructing the test fixture after testing it.

@patch_flag(Setup, True)
def test_setup(self):
24    @patch_flag(Setup, True)
25    def test_setup(self):
26        """Test existing instance"""
27        res = self.client.get(reverse("authentik_core:root-redirect"))
28        self.assertEqual(res.status_code, HTTPStatus.FOUND)
29        self.assertRedirects(
30            res,
31            reverse("authentik_flows:default-authentication") + "?next=/",
32            fetch_redirect_response=False,
33        )
34
35        res = self.client.head(reverse("authentik_core:setup"))
36        self.assertEqual(res.status_code, HTTPStatus.SERVICE_UNAVAILABLE)
37
38        res = self.client.get(reverse("authentik_core:setup"))
39        self.assertEqual(res.status_code, HTTPStatus.FOUND)
40        self.assertRedirects(
41            res,
42            reverse("authentik_core:root-redirect"),
43            fetch_redirect_response=False,
44        )

Test existing instance

@patch_flag(Setup, False)
def test_not_setup_no_flow(self):
46    @patch_flag(Setup, False)
47    def test_not_setup_no_flow(self):
48        """Test case on initial startup; setup flag is not set and oobe flow does
49        not exist yet"""
50        Flow.objects.filter(slug="initial-setup").delete()
51        res = self.client.get(reverse("authentik_core:root-redirect"))
52        self.assertEqual(res.status_code, HTTPStatus.FOUND)
53        self.assertRedirects(res, reverse("authentik_core:setup"), fetch_redirect_response=False)
54        # Flow does not exist, hence 503
55        res = self.client.get(reverse("authentik_core:setup"))
56        self.assertEqual(res.status_code, HTTPStatus.SERVICE_UNAVAILABLE)
57        res = self.client.head(reverse("authentik_core:setup"))
58        self.assertEqual(res.status_code, HTTPStatus.SERVICE_UNAVAILABLE)

Test case on initial startup; setup flag is not set and oobe flow does not exist yet

@patch_flag(Setup, False)
@apply_blueprint('default/flow-oobe.yaml')
def test_not_setup(self):
60    @patch_flag(Setup, False)
61    @apply_blueprint("default/flow-oobe.yaml")
62    def test_not_setup(self):
63        """Test case for when worker comes up, and has created flow"""
64        res = self.client.get(reverse("authentik_core:root-redirect"))
65        self.assertEqual(res.status_code, HTTPStatus.FOUND)
66        self.assertRedirects(res, reverse("authentik_core:setup"), fetch_redirect_response=False)
67        # Flow does not exist, hence 503
68        res = self.client.head(reverse("authentik_core:setup"))
69        self.assertEqual(res.status_code, HTTPStatus.OK)
70        res = self.client.get(reverse("authentik_core:setup"))
71        self.assertEqual(res.status_code, HTTPStatus.FOUND)
72        self.assertRedirects(
73            res,
74            reverse("authentik_core:if-flow", kwargs={"flow_slug": "initial-setup"}),
75            fetch_redirect_response=False,
76        )

Test case for when worker comes up, and has created flow

@apply_blueprint('default/flow-oobe.yaml')
@apply_blueprint('system/bootstrap.yaml')
def test_setup_flow_full(self):
 78    @apply_blueprint("default/flow-oobe.yaml")
 79    @apply_blueprint("system/bootstrap.yaml")
 80    def test_setup_flow_full(self):
 81        """Test full setup flow"""
 82        Setup.set(False)
 83
 84        res = self.client.get(reverse("authentik_core:setup"))
 85        self.assertEqual(res.status_code, HTTPStatus.FOUND)
 86        self.assertRedirects(
 87            res,
 88            reverse("authentik_core:if-flow", kwargs={"flow_slug": "initial-setup"}),
 89            fetch_redirect_response=False,
 90        )
 91
 92        res = self.client.get(
 93            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
 94        )
 95        self.assertEqual(res.status_code, HTTPStatus.OK)
 96        self.assertStageResponse(res, component="ak-stage-prompt")
 97
 98        pw = generate_id()
 99        res = self.client.post(
100            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
101            {
102                "email": f"{generate_id()}@t.goauthentik.io",
103                "password": pw,
104                "password_repeat": pw,
105                "component": "ak-stage-prompt",
106            },
107        )
108        self.assertEqual(res.status_code, HTTPStatus.FOUND)
109
110        res = self.client.get(
111            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
112        )
113        self.assertEqual(res.status_code, HTTPStatus.FOUND)
114
115        res = self.client.get(
116            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
117        )
118        self.assertEqual(res.status_code, HTTPStatus.FOUND)
119
120        res = self.client.get(
121            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"}),
122        )
123        self.assertEqual(res.status_code, HTTPStatus.OK)
124
125        self.assertTrue(Setup.get())
126        user = User.objects.get(username="akadmin")
127        self.assertTrue(user.check_password(pw))

Test full setup flow

@patch_flag(Setup, False)
@apply_blueprint('default/flow-oobe.yaml')
@apply_blueprint('system/bootstrap.yaml')
def test_setup_flow_direct(self):
129    @patch_flag(Setup, False)
130    @apply_blueprint("default/flow-oobe.yaml")
131    @apply_blueprint("system/bootstrap.yaml")
132    def test_setup_flow_direct(self):
133        """Test setup flow, directly accessing the flow"""
134        res = self.client.get(
135            reverse("authentik_api:flow-executor", kwargs={"flow_slug": "initial-setup"})
136        )
137        self.assertStageResponse(
138            res,
139            component="ak-stage-access-denied",
140            error_message="Access the authentik setup by navigating to http://testserver/",
141        )

Test setup flow, directly accessing the flow

def test_setup_bootstrap_env(self):
143    def test_setup_bootstrap_env(self):
144        """Test setup with env vars"""
145        User.objects.filter(username="akadmin").delete()
146        Setup.set(False)
147
148        environ["AUTHENTIK_BOOTSTRAP_PASSWORD"] = generate_id()
149        environ["AUTHENTIK_BOOTSTRAP_TOKEN"] = generate_id()
150        pre_startup.send(sender=self)
151        post_startup.send(sender=self)
152
153        self.assertTrue(Setup.get())
154        user = User.objects.get(username="akadmin")
155        self.assertTrue(user.check_password(environ["AUTHENTIK_BOOTSTRAP_PASSWORD"]))
156
157        token = Token.objects.filter(identifier="authentik-bootstrap-token").first()
158        self.assertEqual(token.intent, TokenIntents.INTENT_API)
159        self.assertEqual(token.key, environ["AUTHENTIK_BOOTSTRAP_TOKEN"])

Test setup with env vars

def test_setup_bootstrap_env_password_hash(self):
161    def test_setup_bootstrap_env_password_hash(self):
162        """Test setup with password hash env var"""
163        User.objects.filter(username="akadmin").delete()
164        Setup.set(False)
165
166        password = generate_id()
167        password_hash = make_password(password)
168        environ["AUTHENTIK_BOOTSTRAP_PASSWORD_HASH"] = password_hash
169        pre_startup.send(sender=self)
170        post_startup.send(sender=self)
171
172        self.assertTrue(Setup.get())
173        user = User.objects.get(username="akadmin")
174        self.assertEqual(user.password, password_hash)
175        self.assertTrue(user.check_password(password))

Test setup with password hash env var