admin: finalise migration

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-04-03 01:20:20 +02:00
parent d7698343ae
commit aaebd01058
16 changed files with 63 additions and 369 deletions

View File

@ -7,5 +7,4 @@ class AuthentikAdminConfig(AppConfig):
name = "authentik.admin"
label = "authentik_admin"
mountpoint = "administration/"
verbose_name = "authentik Admin"

View File

@ -1,18 +0,0 @@
{% extends base_template|default:"generic/form.html" %}
{% load authentik_utils %}
{% load i18n %}
{% block above_form %}
<h1>
{% blocktrans with type=form|form_verbose_name %}
Create {{ type }}
{% endblocktrans %}
</h1>
{% endblock %}
{% block action %}
{% blocktrans with type=form|form_verbose_name %}
Create {{ type }}
{% endblocktrans %}
{% endblock %}

View File

@ -1,38 +0,0 @@
{% load i18n %}
{% load authentik_utils %}
{% load static %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
{% block above_form %}
{% endblock %}
</div>
</section>
<section class="pf-c-page__main-section">
<div class="pf-l-stack">
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__body">
<form id="main-form" action="" method="post" class="pf-c-form pf-m-horizontal" enctype="multipart/form-data">
{% include 'partials/form_horizontal.html' with form=form %}
{% block beneath_form %}
{% endblock %}
</form>
</div>
</div>
</div>
</div>
</section>
<footer class="pf-c-modal-box__footer">
<ak-spinner-button form="main-form">
{% block action %}{% endblock %}
</ak-spinner-button>&nbsp;
<a class="pf-c-button pf-m-secondary" href="#/">{% trans "Cancel" %}</a>
</footer>
{% endblock %}
{% block scripts %}
{{ block.super }}
{{ form.media.js }}
{% endblock %}

View File

@ -1,18 +0,0 @@
{% extends base_template|default:"generic/form.html" %}
{% load authentik_utils %}
{% load i18n %}
{% block above_form %}
<h1>
{% blocktrans with type=form|form_verbose_name|title inst=form.instance %}
Update {{ inst }}
{% endblocktrans %}
</h1>
{% endblock %}
{% block action %}
{% blocktrans with type=form|form_verbose_name %}
Update {{ type }}
{% endblocktrans %}
{% endblock %}

View File

@ -1,14 +0,0 @@
"""authentik URL Configuration"""
from django.urls import path
from authentik.admin.views import stages
urlpatterns = [
# Stages
path("stages/create/", stages.StageCreateView.as_view(), name="stage-create"),
path(
"stages/<uuid:pk>/update/",
stages.StageUpdateView.as_view(),
name="stage-update",
),
]

View File

@ -1,43 +0,0 @@
"""authentik Stage administration"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.views.utils import InheritanceCreateView, InheritanceUpdateView
from authentik.flows.models import Stage
class StageCreateView(
SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
InheritanceCreateView,
):
"""Create new Stage"""
model = Stage
template_name = "generic/create.html"
permission_required = "authentik_flows.add_stage"
success_url = reverse_lazy("authentik_core:if-admin")
success_message = _("Successfully created Stage")
class StageUpdateView(
SuccessMessageMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
InheritanceUpdateView,
):
"""Update stage"""
model = Stage
permission_required = "authentik_flows.update_application"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_core:if-admin")
success_message = _("Successfully updated Stage")

View File

@ -1,50 +0,0 @@
"""authentik admin util views"""
from typing import Any
from django.http import Http404
from django.views.generic import UpdateView
from authentik.lib.utils.reflection import all_subclasses
from authentik.lib.views import CreateAssignPermView
class InheritanceCreateView(CreateAssignPermView):
"""CreateView for objects using InheritanceManager"""
def get_form_class(self):
provider_type = self.request.GET.get("type")
try:
model = next(
x for x in all_subclasses(self.model) if x.__name__ == provider_type
)
except StopIteration as exc:
raise Http404 from exc
return model().form
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs = super().get_context_data(**kwargs)
form_cls = self.get_form_class()
if hasattr(form_cls, "template_name"):
kwargs["base_template"] = form_cls.template_name
return kwargs
class InheritanceUpdateView(UpdateView):
"""UpdateView for objects using InheritanceManager"""
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
kwargs = super().get_context_data(**kwargs)
form_cls = self.get_form_class()
if hasattr(form_cls, "template_name"):
kwargs["base_template"] = form_cls.template_name
return kwargs
def get_form_class(self):
return self.get_object().form
def get_object(self, queryset=None):
return (
self.model.objects.filter(pk=self.kwargs.get("pk"))
.select_subclasses()
.first()
)

View File

@ -1,115 +0,0 @@
{% load authentik_utils %}
{% load i18n %}
{% csrf_token %}
{% for field in form %}
{% if field.field.widget|fieldtype == 'HiddenInput' %}
{{ field }}
{% else %}
<div class="pf-c-form__group {% if field.errors %} has-error {% endif %}">
{% if field.field.widget|fieldtype == 'RadioSelect' %}
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="{{ field.name }}-{{ forloop.counter0 }}">
<span class="pf-c-form__label-text">{{ field.label }}</span>
{% if field.field.required %}
<span class="pf-c-form__label-required" aria-hidden="true">&#42;</span>
{% endif %}
</label>
</div>
<div class="pf-c-form__group-control">
{% for c in field %}
<div class="pf-c-radio">
<input class="pf-c-radio__input"
type="radio" id="{{ field.name }}-{{ forloop.counter0 }}"
name="{% if wizard %}{{ wizard.steps.current }}-{% endif %}{{ field.name }}"
value="{{ c.data.value }}"
{% if c.data.selected %} checked {% endif %}/>
<label class="pf-c-radio__label" for="{{ field.name }}-{{ forloop.counter0 }}">{{ c.choice_label }}</label>
</div>
{% endfor %}
{% if field.help_text %}
<p class="pf-c-form__helper-text">{{ field.help_text }}</p>
{% endif %}
</div>
{% elif field.field.widget|fieldtype == 'Select' or field.field.widget|fieldtype == "SelectMultiple" %}
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="{{ field.name }}-{{ forloop.counter0 }}">
<span class="pf-c-form__label-text">{{ field.label }}</span>
{% if field.field.required %}
<span class="pf-c-form__label-required" aria-hidden="true">&#42;</span>
{% endif %}
</label>
</div>
<div class="pf-c-form__group-control">
<div class="pf-c-form__horizontal-group">
{{ field|css_class:"pf-c-form-control" }}
{% if field.help_text %}
<p class="pf-c-form__helper-text">{{ field.help_text|safe }}</p>
{% endif %}
{% if field.field.widget|fieldtype == 'SelectMultiple' %}
<p class="pf-c-form__helper-text">{% trans 'Hold control/command to select multiple items.' %}</p>
{% endif %}
</div>
</div>
{% elif field.field.widget|fieldtype == 'CheckboxInput' %}
<div class="pf-c-form__group-control">
<div class="pf-c-form__horizontal-group">
<div class="pf-c-check">
{{ field|css_class:"pf-c-check__input" }}
<label class="pf-c-check__label" for="{{ field.name }}-{{ forloop.counter0 }}">{{ field.label }}</label>
</div>
{% if field.help_text %}
<p class="pf-c-form__helper-text">{{ field.help_text|safe }}</p>
{% endif %}
</div>
</div>
{% elif field.field.widget|fieldtype == "FileInput" %}
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="{{ field.name }}-{{ forloop.counter0 }}">
<span class="pf-c-form__label-text">{{ field.label }}</span>
{% if field.field.required %}
<span class="pf-c-form__label-required" aria-hidden="true">&#42;</span>
{% endif %}
</label>
</div>
<div class="pf-c-form__group-control">
<div class="c-form__horizontal-group">
{{ field|css_class:"pf-c-form-control" }}
{% if field.help_text %}
<p class="pf-c-form__helper-text">{{ field.help_text|safe }}</p>
{% endif %}
{% if field.value %}
<a target="_blank" href="{{ field.value.url }}" class="pf-c-form__helper-text">
{% blocktrans with current=field.value %}
Currently set to {{current}}.
{% endblocktrans %}
</a>
{% endif %}
</div>
</div>
{% else %}
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="{{ field.name }}-{{ forloop.counter0 }}">
<span class="pf-c-form__label-text">{{ field.label }}</span>
{% if field.field.required %}
<span class="pf-c-form__label-required" aria-hidden="true">&#42;</span>
{% endif %}
</label>
</div>
<div class="pf-c-form__group-control">
<div class="c-form__horizontal-group">
{{ field|css_class:'pf-c-form-control' }}
{% if field.help_text %}
<p class="pf-c-form__helper-text">{{ field.help_text|safe }}</p>
{% endif %}
</div>
</div>
{% endif %}
{% for error in field.errors %}
<p class="pf-c-form__helper-text pf-m-error">
{{ error }}
</p>
{% endfor %}
</div>
{% endif %}
{% endfor %}

View File

@ -1,7 +1,6 @@
"""Flow Stage API Views"""
from typing import Iterable
from django.urls import reverse
from drf_yasg.utils import swagger_auto_schema
from rest_framework import mixins
from rest_framework.decorators import action
@ -70,8 +69,7 @@ class StageViewSet(
{
"name": verbose_name(subclass),
"description": subclass.__doc__,
"component": reverse("authentik_admin:stage-create")
+ f"?type={subclass.__name__}",
"component": subclass().component,
}
)
data = sorted(data, key=lambda x: x["name"])

View File

@ -3,7 +3,6 @@ from typing import TYPE_CHECKING, Optional, Type
from uuid import uuid4
from django.db import models
from django.forms import ModelForm
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager
@ -60,8 +59,8 @@ class Stage(SerializerModel):
raise NotImplementedError
@property
def form(self) -> Type[ModelForm]:
"""Return Form class used to edit this object"""
def component(self) -> str:
"""Return component used to edit this object"""
raise NotImplementedError
@property

View File

@ -1,31 +0,0 @@
"""flow model tests"""
from typing import Callable, Type
from django.forms import ModelForm
from django.test import TestCase
from authentik.flows.models import Stage
from authentik.flows.stage import StageView
class TestStageProperties(TestCase):
"""Generic model properties tests"""
def stage_tester_factory(model: Type[Stage]) -> Callable:
"""Test a form"""
def tester(self: TestStageProperties):
model_inst = model()
self.assertTrue(issubclass(model_inst.form, ModelForm))
self.assertTrue(issubclass(model_inst.type, StageView))
return tester
for stage_type in Stage.__subclasses__():
setattr(
TestStageProperties,
f"test_stage_{stage_type.__name__}",
stage_tester_factory(stage_type),
)

View File

@ -9,7 +9,6 @@ from authentik.flows.markers import StageMarker
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.flows.planner import FlowPlan
from authentik.flows.views import SESSION_KEY_PLAN
from authentik.stages.deny.forms import DenyStageForm
from authentik.stages.deny.models import DenyStage
@ -52,8 +51,3 @@ class TestUserDenyStage(TestCase):
"type": ChallengeTypes.NATIVE.value,
},
)
def test_form(self):
"""Test Form"""
data = {"name": "test"}
self.assertEqual(DenyStageForm(data).is_valid(), True)

View File

@ -5,7 +5,6 @@ from django.utils.encoding import force_str
from authentik.core.models import User
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.stages.dummy.forms import DummyStageForm
from authentik.stages.dummy.models import DummyStage
@ -49,8 +48,3 @@ class TestDummyStage(TestCase):
force_str(response.content),
{"to": reverse("authentik_core:root-redirect"), "type": "redirect"},
)
def test_form(self):
"""Test Form"""
data = {"name": "test"}
self.assertEqual(DummyStageForm(data).is_valid(), True)

View File

@ -1,15 +1,3 @@
export class AdminURLManager {
static policies(rest: string): string {
return `/administration/policies/${rest}`;
}
static stages(rest: string): string {
return `/administration/stages/${rest}`;
}
}
export class AppURLManager {
static sourceSAML(slug: string, rest: string): string {

View File

@ -4,15 +4,34 @@ import { AKResponse } from "../../api/Client";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown";
import "../../elements/forms/DeleteForm";
import "../../elements/forms/ProxyForm";
import "../../elements/forms/ModalForm";
import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants";
import { Stage, StagesApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
import { ifDefined } from "lit-html/directives/if-defined";
import "./pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts";
import "./pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts";
import "./pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts";
import "./pages/stages/authenticator_webauthn/AuthenticateWebAuthnStageForm.ts";
import "./pages/stages/captcha/CaptchaStageForm.ts";
import "./pages/stages/consent/ConsentStageForm.ts";
import "./pages/stages/deny/DenyStageForm.ts";
import "./pages/stages/dummy/DummyStageForm.ts";
import "./pages/stages/email/EmailStageForm.ts";
import "./pages/stages/identification/IdentificationStageForm.ts";
import "./pages/stages/invitation/InvitationStageForm.ts";
import "./pages/stages/password/PasswordStageForm.ts";
import "./pages/stages/prompt/PromptStageForm.ts";
import "./pages/stages/user_delete/UserDeleteStageForm.ts";
import "./pages/stages/user_login/UserLoginStageForm.ts";
import "./pages/stages/user_logout/UserLogoutStageForm.ts";
import "./pages/stages/user_write/UserWriteStageForm.ts";
@customElement("ak-stage-list")
export class StageListPage extends TablePage<Stage> {
@ -61,12 +80,33 @@ export class StageListPage extends TablePage<Stage> {
</a>`;
})}`,
html`
<ak-modal-button href="${AdminURLManager.stages(`${item.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
<ak-forms-modal>
<span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext(`Update ${item.verboseName}`)}
</span>
<ak-proxy-form
slot="form"
.args=${{
"stageUUID": item.pk
}}
type=${ifDefined(item.objectType)}
.typeMap=${{
"dummy": "ak-policy-dummy-form",
"eventmatcher": "ak-policy-event-matcher-form",
"expression": "ak-policy-expression-form",
"passwordexpiry": "ak-policy-password-expiry-form",
"haveibeenpwend": "ak-policy-hibp-form",
"password": "ak-policy-password-form",
"reputation": "ak-policy-reputation-form",
}}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</button>
</ak-forms-modal>
<ak-forms-delete
.obj=${item}
objectLabel=${gettext("Group")}
@ -93,12 +133,21 @@ export class StageListPage extends TablePage<Stage> {
${until(new StagesApi(DEFAULT_CONFIG).stagesAllTypes().then((types) => {
return types.map((type) => {
return html`<li>
<ak-modal-button href="${type.component}">
<button slot="trigger" class="pf-c-dropdown__menu-item">${type.name}<br>
<ak-forms-modal>
<span slot="submit">
${gettext("Create")}
</span>
<span slot="header">
${gettext(`Create ${type.name}`)}
</span>
<ak-proxy-form
slot="form"
type=${type.component}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}