diff --git a/authentik/admin/forms/users.py b/authentik/admin/forms/users.py deleted file mode 100644 index b7c3cc8d7..000000000 --- a/authentik/admin/forms/users.py +++ /dev/null @@ -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, - } diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 217145e3c..f581a0a89 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -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//update/", users.UserUpdateView.as_view(), name="user-update"), - path( - "users//reset/", - users.UserPasswordResetView.as_view(), - name="user-password-reset", - ), # Certificate-Key Pairs path( "crypto/certificates/create/", diff --git a/authentik/admin/views/users.py b/authentik/admin/views/users.py deleted file mode 100644 index 760c67725..000000000 --- a/authentik/admin/views/users.py +++ /dev/null @@ -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("/") diff --git a/authentik/admin/views/utils.py b/authentik/admin/views/utils.py index 5c5df69dd..4c2fe7a38 100644 --- a/authentik/admin/views/utils.py +++ b/authentik/admin/views/utils.py @@ -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 diff --git a/web/src/api/legacy.ts b/web/src/api/legacy.ts index 7e8504907..e921b689e 100644 --- a/web/src/api/legacy.ts +++ b/web/src/api/legacy.ts @@ -68,10 +68,6 @@ export class AdminURLManager { return `/administration/events/transports/${rest}`; } - static users(rest: string): string { - return `/administration/users/${rest}`; - } - } export class UserURLManager { diff --git a/web/src/elements/buttons/ActionButton.ts b/web/src/elements/buttons/ActionButton.ts index 6e3826f01..ed5e38263 100644 --- a/web/src/elements/buttons/ActionButton.ts +++ b/web/src/elements/buttons/ActionButton.ts @@ -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, diff --git a/web/src/pages/groups/GroupForm.ts b/web/src/pages/groups/GroupForm.ts index 57c46c36d..d173f9326 100644 --- a/web/src/pages/groups/GroupForm.ts +++ b/web/src/pages/groups/GroupForm.ts @@ -18,9 +18,9 @@ export class GroupForm extends Form { 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."); } } diff --git a/web/src/pages/groups/GroupListPage.ts b/web/src/pages/groups/GroupListPage.ts index d505e11a3..294750a69 100644 --- a/web/src/pages/groups/GroupListPage.ts +++ b/web/src/pages/groups/GroupListPage.ts @@ -66,7 +66,7 @@ export class GroupListPage extends TablePage { - diff --git a/web/src/pages/users/UserForm.ts b/web/src/pages/users/UserForm.ts new file mode 100644 index 000000000..0cc8c565b --- /dev/null +++ b/web/src/pages/users/UserForm.ts @@ -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 { + + @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 => { + 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`
+ + +

${gettext("Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.")}

+
+ + +

${gettext("User's display name.")}

+
+ + + + +
+ + +
+

${gettext("Designates whether this user should be treated as active. Unselect this instead of deleting accounts.")}

+
+ + + + +
`; + } + +} diff --git a/web/src/pages/users/UserListPage.ts b/web/src/pages/users/UserListPage.ts index cdc0b4360..b0fd53486 100644 --- a/web/src/pages/users/UserListPage.ts +++ b/web/src/pages/users/UserListPage.ts @@ -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 { @@ -59,12 +61,19 @@ export class UserListPage extends TablePage { html`${item.isActive ? "Yes" : "No"}`, html`${item.lastLogin?.toLocaleString()}`, html` - - + + + ${gettext("Update")} + + + ${gettext("Update User")} + + + + + + ${super.renderToolbar()} `; } + } diff --git a/web/src/pages/users/UserViewPage.ts b/web/src/pages/users/UserViewPage.ts index a1c59160a..e29acc762 100644 --- a/web/src/pages/users/UserViewPage.ts +++ b/web/src/pages/users/UserViewPage.ts @@ -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 {