authentik.core.setup.views

 1from functools import lru_cache
 2from http import HTTPMethod, HTTPStatus
 3
 4from django.contrib.staticfiles import finders
 5from django.db import transaction
 6from django.http import HttpRequest, HttpResponse
 7from django.shortcuts import redirect
 8from django.urls import reverse
 9from django.views import View
10from structlog.stdlib import get_logger
11
12from authentik.blueprints.models import BlueprintInstance
13from authentik.core.apps import Setup
14from authentik.flows.models import Flow, FlowAuthenticationRequirement, in_memory_stage
15from authentik.flows.planner import FlowPlanner
16from authentik.flows.stage import StageView
17
18LOGGER = get_logger()
19FLOW_CONTEXT_START_BY = "goauthentik.io/core/setup/started-by"
20
21
22@lru_cache
23def read_static(path: str) -> str | None:
24    result = finders.find(path)
25    if not result:
26        return None
27    with open(result, encoding="utf8") as _file:
28        return _file.read()
29
30
31class SetupView(View):
32
33    setup_flow_slug = "initial-setup"
34
35    def dispatch(self, request: HttpRequest, *args, **kwargs):
36        if request.method != HTTPMethod.HEAD and Setup.get():
37            return redirect(reverse("authentik_core:root-redirect"))
38        return super().dispatch(request, *args, **kwargs)
39
40    def head(self, request: HttpRequest, *args, **kwargs):
41        if Setup.get():
42            return HttpResponse(status=HTTPStatus.SERVICE_UNAVAILABLE)
43        if not Flow.objects.filter(slug=self.setup_flow_slug).exists():
44            return HttpResponse(status=HTTPStatus.SERVICE_UNAVAILABLE)
45        return HttpResponse(status=HTTPStatus.OK)
46
47    def get(self, request: HttpRequest):
48        flow = Flow.objects.filter(slug=self.setup_flow_slug).first()
49        if not flow:
50            LOGGER.info("Setup flow does not exist yet, waiting for worker to finish")
51            return HttpResponse(
52                read_static("dist/standalone/loading/startup.html"),
53                status=HTTPStatus.SERVICE_UNAVAILABLE,
54            )
55        planner = FlowPlanner(flow)
56        plan = planner.plan(request, {FLOW_CONTEXT_START_BY: "setup"})
57        plan.append_stage(in_memory_stage(PostSetupStageView))
58        return plan.to_redirect(request, flow)
59
60
61class PostSetupStageView(StageView):
62    """Run post-setup tasks"""
63
64    def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
65        """Wrapper when this stage gets hit with a post request"""
66        return self.get(request, *args, **kwargs)
67
68    def get(self, requeset: HttpRequest, *args, **kwargs):
69        with transaction.atomic():
70            # Remember we're setup
71            Setup.set(True)
72            # Disable OOBE Blueprints
73            BlueprintInstance.objects.filter(
74                **{"metadata__labels__blueprints.goauthentik.io/system-oobe": "true"}
75            ).update(enabled=False)
76            # Make flow inaccessible
77            Flow.objects.filter(slug="initial-setup").update(
78                authentication=FlowAuthenticationRequirement.REQUIRE_SUPERUSER
79            )
80        return self.executor.stage_ok()
LOGGER = <BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=())>
FLOW_CONTEXT_START_BY = 'goauthentik.io/core/setup/started-by'
@lru_cache
def read_static(path: str) -> str | None:
23@lru_cache
24def read_static(path: str) -> str | None:
25    result = finders.find(path)
26    if not result:
27        return None
28    with open(result, encoding="utf8") as _file:
29        return _file.read()
class SetupView(django.views.generic.base.View):
32class SetupView(View):
33
34    setup_flow_slug = "initial-setup"
35
36    def dispatch(self, request: HttpRequest, *args, **kwargs):
37        if request.method != HTTPMethod.HEAD and Setup.get():
38            return redirect(reverse("authentik_core:root-redirect"))
39        return super().dispatch(request, *args, **kwargs)
40
41    def head(self, request: HttpRequest, *args, **kwargs):
42        if Setup.get():
43            return HttpResponse(status=HTTPStatus.SERVICE_UNAVAILABLE)
44        if not Flow.objects.filter(slug=self.setup_flow_slug).exists():
45            return HttpResponse(status=HTTPStatus.SERVICE_UNAVAILABLE)
46        return HttpResponse(status=HTTPStatus.OK)
47
48    def get(self, request: HttpRequest):
49        flow = Flow.objects.filter(slug=self.setup_flow_slug).first()
50        if not flow:
51            LOGGER.info("Setup flow does not exist yet, waiting for worker to finish")
52            return HttpResponse(
53                read_static("dist/standalone/loading/startup.html"),
54                status=HTTPStatus.SERVICE_UNAVAILABLE,
55            )
56        planner = FlowPlanner(flow)
57        plan = planner.plan(request, {FLOW_CONTEXT_START_BY: "setup"})
58        plan.append_stage(in_memory_stage(PostSetupStageView))
59        return plan.to_redirect(request, flow)

Intentionally simple parent class for all views. Only implements dispatch-by-method and simple sanity checking.

setup_flow_slug = 'initial-setup'
def dispatch(self, request: django.http.request.HttpRequest, *args, **kwargs):
36    def dispatch(self, request: HttpRequest, *args, **kwargs):
37        if request.method != HTTPMethod.HEAD and Setup.get():
38            return redirect(reverse("authentik_core:root-redirect"))
39        return super().dispatch(request, *args, **kwargs)
def head(self, request: django.http.request.HttpRequest, *args, **kwargs):
41    def head(self, request: HttpRequest, *args, **kwargs):
42        if Setup.get():
43            return HttpResponse(status=HTTPStatus.SERVICE_UNAVAILABLE)
44        if not Flow.objects.filter(slug=self.setup_flow_slug).exists():
45            return HttpResponse(status=HTTPStatus.SERVICE_UNAVAILABLE)
46        return HttpResponse(status=HTTPStatus.OK)
def get(self, request: django.http.request.HttpRequest):
48    def get(self, request: HttpRequest):
49        flow = Flow.objects.filter(slug=self.setup_flow_slug).first()
50        if not flow:
51            LOGGER.info("Setup flow does not exist yet, waiting for worker to finish")
52            return HttpResponse(
53                read_static("dist/standalone/loading/startup.html"),
54                status=HTTPStatus.SERVICE_UNAVAILABLE,
55            )
56        planner = FlowPlanner(flow)
57        plan = planner.plan(request, {FLOW_CONTEXT_START_BY: "setup"})
58        plan.append_stage(in_memory_stage(PostSetupStageView))
59        return plan.to_redirect(request, flow)
class PostSetupStageView(authentik.flows.stage.StageView):
62class PostSetupStageView(StageView):
63    """Run post-setup tasks"""
64
65    def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
66        """Wrapper when this stage gets hit with a post request"""
67        return self.get(request, *args, **kwargs)
68
69    def get(self, requeset: HttpRequest, *args, **kwargs):
70        with transaction.atomic():
71            # Remember we're setup
72            Setup.set(True)
73            # Disable OOBE Blueprints
74            BlueprintInstance.objects.filter(
75                **{"metadata__labels__blueprints.goauthentik.io/system-oobe": "true"}
76            ).update(enabled=False)
77            # Make flow inaccessible
78            Flow.objects.filter(slug="initial-setup").update(
79                authentication=FlowAuthenticationRequirement.REQUIRE_SUPERUSER
80            )
81        return self.executor.stage_ok()

Run post-setup tasks

def post( self, request: django.http.request.HttpRequest, *args, **kwargs) -> django.http.response.HttpResponse:
65    def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
66        """Wrapper when this stage gets hit with a post request"""
67        return self.get(request, *args, **kwargs)

Wrapper when this stage gets hit with a post request

def get(self, requeset: django.http.request.HttpRequest, *args, **kwargs):
69    def get(self, requeset: HttpRequest, *args, **kwargs):
70        with transaction.atomic():
71            # Remember we're setup
72            Setup.set(True)
73            # Disable OOBE Blueprints
74            BlueprintInstance.objects.filter(
75                **{"metadata__labels__blueprints.goauthentik.io/system-oobe": "true"}
76            ).update(enabled=False)
77            # Make flow inaccessible
78            Flow.objects.filter(slug="initial-setup").update(
79                authentication=FlowAuthenticationRequirement.REQUIRE_SUPERUSER
80            )
81        return self.executor.stage_ok()