web/admin: migrate user forms to web

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-03-29 16:16:27 +02:00
parent fac8d53163
commit 526af26536
11 changed files with 145 additions and 144 deletions

View File

@ -1,22 +0,0 @@
"""authentik administrative user forms"""
from django import forms
from authentik.admin.fields import CodeMirrorWidget, YAMLField
from authentik.core.models import User
class UserForm(forms.ModelForm):
"""Update User Details"""
class Meta:
model = User
fields = ["username", "name", "email", "is_active", "attributes"]
widgets = {
"name": forms.TextInput,
"attributes": CodeMirrorWidget,
}
field_classes = {
"attributes": YAMLField,
}

View File

@ -18,7 +18,6 @@ from authentik.admin.views import (
stages_bindings,
stages_invitations,
stages_prompts,
users,
)
from authentik.providers.saml.views.metadata import MetadataImportView
@ -152,14 +151,6 @@ urlpatterns = [
property_mappings.PropertyMappingTestView.as_view(),
name="property-mapping-test",
),
# Users
path("users/create/", users.UserCreateView.as_view(), name="user-create"),
path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"),
path(
"users/<int:pk>/reset/",
users.UserPasswordResetView.as_view(),
name="user-password-reset",
),
# Certificate-Key Pairs
path(
"crypto/certificates/create/",

View File

@ -1,74 +0,0 @@
"""authentik User administration"""
from django.contrib import messages
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.http import HttpRequest, HttpResponse
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.utils.http import urlencode
from django.utils.translation import gettext as _
from django.views.generic import DetailView, UpdateView
from guardian.mixins import PermissionRequiredMixin
from authentik.admin.forms.users import UserForm
from authentik.core.models import Token, User
from authentik.lib.views import CreateAssignPermView
class UserCreateView(
SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create user"""
model = User
form_class = UserForm
permission_required = "authentik_core.add_user"
template_name = "generic/create.html"
success_url = reverse_lazy("authentik_core:if-admin")
success_message = _("Successfully created User")
class UserUpdateView(
SuccessMessageMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
):
"""Update user"""
model = User
form_class = UserForm
permission_required = "authentik_core.change_user"
# By default the object's name is user which is used by other checks
context_object_name = "object"
template_name = "generic/update.html"
success_url = reverse_lazy("authentik_core:if-admin")
success_message = _("Successfully updated User")
class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
"""Get Password reset link for user"""
model = User
permission_required = "authentik_core.reset_user_password"
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Create token for user and return link"""
super().get(request, *args, **kwargs)
token, __ = Token.objects.get_or_create(
identifier="password-reset-temp", user=self.object
)
querystring = urlencode({"token": token.key})
link = request.build_absolute_uri(
reverse_lazy("authentik_flows:default-recovery") + f"?{querystring}"
)
messages.success(request, _("Password reset link: %(link)s" % {"link": link}))
return redirect("/")

View File

@ -1,11 +1,8 @@
"""authentik admin util views"""
from typing import Any
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.http import Http404
from django.urls import reverse_lazy
from django.views.generic import DeleteView, UpdateView
from django.views.generic import UpdateView
from authentik.lib.utils.reflection import all_subclasses
from authentik.lib.views import CreateAssignPermView

View File

@ -68,10 +68,6 @@ export class AdminURLManager {
return `/administration/events/transports/${rest}`;
}
static users(rest: string): string {
return `/administration/users/${rest}`;
}
}
export class UserURLManager {

View File

@ -23,8 +23,7 @@ export class ActionButton extends SpinnerButton {
this.setLoading();
this.apiRequest().then(() => {
this.setDone(SUCCESS_CLASS);
})
.catch((e: Error | Response) => {
}).catch((e: Error | Response) => {
if (e instanceof Error) {
showMessage({
level: MessageLevel.error,

View File

@ -18,9 +18,9 @@ export class GroupForm extends Form<Group> {
getSuccessMessage(): string {
if (this.group) {
return gettext("Successfully updated group");
return gettext("Successfully updated group.");
} else {
return gettext("Successfully created group");
return gettext("Successfully created group.");
}
}

View File

@ -66,7 +66,7 @@ export class GroupListPage extends TablePage<Group> {
</span>
<ak-group-form slot="form" .group=${item}>
</ak-group-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
<button slot="trigger" class="pf-c-button pf-m-secondary">
${gettext("Edit")}
</button>
</ak-forms-modal>

View File

@ -0,0 +1,68 @@
import { CoreApi, User } from "authentik-api";
import { gettext } from "django";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../api/Config";
import { Form } from "../../elements/forms/Form";
import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/forms/HorizontalFormElement";
import "../../elements/CodeMirror";
import YAML from "yaml";
@customElement("ak-user-form")
export class UserForm extends Form<User> {
@property({ attribute: false })
user?: User;
getSuccessMessage(): string {
if (this.user) {
return gettext("Successfully updated user.");
} else {
return gettext("Successfully created user.");
}
}
send = (data: User): Promise<User> => {
if (this.user) {
return new CoreApi(DEFAULT_CONFIG).coreUsersUpdate({
id: this.user.pk || 0,
data: data
});
} else {
return new CoreApi(DEFAULT_CONFIG).coreUsersCreate({
data: data
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${gettext("Username")} ?required=${true}>
<input type="text" name="username" value="${ifDefined(this.user?.username)}" class="pf-c-form-control" required="">
<p class="pf-c-form__helper-text">${gettext("Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${gettext("Name")} ?required=${true}>
<input type="text" name="name" value="${ifDefined(this.user?.name)}" class="pf-c-form-control" required="">
<p class="pf-c-form__helper-text">${gettext("User's display name.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${gettext("Email")} ?required=${true}>
<input type="email" name="email" autocomplete="off" value="${ifDefined(this.user?.email)}" class="pf-c-form-control" required="">
</ak-form-element-horizontal>
<ak-form-element-horizontal>
<div class="pf-c-check">
<input type="checkbox" name="is_active" class="pf-c-check__input" ?checked=${this.user?.isActive || false}>
<label class="pf-c-check__label">
${gettext("Is active")}
</label>
</div>
<p class="pf-c-form__helper-text">${gettext("Designates whether this user should be treated as active. Unselect this instead of deleting accounts.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${gettext("Attributes")}>
<ak-codemirror mode="yaml" name="attributes" value="${YAML.stringify(this.user?.attributes)}">
</ak-codemirror>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -3,16 +3,18 @@ import { customElement, html, property, TemplateResult } from "lit-element";
import { AKResponse } from "../../api/Client";
import { TablePage } from "../../elements/table/TablePage";
import "../../elements/buttons/ModalButton";
import "../../elements/forms/ModalForm";
import "../../elements/buttons/Dropdown";
import "../../elements/buttons/ActionButton";
import { TableColumn } from "../../elements/table/Table";
import { PAGE_SIZE } from "../../constants";
import { CoreApi, User } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
import "../../elements/forms/DeleteForm";
import "./UserActiveForm";
import "./UserForm";
import { showMessage } from "../../elements/messages/MessageContainer";
import { MessageLevel } from "../../elements/messages/Message";
@customElement("ak-user-list")
export class UserListPage extends TablePage<User> {
@ -59,12 +61,19 @@ export class UserListPage extends TablePage<User> {
html`${item.isActive ? "Yes" : "No"}`,
html`${item.lastLogin?.toLocaleString()}`,
html`
<ak-modal-button href="${AdminURLManager.users(`${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 User")}
</span>
<ak-user-form slot="form" .user=${item}>
</ak-user-form>
<button slot="trigger" class="pf-m-secondary pf-c-button">
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</button>
</ak-forms-modal>
<ak-dropdown class="pf-c-dropdown">
<button class="pf-c-dropdown__toggle pf-m-primary" type="button">
<span class="pf-c-dropdown__toggle-text">${gettext(item.isActive ? "Disable" : "Enable")}</span>
@ -107,7 +116,18 @@ export class UserListPage extends TablePage<User> {
</li>
</ul>
</ak-dropdown>
<ak-action-button method="GET" url="${AdminURLManager.users(`${item.pk}/reset/`)}">
<ak-action-button
.apiRequest=${() => {
return new CoreApi(DEFAULT_CONFIG).coreUsersRecovery({
id: item.pk || 0,
}).then(rec => {
showMessage({
level: MessageLevel.success,
message: gettext("Successfully generated recovery link"),
description: rec.link
});
});
}}>
${gettext("Reset Password")}
</ak-action-button>
<a class="pf-c-button pf-m-tertiary" href="${`/-/impersonation/${item.pk}/`}">
@ -118,13 +138,21 @@ export class UserListPage extends TablePage<User> {
renderToolbar(): TemplateResult {
return html`
<ak-modal-button href=${AdminURLManager.users("create/")}>
<ak-spinner-button slot="trigger" class="pf-m-primary">
<ak-forms-modal>
<span slot="submit">
${gettext("Create")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</span>
<span slot="header">
${gettext("Create User")}
</span>
<ak-user-form slot="form">
</ak-user-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${gettext("Create")}
</button>
</ak-forms-modal>
${super.renderToolbar()}
`;
}
}

View File

@ -12,7 +12,9 @@ import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import AKGlobal from "../../authentik.css";
import "../../elements/buttons/ModalButton";
import "../../elements/forms/ModalForm";
import "./UserForm";
import "../../elements/buttons/ActionButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/CodeMirror";
import "../../elements/Tabs";
@ -24,8 +26,9 @@ import "../../elements/charts/UserChart";
import { Page } from "../../elements/Page";
import { CoreApi, User } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
import { EVENT_REFRESH } from "../../constants";
import { showMessage } from "../../elements/messages/MessageContainer";
import { MessageLevel } from "../../elements/messages/Message";
@customElement("ak-user-view")
export class UserViewPage extends Page {
@ -131,20 +134,35 @@ export class UserViewPage extends Page {
</dl>
</div>
<div class="pf-c-card__footer">
<ak-modal-button href="${AdminURLManager.users(`${this.user.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-primary">
<ak-forms-modal>
<span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext("Update User")}
</span>
<ak-user-form slot="form" .user=${this.user}>
</ak-user-form>
<button slot="trigger" class="pf-m-primary pf-c-button">
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
</button>
</ak-forms-modal>
</div>
<div class="pf-c-card__footer">
<ak-modal-button href="${AdminURLManager.users(`${this.user.pk}/reset/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Reset Password")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-action-button
.apiRequest=${() => {
return new CoreApi(DEFAULT_CONFIG).coreUsersRecovery({
id: this.user?.pk || 0,
}).then(rec => {
showMessage({
level: MessageLevel.success,
message: gettext("Successfully generated recovery link"),
description: rec.link
});
});
}}>
${gettext("Reset Password")}
</ak-action-button>
</div>
</div>
<div class="pf-c-card pf-l-gallery__item pf-m-4-col" style="grid-column-end: span 4;grid-row-end: span 2;">