From 4cfcc48b238f25a49f9a98a4ed178a7200044bb2 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 16 Feb 2021 23:13:22 +0100 Subject: [PATCH] admin: migrate certificate-keypair list to web --- .../certificatekeypair/list.html | 122 --------------- .../administration/property_mapping/test.html | 28 ---- .../templates/administration/source/list.html | 148 ------------------ authentik/admin/urls.py | 5 - authentik/admin/views/certificate_key_pair.py | 37 +---- authentik/crypto/api.py | 6 + swagger.yaml | 4 + web/src/api/CertificateKeyPair.ts | 26 +++ web/src/interfaces/AdminInterface.ts | 2 +- .../crypto/CertificateKeyPairListPage.ts | 115 ++++++++++++++ web/src/pages/flows/FlowDiagram.ts | 2 +- web/src/routes.ts | 4 +- 12 files changed, 163 insertions(+), 336 deletions(-) delete mode 100644 authentik/admin/templates/administration/certificatekeypair/list.html delete mode 100644 authentik/admin/templates/administration/property_mapping/test.html delete mode 100644 authentik/admin/templates/administration/source/list.html create mode 100644 web/src/api/CertificateKeyPair.ts create mode 100644 web/src/pages/crypto/CertificateKeyPairListPage.ts diff --git a/authentik/admin/templates/administration/certificatekeypair/list.html b/authentik/admin/templates/administration/certificatekeypair/list.html deleted file mode 100644 index 9bf8af298..000000000 --- a/authentik/admin/templates/administration/certificatekeypair/list.html +++ /dev/null @@ -1,122 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Certificate-Key Pairs' %} -

-

{% trans "Import certificates of external providers or create certificates to sign requests with." %}

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - {% trans 'Create' %} - -
-
- - - {% trans 'Generate' %} - -
-
- -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - {% for kp in object_list %} - - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Private Key available' %}{% trans 'Fingerprint' %}
-
-
{{ kp.name }}
-
-
- - {% if kp.key_data is not None %} - {% trans 'Yes' %} - {% else %} - {% trans 'No' %} - {% endif %} - - - {{ kp.fingerprint }} - - - - {% trans 'Edit' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Certificates.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any certificates." %} - {% else %} - {% trans 'Currently no certificates exist. Click the button below to create one.' %} - {% endif %} -
- - - {% trans 'Create' %} - -
-
-
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templates/administration/property_mapping/test.html b/authentik/admin/templates/administration/property_mapping/test.html deleted file mode 100644 index d52dcff56..000000000 --- a/authentik/admin/templates/administration/property_mapping/test.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'generic/form.html' %} - -{% load i18n %} - -{% block above_form %} -

{% blocktrans with property_mapping=property_mapping %}Test {{ property_mapping }}{% endblocktrans %}

-{% endblock %} - -{% block beneath_form %} -{% if result %} -
-
- -
-
-
- -
-
-
-{% endif %} -{% endblock %} - -{% block action %} -{% trans 'Test' %} -{% endblock %} diff --git a/authentik/admin/templates/administration/source/list.html b/authentik/admin/templates/administration/source/list.html deleted file mode 100644 index 777a6ded2..000000000 --- a/authentik/admin/templates/administration/source/list.html +++ /dev/null @@ -1,148 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load authentik_utils %} - -{% block content %} -
-
-

- - {% trans 'Source' %} -

-

{% trans "External Sources which can be used to get Identities into authentik, for example Social Providers like Twiter and GitHub or Enterprise Providers like ADFS and LDAP." %} -

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - - - -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - {% for source in object_list %} - - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Type' %}{% trans 'Additional Info' %}
- -
{{ source.name }}
- {% if not source.enabled %} - {% trans 'Disabled' %} - {% endif %} -
-
- - {{ source|fieldtype }} - - - - {{ source.ui_additional_info|default:""|safe }} - - - - - {% trans 'Edit' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Sources.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any sources." %} - {% else %} - {% trans 'Currently no sources exist. Click the button below to create one.' %} - {% endif %} -
- - - - -
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 499c89947..c35cdbc90 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -283,11 +283,6 @@ urlpatterns = [ name="group-delete", ), # Certificate-Key Pairs - path( - "crypto/certificates/", - certificate_key_pair.CertificateKeyPairListView.as_view(), - name="certificate_key_pair", - ), path( "crypto/certificates/create/", certificate_key_pair.CertificateKeyPairCreateView.as_view(), diff --git a/authentik/admin/views/certificate_key_pair.py b/authentik/admin/views/certificate_key_pair.py index 7f01e45de..4653cb154 100644 --- a/authentik/admin/views/certificate_key_pair.py +++ b/authentik/admin/views/certificate_key_pair.py @@ -5,18 +5,12 @@ from django.contrib.auth.mixins import ( ) from django.contrib.messages.views import SuccessMessageMixin from django.http.response import HttpResponse -from django.urls import reverse_lazy from django.utils.translation import gettext as _ -from django.views.generic import ListView, UpdateView +from django.views.generic import UpdateView from django.views.generic.edit import FormView -from guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import ( - BackSuccessUrlMixin, - DeleteMessageView, - SearchListMixin, - UserPaginateListMixin, -) +from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView from authentik.crypto.builder import CertificateBuilder from authentik.crypto.forms import ( CertificateKeyPairForm, @@ -26,23 +20,6 @@ from authentik.crypto.models import CertificateKeyPair from authentik.lib.views import CreateAssignPermView -class CertificateKeyPairListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - ListView, -): - """Show list of all keypairs""" - - model = CertificateKeyPair - permission_required = "authentik_crypto.view_certificatekeypair" - ordering = "name" - template_name = "administration/certificatekeypair/list.html" - - search_fields = ["name"] - - class CertificateKeyPairCreateView( SuccessMessageMixin, BackSuccessUrlMixin, @@ -57,7 +34,7 @@ class CertificateKeyPairCreateView( permission_required = "authentik_crypto.add_certificatekeypair" template_name = "generic/create.html" - success_url = reverse_lazy("authentik_admin:certificate_key_pair") + success_url = "/" success_message = _("Successfully created Certificate-Key Pair") @@ -75,7 +52,7 @@ class CertificateKeyPairGenerateView( permission_required = "authentik_crypto.add_certificatekeypair" template_name = "administration/certificatekeypair/generate.html" - success_url = reverse_lazy("authentik_admin:certificate_key_pair") + success_url = "/" success_message = _("Successfully generated Certificate-Key Pair") def form_valid(self, form: CertificateKeyPairGenerateForm) -> HttpResponse: @@ -103,7 +80,7 @@ class CertificateKeyPairUpdateView( permission_required = "authentik_crypto.change_certificatekeypair" template_name = "generic/update.html" - success_url = reverse_lazy("authentik_admin:certificate_key_pair") + success_url = "/" success_message = _("Successfully updated Certificate-Key Pair") @@ -116,5 +93,5 @@ class CertificateKeyPairDeleteView( permission_required = "authentik_crypto.delete_certificatekeypair" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:certificate_key_pair") + success_url = "/" success_message = _("Successfully deleted Certificate-Key Pair") diff --git a/authentik/crypto/api.py b/authentik/crypto/api.py index 5fecf7872..e26cd79cf 100644 --- a/authentik/crypto/api.py +++ b/authentik/crypto/api.py @@ -20,11 +20,16 @@ class CertificateKeyPairSerializer(ModelSerializer): cert_expiry = DateTimeField(source="certificate.not_valid_after", read_only=True) cert_subject = SerializerMethodField() + private_key_available = SerializerMethodField() def get_cert_subject(self, instance: CertificateKeyPair) -> str: """Get certificate subject as full rfc4514""" return instance.certificate.subject.rfc4514_string() + def get_private_key_available(self, instance: CertificateKeyPair) -> bool: + """Show if this keypair has a private key configured or not""" + return instance.key_data != "" and instance.key_data is not None + def validate_certificate_data(self, value): """Verify that input is a valid PEM x509 Certificate""" try: @@ -58,6 +63,7 @@ class CertificateKeyPairSerializer(ModelSerializer): "key_data", "cert_expiry", "cert_subject", + "private_key_available", ] extra_kwargs = { "key_data": {"write_only": True}, diff --git a/swagger.yaml b/swagger.yaml index a3864bb24..ea37c08f0 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -7913,6 +7913,10 @@ definitions: title: Cert subject type: string readOnly: true + private_key_available: + title: Private key available + type: boolean + readOnly: true CertificateData: description: Get CertificateKeyPair's data type: object diff --git a/web/src/api/CertificateKeyPair.ts b/web/src/api/CertificateKeyPair.ts new file mode 100644 index 000000000..4681f8c0d --- /dev/null +++ b/web/src/api/CertificateKeyPair.ts @@ -0,0 +1,26 @@ +import { DefaultClient, AKResponse, QueryArguments } from "./Client"; + +export class CertificateKeyPair { + pk: string; + name: string; + fingerprint: string; + cert_expiry: number; + cert_subject: string; + private_key_available: boolean; + + constructor() { + throw Error(); + } + + static get(slug: string): Promise { + return DefaultClient.fetch(["crypto", "certificatekeypairs", slug]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["crypto", "certificatekeypairs"], filter); + } + + static adminUrl(rest: string): string { + return `/administration/crypto/certificates/${rest}`; + } +} diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts index d05633365..57f3e3329 100644 --- a/web/src/interfaces/AdminInterface.ts +++ b/web/src/interfaces/AdminInterface.ts @@ -49,7 +49,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ new SidebarItem("Identity & Cryptography").children( new SidebarItem("User", "/administration/users/"), new SidebarItem("Groups", "/administration/groups/"), - new SidebarItem("Certificates", "/administration/crypto/certificates"), + new SidebarItem("Certificates", "/crypto/certificates"), new SidebarItem("Tokens", "/administration/tokens/"), ).when((): Promise => { return User.me().then(u => u.is_superuser); diff --git a/web/src/pages/crypto/CertificateKeyPairListPage.ts b/web/src/pages/crypto/CertificateKeyPairListPage.ts new file mode 100644 index 000000000..806d9852d --- /dev/null +++ b/web/src/pages/crypto/CertificateKeyPairListPage.ts @@ -0,0 +1,115 @@ +import { gettext } from "django"; +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/buttons/SpinnerButton"; +import { TableColumn } from "../../elements/table/Table"; +import { CertificateKeyPair } from "../../api/CertificateKeyPair"; + +@customElement("ak-crypto-certificatekeypair-list") +export class CertificateKeyPairListPage extends TablePage { + expandable = true; + + searchEnabled(): boolean { + return true; + } + pageTitle(): string { + return gettext("Certificate-Key Pairs"); + } + pageDescription(): string { + return gettext("Import certificates of external providers or create certificates to sign requests with."); + } + pageIcon(): string { + return gettext("pf-icon pf-icon-key"); + } + + @property() + order = "name"; + + apiEndpoint(page: number): Promise> { + return CertificateKeyPair.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Name", "name"), + new TableColumn("Private key available?"), + new TableColumn("Expiry date"), + new TableColumn(""), + ]; + } + + row(item: CertificateKeyPair): TemplateResult[] { + return [ + html`${item.name}`, + html`${gettext(item.private_key_available ? "Yes" : "No")}`, + html`${new Date(item.cert_expiry * 1000).toLocaleString()}`, + html` + + + ${gettext("Edit")} + +
+
  + + + ${gettext("Delete")} + +
+
+ `, + ]; + } + + renderExpanded(item: CertificateKeyPair): TemplateResult { + return html` + +
+
+
+
+ ${gettext("Certificate Fingerprint")} +
+
+
${item.fingerprint}
+
+
+
+
+ ${gettext("Certificate Subjet")} +
+
+
${item.cert_subject}
+
+
+
+
+ + + `; + } + + renderToolbar(): TemplateResult { + return html` + + + ${gettext("Create")} + +
+
  + + + ${gettext("Generate")} + +
+
+ ${super.renderToolbar()} + `; + } +} diff --git a/web/src/pages/flows/FlowDiagram.ts b/web/src/pages/flows/FlowDiagram.ts index a1118aa21..726a770ff 100644 --- a/web/src/pages/flows/FlowDiagram.ts +++ b/web/src/pages/flows/FlowDiagram.ts @@ -37,7 +37,7 @@ export class FlowDiagram extends LitElement { constructor() { super(); this.addEventListener("ak-refresh", () => { - if (!this._flowSlug) return + if (!this._flowSlug) return; this.flowSlug = this._flowSlug; }); window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", (ev) => { diff --git a/web/src/routes.ts b/web/src/routes.ts index 62b876862..778b7e4d1 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -17,6 +17,7 @@ import "./pages/providers/ProviderListPage"; import "./pages/providers/ProviderViewPage"; import "./pages/property-mappings/PropertyMappingListPage"; import "./pages/outposts/OutpostListPage"; +import "./pages/crypto/CertificateKeyPairListPage"; export const ROUTES: Route[] = [ // Prevent infinite Shell loops @@ -36,7 +37,7 @@ export const ROUTES: Route[] = [ new Route(new RegExp(`^/sources/(?${SLUG_REGEX})$`)).then((args) => { return html``; }), - new Route(new RegExp(`^/flows$`), html``), + new Route(new RegExp("^/flows$"), html``), new Route(new RegExp(`^/flows/(?${SLUG_REGEX})$`)).then((args) => { return html``; }), @@ -48,4 +49,5 @@ export const ROUTES: Route[] = [ new Route(new RegExp("^/events/rules$"), html``), new Route(new RegExp("^/property-mappings$"), html``), new Route(new RegExp("^/outposts$"), html``), + new Route(new RegExp("^/crypto/certificates$"), html``), ];