web/admin: migrate user forms to web
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
fac8d53163
commit
526af26536
|
@ -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,
|
||||
}
|
|
@ -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/",
|
||||
|
|
|
@ -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("/")
|
|
@ -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
|
||||
|
|
|
@ -68,10 +68,6 @@ export class AdminURLManager {
|
|||
return `/administration/events/transports/${rest}`;
|
||||
}
|
||||
|
||||
static users(rest: string): string {
|
||||
return `/administration/users/${rest}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class UserURLManager {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>`;
|
||||
}
|
||||
|
||||
}
|
|
@ -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()}
|
||||
`;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
<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-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
</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;">
|
||||
|
|
Reference in New Issue