add tenant migration, migrate default urls and redirects
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
e39c460e3a
commit
246a6c7384
|
@ -25,7 +25,8 @@ from authentik.flows.planner import (
|
||||||
)
|
)
|
||||||
from authentik.flows.stage import StageView
|
from authentik.flows.stage import StageView
|
||||||
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
|
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
|
||||||
from authentik.lib.utils.urls import redirect_with_qs
|
from authentik.interfaces.models import InterfaceType
|
||||||
|
from authentik.interfaces.views import redirect_to_default_interface
|
||||||
from authentik.lib.views import bad_request_message
|
from authentik.lib.views import bad_request_message
|
||||||
from authentik.policies.denied import AccessDeniedResponse
|
from authentik.policies.denied import AccessDeniedResponse
|
||||||
from authentik.policies.utils import delete_none_keys
|
from authentik.policies.utils import delete_none_keys
|
||||||
|
@ -226,7 +227,7 @@ class SourceFlowManager:
|
||||||
# Ensure redirect is carried through when user was trying to
|
# Ensure redirect is carried through when user was trying to
|
||||||
# authorize application
|
# authorize application
|
||||||
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
||||||
NEXT_ARG_NAME, "authentik_core:if-user"
|
NEXT_ARG_NAME, "authentik_core:root-redirect"
|
||||||
)
|
)
|
||||||
kwargs.update(
|
kwargs.update(
|
||||||
{
|
{
|
||||||
|
@ -253,9 +254,9 @@ class SourceFlowManager:
|
||||||
for stage in stages:
|
for stage in stages:
|
||||||
plan.append_stage(stage)
|
plan.append_stage(stage)
|
||||||
self.request.session[SESSION_KEY_PLAN] = plan
|
self.request.session[SESSION_KEY_PLAN] = plan
|
||||||
return redirect_with_qs(
|
return redirect_to_default_interface(
|
||||||
"authentik_core:if-flow",
|
self.request,
|
||||||
self.request.GET,
|
InterfaceType.FLOW,
|
||||||
flow_slug=flow.slug,
|
flow_slug=flow.slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -299,8 +300,9 @@ class SourceFlowManager:
|
||||||
_("Successfully linked %(source)s!" % {"source": self.source.name}),
|
_("Successfully linked %(source)s!" % {"source": self.source.name}),
|
||||||
)
|
)
|
||||||
return redirect(
|
return redirect(
|
||||||
|
# Not ideal that we don't directly redirect to the configured user interface
|
||||||
reverse(
|
reverse(
|
||||||
"authentik_core:if-user",
|
"authentik_core:root-redirect",
|
||||||
)
|
)
|
||||||
+ f"#/settings;page-{self.source.slug}"
|
+ f"#/settings;page-{self.source.slug}"
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,4 +59,6 @@ class TestImpersonation(TestCase):
|
||||||
self.client.force_login(self.other_user)
|
self.client.force_login(self.other_user)
|
||||||
|
|
||||||
response = self.client.get(reverse("authentik_core:impersonate-end"))
|
response = self.client.get(reverse("authentik_core:impersonate-end"))
|
||||||
self.assertRedirects(response, reverse("authentik_core:if-user"))
|
self.assertRedirects(
|
||||||
|
response, reverse("authentik_interfaces:if", kwargs={"if_name", "user"})
|
||||||
|
)
|
||||||
|
|
|
@ -3,27 +3,30 @@ from channels.auth import AuthMiddleware
|
||||||
from channels.sessions import CookieMiddleware
|
from channels.sessions import CookieMiddleware
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||||
from django.views.generic import RedirectView
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from authentik.core.views import apps, impersonate
|
from authentik.core.views import apps, impersonate
|
||||||
from authentik.core.views.debug import AccessDeniedView
|
from authentik.core.views.debug import AccessDeniedView
|
||||||
from authentik.core.views.session import EndSessionView
|
from authentik.core.views.session import EndSessionView
|
||||||
|
from authentik.interfaces.models import InterfaceType
|
||||||
|
from authentik.interfaces.views import RedirectToInterface
|
||||||
from authentik.root.asgi_middleware import SessionMiddleware
|
from authentik.root.asgi_middleware import SessionMiddleware
|
||||||
from authentik.root.messages.consumer import MessageConsumer
|
from authentik.root.messages.consumer import MessageConsumer
|
||||||
|
|
||||||
|
|
||||||
def placeholder_view(request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def placeholder_view(request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
"""Empty view used as placeholder
|
||||||
|
|
||||||
|
(Mounted to websocket endpoints and used by e2e tests)"""
|
||||||
return HttpResponse(status_code=200)
|
return HttpResponse(status_code=200)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path(
|
||||||
"",
|
"",
|
||||||
login_required(
|
login_required(RedirectToInterface.as_view(type=InterfaceType.USER)),
|
||||||
RedirectView.as_view(pattern_name="authentik_core:if-user", query_string=True)
|
|
||||||
),
|
|
||||||
name="root-redirect",
|
name="root-redirect",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
|
|
|
@ -20,7 +20,8 @@ from authentik.flows.views.executor import (
|
||||||
SESSION_KEY_PLAN,
|
SESSION_KEY_PLAN,
|
||||||
ToDefaultFlow,
|
ToDefaultFlow,
|
||||||
)
|
)
|
||||||
from authentik.lib.utils.urls import redirect_with_qs
|
from authentik.interfaces.models import InterfaceType
|
||||||
|
from authentik.interfaces.views import redirect_to_default_interface
|
||||||
from authentik.stages.consent.stage import (
|
from authentik.stages.consent.stage import (
|
||||||
PLAN_CONTEXT_CONSENT_HEADER,
|
PLAN_CONTEXT_CONSENT_HEADER,
|
||||||
PLAN_CONTEXT_CONSENT_PERMISSIONS,
|
PLAN_CONTEXT_CONSENT_PERMISSIONS,
|
||||||
|
@ -59,7 +60,7 @@ class RedirectToAppLaunch(View):
|
||||||
raise Http404
|
raise Http404
|
||||||
plan.insert_stage(in_memory_stage(RedirectToAppStage))
|
plan.insert_stage(in_memory_stage(RedirectToAppStage))
|
||||||
request.session[SESSION_KEY_PLAN] = plan
|
request.session[SESSION_KEY_PLAN] = plan
|
||||||
return redirect_with_qs("authentik_core:if-flow", request.GET, flow_slug=flow.slug)
|
return redirect_to_default_interface(request, InterfaceType.FLOW, flow_slug=flow.slug)
|
||||||
|
|
||||||
|
|
||||||
class RedirectToAppStage(ChallengeStageView):
|
class RedirectToAppStage(ChallengeStageView):
|
||||||
|
|
|
@ -35,7 +35,7 @@ class ImpersonateInitView(View):
|
||||||
|
|
||||||
Event.new(EventAction.IMPERSONATION_STARTED).from_http(request, user_to_be)
|
Event.new(EventAction.IMPERSONATION_STARTED).from_http(request, user_to_be)
|
||||||
|
|
||||||
return redirect("authentik_core:if-user")
|
return redirect("authentik_core:root-redirect")
|
||||||
|
|
||||||
|
|
||||||
class ImpersonateEndView(View):
|
class ImpersonateEndView(View):
|
||||||
|
@ -48,7 +48,7 @@ class ImpersonateEndView(View):
|
||||||
or SESSION_KEY_IMPERSONATE_ORIGINAL_USER not in request.session
|
or SESSION_KEY_IMPERSONATE_ORIGINAL_USER not in request.session
|
||||||
):
|
):
|
||||||
LOGGER.debug("Can't end impersonation", user=request.user)
|
LOGGER.debug("Can't end impersonation", user=request.user)
|
||||||
return redirect("authentik_core:if-user")
|
return redirect("authentik_core:root-redirect")
|
||||||
|
|
||||||
original_user = request.session[SESSION_KEY_IMPERSONATE_ORIGINAL_USER]
|
original_user = request.session[SESSION_KEY_IMPERSONATE_ORIGINAL_USER]
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,8 @@ from authentik.flows.planner import (
|
||||||
FlowPlanner,
|
FlowPlanner,
|
||||||
)
|
)
|
||||||
from authentik.flows.stage import AccessDeniedChallengeView, StageView
|
from authentik.flows.stage import AccessDeniedChallengeView, StageView
|
||||||
|
from authentik.interfaces.models import InterfaceType
|
||||||
|
from authentik.interfaces.views import redirect_to_default_interface
|
||||||
from authentik.lib.sentry import SentryIgnoredException
|
from authentik.lib.sentry import SentryIgnoredException
|
||||||
from authentik.lib.utils.errors import exception_to_string
|
from authentik.lib.utils.errors import exception_to_string
|
||||||
from authentik.lib.utils.reflection import all_subclasses, class_to_path
|
from authentik.lib.utils.reflection import all_subclasses, class_to_path
|
||||||
|
@ -512,7 +514,7 @@ class ToDefaultFlow(View):
|
||||||
flow_slug=flow.slug,
|
flow_slug=flow.slug,
|
||||||
)
|
)
|
||||||
del self.request.session[SESSION_KEY_PLAN]
|
del self.request.session[SESSION_KEY_PLAN]
|
||||||
return redirect_with_qs("authentik_core:if-flow", request.GET, flow_slug=flow.slug)
|
return redirect_to_default_interface(request, InterfaceType.FLOW, flow_slug=flow.slug)
|
||||||
|
|
||||||
|
|
||||||
def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpResponse:
|
def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpResponse:
|
||||||
|
@ -583,8 +585,8 @@ class ConfigureFlowInitView(LoginRequiredMixin, View):
|
||||||
LOGGER.warning("Flow not applicable to user")
|
LOGGER.warning("Flow not applicable to user")
|
||||||
raise Http404
|
raise Http404
|
||||||
request.session[SESSION_KEY_PLAN] = plan
|
request.session[SESSION_KEY_PLAN] = plan
|
||||||
return redirect_with_qs(
|
return redirect_to_default_interface(
|
||||||
"authentik_core:if-flow",
|
self.request,
|
||||||
self.request.GET,
|
InterfaceType.FLOW,
|
||||||
flow_slug=stage.configure_flow.slug,
|
flow_slug=stage.configure_flow.slug,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
"""interfaces API"""
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
@ -5,6 +6,8 @@ from authentik.interfaces.models import Interface
|
||||||
|
|
||||||
|
|
||||||
class InterfaceSerializer(ModelSerializer):
|
class InterfaceSerializer(ModelSerializer):
|
||||||
|
"""Interface serializer"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -16,5 +19,8 @@ class InterfaceSerializer(ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class InterfaceViewSet(ModelViewSet):
|
class InterfaceViewSet(ModelViewSet):
|
||||||
|
"""Interface serializer"""
|
||||||
|
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
serializer_class = InterfaceSerializer
|
serializer_class = InterfaceSerializer
|
||||||
|
filterset_fields = ["url_name", "type", "template"]
|
||||||
|
|
|
@ -20,7 +20,7 @@ class Migration(migrations.Migration):
|
||||||
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("url_name", models.SlugField()),
|
("url_name", models.SlugField(unique=True)),
|
||||||
(
|
(
|
||||||
"type",
|
"type",
|
||||||
models.TextField(
|
models.TextField(
|
||||||
|
|
|
@ -21,7 +21,7 @@ class Interface(SerializerModel):
|
||||||
|
|
||||||
interface_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
interface_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||||
|
|
||||||
url_name = models.SlugField()
|
url_name = models.SlugField(unique=True)
|
||||||
|
|
||||||
type = models.TextField(choices=InterfaceType.choices)
|
type = models.TextField(choices=InterfaceType.choices)
|
||||||
template = models.TextField()
|
template = models.TextField()
|
||||||
|
|
|
@ -10,7 +10,5 @@ urlpatterns = [
|
||||||
kwargs={"flow_slug": None},
|
kwargs={"flow_slug": None},
|
||||||
name="if",
|
name="if",
|
||||||
),
|
),
|
||||||
path(
|
path("<slug:if_name>/<slug:flow_slug>/", InterfaceView.as_view(), name="if"),
|
||||||
"<slug:if_name>/<slug:flow_slug>/", InterfaceView.as_view(), name="if"
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
"""Interface views"""
|
"""Interface views"""
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
from django.http import Http404, HttpRequest, HttpResponse
|
from django.http import Http404, HttpRequest, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template import Template, TemplateSyntaxError, engines
|
from django.template import Template, TemplateSyntaxError, engines
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.views import View
|
|
||||||
from rest_framework.request import Request
|
|
||||||
from django.views.decorators.cache import cache_page
|
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
|
from django.views.decorators.cache import cache_page
|
||||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||||
|
from rest_framework.request import Request
|
||||||
|
|
||||||
from authentik import get_build_hash
|
from authentik import get_build_hash
|
||||||
from authentik.admin.tasks import LOCAL_VERSION
|
from authentik.admin.tasks import LOCAL_VERSION
|
||||||
from authentik.api.v3.config import ConfigView
|
from authentik.api.v3.config import ConfigView
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
from authentik.interfaces.models import Interface, InterfaceType
|
from authentik.interfaces.models import Interface, InterfaceType
|
||||||
|
from authentik.lib.utils.urls import redirect_with_qs
|
||||||
from authentik.tenants.api import CurrentTenantSerializer
|
from authentik.tenants.api import CurrentTenantSerializer
|
||||||
|
from authentik.tenants.models import Tenant
|
||||||
|
|
||||||
|
|
||||||
def template_from_string(template_string: str) -> Template:
|
def template_from_string(template_string: str) -> Template:
|
||||||
|
@ -32,6 +34,38 @@ def template_from_string(template_string: str) -> Template:
|
||||||
raise TemplateSyntaxError(template_string, chain=chain)
|
raise TemplateSyntaxError(template_string, chain=chain)
|
||||||
|
|
||||||
|
|
||||||
|
def redirect_to_default_interface(request: HttpRequest, interface_type: InterfaceType, **kwargs):
|
||||||
|
"""Shortcut to inline redirect to default interface,
|
||||||
|
keeping GET parameters of the passed request"""
|
||||||
|
return RedirectToInterface.as_view(type=interface_type)(request, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RedirectToInterface(View):
|
||||||
|
"""Redirect to tenant's configured view for specified type"""
|
||||||
|
|
||||||
|
type: Optional[InterfaceType] = None
|
||||||
|
|
||||||
|
def dispatch(self, request: HttpRequest, **kwargs: Any) -> HttpResponse:
|
||||||
|
tenant: Tenant = request.tenant
|
||||||
|
interface: Interface = None
|
||||||
|
|
||||||
|
if self.type == InterfaceType.USER:
|
||||||
|
interface = tenant.interface_user
|
||||||
|
if self.type == InterfaceType.ADMIN:
|
||||||
|
interface = tenant.interface_admin
|
||||||
|
if self.type == InterfaceType.FLOW:
|
||||||
|
interface = tenant.interface_flow
|
||||||
|
|
||||||
|
if not interface:
|
||||||
|
raise Http404()
|
||||||
|
return redirect_with_qs(
|
||||||
|
"authentik_interfaces:if",
|
||||||
|
self.request.GET,
|
||||||
|
if_name=interface.url_name,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(ensure_csrf_cookie, name="dispatch")
|
@method_decorator(ensure_csrf_cookie, name="dispatch")
|
||||||
@method_decorator(cache_page(60 * 10), name="dispatch")
|
@method_decorator(cache_page(60 * 10), name="dispatch")
|
||||||
class InterfaceView(View):
|
class InterfaceView(View):
|
||||||
|
|
|
@ -22,4 +22,4 @@ class UseTokenView(View):
|
||||||
login(request, token.user, backend=BACKEND_INBUILT)
|
login(request, token.user, backend=BACKEND_INBUILT)
|
||||||
token.delete()
|
token.delete()
|
||||||
messages.warning(request, _("Used recovery-link to authenticate."))
|
messages.warning(request, _("Used recovery-link to authenticate."))
|
||||||
return redirect("authentik_core:if-user")
|
return redirect("authentik_core:root-redirect")
|
||||||
|
|
|
@ -72,7 +72,7 @@ class InitiateView(View):
|
||||||
# Ensure redirect is carried through when user was trying to
|
# Ensure redirect is carried through when user was trying to
|
||||||
# authorize application
|
# authorize application
|
||||||
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
||||||
NEXT_ARG_NAME, "authentik_core:if-user"
|
NEXT_ARG_NAME, "authentik_core:root-redirect"
|
||||||
)
|
)
|
||||||
kwargs.update(
|
kwargs.update(
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,6 +54,9 @@ class TenantSerializer(ModelSerializer):
|
||||||
"flow_unenrollment",
|
"flow_unenrollment",
|
||||||
"flow_user_settings",
|
"flow_user_settings",
|
||||||
"flow_device_code",
|
"flow_device_code",
|
||||||
|
"interface_admin",
|
||||||
|
"interface_user",
|
||||||
|
"interface_flow",
|
||||||
"event_retention",
|
"event_retention",
|
||||||
"web_certificate",
|
"web_certificate",
|
||||||
"attributes",
|
"attributes",
|
||||||
|
@ -120,6 +123,9 @@ class TenantViewSet(UsedByMixin, ModelViewSet):
|
||||||
"flow_unenrollment",
|
"flow_unenrollment",
|
||||||
"flow_user_settings",
|
"flow_user_settings",
|
||||||
"flow_device_code",
|
"flow_device_code",
|
||||||
|
"interface_admin",
|
||||||
|
"interface_user",
|
||||||
|
"interface_flow",
|
||||||
"event_retention",
|
"event_retention",
|
||||||
"web_certificate",
|
"web_certificate",
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Generated by Django 4.1.7 on 2023-02-21 14:18
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.apps.registry import Apps
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_set_default(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
Tenant = apps.get_model("authentik_tenants", "tenant")
|
||||||
|
Interface = apps.get_model("authentik_interfaces", "Interface")
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
from authentik.blueprints.models import BlueprintInstance
|
||||||
|
from authentik.blueprints.v1.importer import Importer
|
||||||
|
from authentik.blueprints.v1.tasks import blueprints_discover
|
||||||
|
from authentik.interfaces.models import InterfaceType
|
||||||
|
|
||||||
|
# If we don't have any tenants yet, we don't need wait for the default interface blueprint
|
||||||
|
if not Tenant.objects.using(db_alias).exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
interface_blueprint = BlueprintInstance.objects.filter(path="system/interfaces.yaml").first()
|
||||||
|
if not interface_blueprint:
|
||||||
|
blueprints_discover.delay().get()
|
||||||
|
interface_blueprint = BlueprintInstance.objects.filter(
|
||||||
|
path="system/interfaces.yaml"
|
||||||
|
).first()
|
||||||
|
if not interface_blueprint:
|
||||||
|
raise ValueError("Failed to apply system/interfaces.yaml blueprint")
|
||||||
|
Importer(interface_blueprint.retrieve()).apply()
|
||||||
|
|
||||||
|
for tenant in Tenant.objects.using(db_alias).all():
|
||||||
|
tenant.interface_admin = Interface.objects.filter(type=InterfaceType.ADMIN).first()
|
||||||
|
tenant.interface_user = Interface.objects.filter(type=InterfaceType.USER).first()
|
||||||
|
tenant.interface_flow = Interface.objects.filter(type=InterfaceType.FLOW).first()
|
||||||
|
tenant.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("authentik_interfaces", "0001_initial"),
|
||||||
|
("authentik_tenants", "0004_tenant_flow_device_code"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="tenant",
|
||||||
|
name="interface_admin",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="tenant_admin",
|
||||||
|
to="authentik_interfaces.interface",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="tenant",
|
||||||
|
name="interface_flow",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="tenant_flow",
|
||||||
|
to="authentik_interfaces.interface",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="tenant",
|
||||||
|
name="interface_user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="tenant_user",
|
||||||
|
to="authentik_interfaces.interface",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_set_default),
|
||||||
|
]
|
|
@ -8,6 +8,7 @@ from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
|
from authentik.interfaces.models import Interface
|
||||||
from authentik.lib.models import SerializerModel
|
from authentik.lib.models import SerializerModel
|
||||||
from authentik.lib.utils.time import timedelta_string_validator
|
from authentik.lib.utils.time import timedelta_string_validator
|
||||||
|
|
||||||
|
@ -51,6 +52,25 @@ class Tenant(SerializerModel):
|
||||||
Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_device_code"
|
Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_device_code"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface_flow = models.ForeignKey(
|
||||||
|
Interface,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
related_name="tenant_flow",
|
||||||
|
)
|
||||||
|
interface_user = models.ForeignKey(
|
||||||
|
Interface,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
related_name="tenant_user",
|
||||||
|
)
|
||||||
|
interface_admin = models.ForeignKey(
|
||||||
|
Interface,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
related_name="tenant_admin",
|
||||||
|
)
|
||||||
|
|
||||||
event_retention = models.TextField(
|
event_retention = models.TextField(
|
||||||
default="days=365",
|
default="days=365",
|
||||||
validators=[timedelta_string_validator],
|
validators=[timedelta_string_validator],
|
||||||
|
|
|
@ -2,6 +2,11 @@ metadata:
|
||||||
name: Default - Tenant
|
name: Default - Tenant
|
||||||
version: 1
|
version: 1
|
||||||
entries:
|
entries:
|
||||||
|
- model: authentik_blueprints.metaapplyblueprint
|
||||||
|
attrs:
|
||||||
|
identifiers:
|
||||||
|
name: System - Interfaces
|
||||||
|
required: false
|
||||||
- model: authentik_blueprints.metaapplyblueprint
|
- model: authentik_blueprints.metaapplyblueprint
|
||||||
attrs:
|
attrs:
|
||||||
identifiers:
|
identifiers:
|
||||||
|
@ -21,6 +26,9 @@ entries:
|
||||||
flow_authentication: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
flow_authentication: !Find [authentik_flows.flow, [slug, default-authentication-flow]]
|
||||||
flow_invalidation: !Find [authentik_flows.flow, [slug, default-invalidation-flow]]
|
flow_invalidation: !Find [authentik_flows.flow, [slug, default-invalidation-flow]]
|
||||||
flow_user_settings: !Find [authentik_flows.flow, [slug, default-user-settings-flow]]
|
flow_user_settings: !Find [authentik_flows.flow, [slug, default-user-settings-flow]]
|
||||||
|
interface_admin: !Find [authentik_interfaces.Interface, [type, admin]]
|
||||||
|
interface_user: !Find [authentik_interfaces.Interface, [type, user]]
|
||||||
|
interface_flow: !Find [authentik_interfaces.Interface, [type, flow]]
|
||||||
identifiers:
|
identifiers:
|
||||||
domain: authentik-default
|
domain: authentik-default
|
||||||
default: True
|
default: True
|
||||||
|
|
Reference in a new issue