From 488e8f769adcb84541278b607079a0d5a9abc90c Mon Sep 17 00:00:00 2001 From: Jens L Date: Sat, 12 Dec 2020 19:39:09 +0100 Subject: [PATCH] web: remove policy bindings page (#370) * admin: accept ?target for PolicyBindingCreateView * core: fix rendering of hidden fields in horizontal form * web: add create button for application's bound policies * admin: fix delete form not working * web: fix ak-refresh event not being dispatched correctly * web: fix linting errors * admin: fix tests not loading * build(deps-dev): bump eslint from 7.14.0 to 7.15.0 in /web (#372) Bumps [eslint](https://github.com/eslint/eslint) from 7.14.0 to 7.15.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v7.14.0...v7.15.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump rollup from 2.34.1 to 2.34.2 in /web (#373) Bumps [rollup](https://github.com/rollup/rollup) from 2.34.1 to 2.34.2. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.34.1...v2.34.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @types/codemirror from 0.0.100 to 0.0.102 in /web (#374) Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.100 to 0.0.102. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump bandit from 1.6.2 to 1.6.3 (#371) * build(deps-dev): bump bandit from 1.6.2 to 1.6.3 Bumps [bandit](https://github.com/PyCQA/bandit) from 1.6.2 to 1.6.3. - [Release notes](https://github.com/PyCQA/bandit/releases) - [Commits](https://github.com/PyCQA/bandit/compare/1.6.2...1.6.3) Signed-off-by: dependabot[bot] * root: update for new bandit version Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jens Langhammer * web: add header to bound-policies * web: fix spacing between bulk_select buttons * web: add separate ak-bound-policies-list, add flow view page * web: fix flows' policies not loading * Squashed commit of the following: commit e535cb0ec82f38e6cfbe1a1ba4e1b49dd518d669 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Dec 10 09:58:07 2020 +0100 build(deps): bump boto3 from 1.16.32 to 1.16.33 (#383) Bumps [boto3](https://github.com/boto/boto3) from 1.16.32 to 1.16.33. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.16.32...1.16.33) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 8c1f55d3e3cfd88257b1e9eb285c3aeeccb941a9 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Dec 9 09:06:45 2020 +0100 build(deps): bump boto3 from 1.16.31 to 1.16.32 (#382) Bumps [boto3](https://github.com/boto/boto3) from 1.16.31 to 1.16.32. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.16.31...1.16.32) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit c3a2cb44cd52b885045a7bd90466809f8bc9b35e Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Dec 9 09:06:29 2020 +0100 build(deps): bump celery from 5.0.3 to 5.0.4 (#380) Bumps [celery](https://github.com/celery/celery) from 5.0.3 to 5.0.4. - [Release notes](https://github.com/celery/celery/releases) - [Changelog](https://github.com/celery/celery/blob/master/Changelog.rst) - [Commits](https://github.com/celery/celery/compare/v5.0.3...v5.0.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 682401bbf23de689cdcf01e0ecf3431fe485e5dc Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed Dec 9 07:20:45 2020 +0100 build(deps): bump uvicorn from 0.12.3 to 0.13.0 (#381) Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.12.3 to 0.13.0. - [Release notes](https://github.com/encode/uvicorn/releases) - [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/uvicorn/compare/0.12.3...0.13.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 3e6e16734859e1c7cb897d64d4f8de71b6007227 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Dec 8 10:32:00 2020 +0100 build(deps-dev): bump @typescript-eslint/parser in /web (#377) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.9.0 to 4.9.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.9.1/packages/parser) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit d08c1b7b02e0946560eebc5556069e709e86cada Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Dec 8 10:31:47 2020 +0100 build(deps): bump @sentry/browser from 5.28.0 to 5.29.0 in /web (#378) Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 5.28.0 to 5.29.0. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/5.28.0...5.29.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 94d70d252c0f1e1990c6fd83359edae30ae533bd Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Dec 8 09:02:37 2020 +0100 build(deps): bump boto3 from 1.16.30 to 1.16.31 (#375) Bumps [boto3](https://github.com/boto/boto3) from 1.16.30 to 1.16.31. - [Release notes](https://github.com/boto/boto3/releases) - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst) - [Commits](https://github.com/boto/boto3/compare/1.16.30...1.16.31) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit ccfe746dd59bc08764dc7db016bfa6dadcb54fc7 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Dec 8 09:02:28 2020 +0100 build(deps-dev): bump @typescript-eslint/eslint-plugin in /web (#376) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.9.0 to 4.9.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.9.1/packages/eslint-plugin) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit ef5dffa96a36fe3dd71447680ddce796d16028ef Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Dec 8 09:02:16 2020 +0100 build(deps): bump @sentry/tracing from 5.28.0 to 5.29.0 in /web (#379) Bumps [@sentry/tracing](https://github.com/getsentry/sentry-javascript) from 5.28.0 to 5.29.0. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/5.28.0...5.29.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 2caa1e765092e9a00618cd9e9bc426c4eaefda33 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Dec 7 11:21:07 2020 +0100 build(deps-dev): bump bandit from 1.6.2 to 1.6.3 (#371) * build(deps-dev): bump bandit from 1.6.2 to 1.6.3 Bumps [bandit](https://github.com/PyCQA/bandit) from 1.6.2 to 1.6.3. - [Release notes](https://github.com/PyCQA/bandit/releases) - [Commits](https://github.com/PyCQA/bandit/compare/1.6.2...1.6.3) Signed-off-by: dependabot[bot] * root: update for new bandit version Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jens Langhammer commit 2246f3a53445e92f9fc2b5946f4ea4d0ba5f7934 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Dec 7 10:26:01 2020 +0100 build(deps): bump @types/codemirror from 0.0.100 to 0.0.102 in /web (#374) Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.100 to 0.0.102. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 95ba00cb79fbff8e68e2b76d50a3c923361bec97 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Dec 7 09:09:49 2020 +0100 build(deps): bump rollup from 2.34.1 to 2.34.2 in /web (#373) Bumps [rollup](https://github.com/rollup/rollup) from 2.34.1 to 2.34.2. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.34.1...v2.34.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 2ab4d6620f21eaab3f43739b61a83d595088d60d Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Dec 7 09:09:24 2020 +0100 build(deps-dev): bump eslint from 7.14.0 to 7.15.0 in /web (#372) Bumps [eslint](https://github.com/eslint/eslint) from 7.14.0 to 7.15.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v7.14.0...v7.15.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * web: fix linting error * web: simplify sidebar logic * web: add support for multiple active matchers per sidebar item * web: move router to elements * flows: add stage_obj to flows api * sources/*: make all sources implement SerializerModel * web: improve listing of stages * web: implement expandable table * web/table: use TemplateResult as return value for row() * web: add empty state, fix link for BoundStageList * admin: make stage binding form accept ?target like policy binding * web: fix styles in dark mode for expanding tables * flows: add policybindingmodel_ptr_id to FlowStageBinding API * web: improve wording for policies * web: fix dark theme for tertiary buttons and static modals * web: implement SourceViewPage * web: add empty state for BoundPoliciesList * web: cleanup URLs for FlowStageBindings * root: remove url attribute from ak-messages Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../templates/administration/flow/list.html | 4 +- .../templates/administration/source/list.html | 4 +- authentik/admin/tests/__init__.py | 0 .../{tests.py => tests/test_generated.py} | 0 authentik/admin/tests/test_policy_binding.py | 26 ++++ authentik/admin/tests/test_stage_bindings.py | 26 ++++ authentik/admin/views/policies_bindings.py | 22 ++- authentik/admin/views/stages_bindings.py | 19 ++- authentik/core/api/sources.py | 7 + authentik/core/models.py | 4 +- authentik/core/templates/generic/delete.html | 4 +- authentik/core/templates/login/base.html | 2 +- authentik/core/templates/login/base_full.html | 2 +- .../templates/partials/form_horizontal.html | 4 + authentik/flows/api.py | 56 ++++--- authentik/flows/forms.py | 5 + authentik/policies/forms.py | 5 + authentik/root/messages/storage.py | 4 +- authentik/sources/ldap/models.py | 7 + authentik/sources/oauth/models.py | 7 + authentik/sources/saml/models.py | 7 + swagger.yaml | 75 +++++---- web/src/api/flow.ts | 76 +++++++++ web/src/api/policy_binding.ts | 22 ++- web/src/api/source.ts | 22 +++ web/src/authentik.css | 21 +++ web/src/elements/EmptyState.ts | 34 ++++ web/src/elements/buttons/ModalButton.ts | 1 + .../elements/policies/BoundPoliciesList.ts | 79 ++++++++++ web/src/{pages => elements}/router/Route.ts | 0 .../{pages => elements}/router/RouteMatch.ts | 0 .../router/RouterOutlet.ts | 2 +- web/src/elements/sidebar/Sidebar.ts | 61 +++++++- web/src/elements/table/Table.ts | 88 ++++++++--- web/src/index.html | 2 +- web/src/interfaces/AdminInterface.ts | 147 +++++------------- web/src/interfaces/Interface.ts | 2 +- web/src/main.ts | 4 +- .../pages/applications/ApplicationListPage.ts | 17 +- .../pages/applications/ApplicationViewPage.ts | 56 ++----- web/src/pages/flows/BoundStagesList.ts | 97 ++++++++++++ web/src/pages/flows/FlowViewPage.ts | 71 +++++++++ web/src/pages/sources/SourceViewPage.ts | 63 ++++++++ web/src/routes.ts | 10 +- 44 files changed, 896 insertions(+), 269 deletions(-) create mode 100644 authentik/admin/tests/__init__.py rename authentik/admin/{tests.py => tests/test_generated.py} (100%) create mode 100644 authentik/admin/tests/test_policy_binding.py create mode 100644 authentik/admin/tests/test_stage_bindings.py create mode 100644 web/src/api/flow.ts create mode 100644 web/src/api/source.ts create mode 100644 web/src/elements/EmptyState.ts create mode 100644 web/src/elements/policies/BoundPoliciesList.ts rename web/src/{pages => elements}/router/Route.ts (100%) rename web/src/{pages => elements}/router/RouteMatch.ts (100%) rename web/src/{pages => elements}/router/RouterOutlet.ts (98%) create mode 100644 web/src/pages/flows/BoundStagesList.ts create mode 100644 web/src/pages/flows/FlowViewPage.ts create mode 100644 web/src/pages/sources/SourceViewPage.ts diff --git a/authentik/admin/templates/administration/flow/list.html b/authentik/admin/templates/administration/flow/list.html index f1a27a0d3..5bb2dfcd4 100644 --- a/authentik/admin/templates/administration/flow/list.html +++ b/authentik/admin/templates/administration/flow/list.html @@ -53,10 +53,10 @@ {% for flow in object_list %} - + diff --git a/authentik/admin/templates/administration/source/list.html b/authentik/admin/templates/administration/source/list.html index a4a01442e..e9af3c44e 100644 --- a/authentik/admin/templates/administration/source/list.html +++ b/authentik/admin/templates/administration/source/list.html @@ -63,12 +63,12 @@ {% for source in object_list %} - + diff --git a/authentik/admin/tests/__init__.py b/authentik/admin/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/admin/tests.py b/authentik/admin/tests/test_generated.py similarity index 100% rename from authentik/admin/tests.py rename to authentik/admin/tests/test_generated.py diff --git a/authentik/admin/tests/test_policy_binding.py b/authentik/admin/tests/test_policy_binding.py new file mode 100644 index 000000000..5a964bf81 --- /dev/null +++ b/authentik/admin/tests/test_policy_binding.py @@ -0,0 +1,26 @@ +"""admin tests""" +from django.test import TestCase +from django.test.client import RequestFactory + +from authentik.admin.views.policies_bindings import PolicyBindingCreateView +from authentik.core.models import Application + + +class TestPolicyBindingView(TestCase): + """Generic admin tests""" + + def setUp(self): + self.factory = RequestFactory() + + def test_without_get_param(self): + """Test PolicyBindingCreateView without get params""" + request = self.factory.get("/") + view = PolicyBindingCreateView(request=request) + self.assertEqual(view.get_initial(), {}) + + def test_with_param(self): + """Test PolicyBindingCreateView with get params""" + target = Application.objects.create(name="test") + request = self.factory.get("/", {"target": target.pk.hex}) + view = PolicyBindingCreateView(request=request) + self.assertEqual(view.get_initial(), {"target": target, "order": 0}) diff --git a/authentik/admin/tests/test_stage_bindings.py b/authentik/admin/tests/test_stage_bindings.py new file mode 100644 index 000000000..169048e4f --- /dev/null +++ b/authentik/admin/tests/test_stage_bindings.py @@ -0,0 +1,26 @@ +"""admin tests""" +from django.test import TestCase +from django.test.client import RequestFactory + +from authentik.admin.views.stages_bindings import StageBindingCreateView +from authentik.flows.models import Flow + + +class TestStageBindingView(TestCase): + """Generic admin tests""" + + def setUp(self): + self.factory = RequestFactory() + + def test_without_get_param(self): + """Test StageBindingCreateView without get params""" + request = self.factory.get("/") + view = StageBindingCreateView(request=request) + self.assertEqual(view.get_initial(), {}) + + def test_with_param(self): + """Test StageBindingCreateView with get params""" + target = Flow.objects.create(name="test", slug="test") + request = self.factory.get("/", {"target": target.pk.hex}) + view = StageBindingCreateView(request=request) + self.assertEqual(view.get_initial(), {"target": target, "order": 0}) diff --git a/authentik/admin/views/policies_bindings.py b/authentik/admin/views/policies_bindings.py index 9f7a8f97a..be19eb815 100644 --- a/authentik/admin/views/policies_bindings.py +++ b/authentik/admin/views/policies_bindings.py @@ -1,10 +1,12 @@ """authentik PolicyBinding administration""" +from typing import Any + 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.db.models import QuerySet +from django.db.models import Max, QuerySet from django.urls import reverse_lazy from django.utils.translation import gettext as _ from django.views.generic import ListView, UpdateView @@ -18,7 +20,7 @@ from authentik.admin.views.utils import ( ) from authentik.lib.views import CreateAssignPermView from authentik.policies.forms import PolicyBindingForm -from authentik.policies.models import PolicyBinding +from authentik.policies.models import PolicyBinding, PolicyBindingModel class PolicyBindingListView( @@ -67,6 +69,22 @@ class PolicyBindingCreateView( success_url = reverse_lazy("authentik_admin:policies-bindings") success_message = _("Successfully created PolicyBinding") + def get_initial(self) -> dict[str, Any]: + if "target" in self.request.GET: + initial_target_pk = self.request.GET["target"] + targets = PolicyBindingModel.objects.filter( + pk=initial_target_pk + ).select_subclasses() + if not targets.exists(): + return {} + max_order = PolicyBinding.objects.filter(target=targets.first()).aggregate( + Max("order") + )["order__max"] + if not isinstance(max_order, int): + max_order = -1 + return {"target": targets.first(), "order": max_order + 1} + return super().get_initial() + class PolicyBindingUpdateView( SuccessMessageMixin, diff --git a/authentik/admin/views/stages_bindings.py b/authentik/admin/views/stages_bindings.py index d048764e8..7e416d19f 100644 --- a/authentik/admin/views/stages_bindings.py +++ b/authentik/admin/views/stages_bindings.py @@ -1,9 +1,12 @@ """authentik StageBinding administration""" +from typing import Any + 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.db.models import Max from django.urls import reverse_lazy from django.utils.translation import gettext as _ from django.views.generic import ListView, UpdateView @@ -15,7 +18,7 @@ from authentik.admin.views.utils import ( UserPaginateListMixin, ) from authentik.flows.forms import FlowStageBindingForm -from authentik.flows.models import FlowStageBinding +from authentik.flows.models import Flow, FlowStageBinding from authentik.lib.views import CreateAssignPermView @@ -47,6 +50,20 @@ class StageBindingCreateView( success_url = reverse_lazy("authentik_admin:stage-bindings") success_message = _("Successfully created StageBinding") + def get_initial(self) -> dict[str, Any]: + if "target" in self.request.GET: + initial_target_pk = self.request.GET["target"] + targets = Flow.objects.filter(pk=initial_target_pk).select_subclasses() + if not targets.exists(): + return {} + max_order = FlowStageBinding.objects.filter( + target=targets.first() + ).aggregate(Max("order"))["order__max"] + if not isinstance(max_order, int): + max_order = -1 + return {"target": targets.first(), "order": max_order + 1} + return super().get_initial() + class StageBindingUpdateView( SuccessMessageMixin, diff --git a/authentik/core/api/sources.py b/authentik/core/api/sources.py index e19acf27e..120dd8ac1 100644 --- a/authentik/core/api/sources.py +++ b/authentik/core/api/sources.py @@ -15,6 +15,12 @@ class SourceSerializer(ModelSerializer): """Get object type so that we know which API Endpoint to use to get the full object""" return obj._meta.object_name.lower().replace("source", "") + def to_representation(self, instance: Source): + # pyright: reportGeneralTypeIssues=false + if instance.__class__ == Source: + return super().to_representation(instance) + return instance.serializer(instance=instance).data + class Meta: model = Source @@ -26,6 +32,7 @@ class SourceViewSet(ReadOnlyModelViewSet): queryset = Source.objects.all() serializer_class = SourceSerializer + lookup_field = "slug" def get_queryset(self): return Source.objects.select_subclasses() diff --git a/authentik/core/models.py b/authentik/core/models.py index b55acac51..d1fbd9d67 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -20,7 +20,7 @@ from authentik.core.exceptions import PropertyMappingExpressionException from authentik.core.signals import password_changed from authentik.core.types import UILoginButton from authentik.flows.models import Flow -from authentik.lib.models import CreatedUpdatedModel +from authentik.lib.models import CreatedUpdatedModel, SerializerModel from authentik.policies.models import PolicyBindingModel LOGGER = get_logger() @@ -200,7 +200,7 @@ class Application(PolicyBindingModel): verbose_name_plural = _("Applications") -class Source(PolicyBindingModel): +class Source(SerializerModel, PolicyBindingModel): """Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server""" name = models.TextField(help_text=_("Source's display Name.")) diff --git a/authentik/core/templates/generic/delete.html b/authentik/core/templates/generic/delete.html index 2cfab27f2..234dab40e 100644 --- a/authentik/core/templates/generic/delete.html +++ b/authentik/core/templates/generic/delete.html @@ -20,7 +20,7 @@
-
+ {% csrf_token %}

{% blocktrans with object_type=object|verbose_name name=object %} @@ -35,7 +35,7 @@

{% endblock %} diff --git a/authentik/core/templates/login/base.html b/authentik/core/templates/login/base.html index 401c5c1cd..c029b284c 100644 --- a/authentik/core/templates/login/base.html +++ b/authentik/core/templates/login/base.html @@ -1,7 +1,7 @@ {% load static %} {% load i18n %} - +
- +
@@ -56,21 +70,59 @@ export abstract class Table extends LitElement { `; } + renderEmpty(inner?: TemplateResult): TemplateResult { + return html` + + +
+ ${inner ? inner : html``} +
+ + + `; + } + private renderRows(): TemplateResult[] | undefined { if (!this.data) { return; } - return this.data.results.map((item) => { - const fullRow = [""].concat( - this.row(item).map((col) => { - return `${col}`; - }) - ); - fullRow.push(""); - return htmlFromString(...fullRow); + if (this.data.pagination.count === 0) { + return [this.renderEmpty()]; + } + return this.data.results.map((item: T, idx: number) => { + if ((this.expandedRows.length - 1) < idx) { + this.expandedRows[idx] = false; + } + return html` + + ${this.expandable ? html` + + ` : html``} + ${this.row(item).map((col) => { + return html`${col}`; + })} + + + + ${this.renderExpanded(item)} + + `; }); } + renderToolbar(): TemplateResult { + return html` `; + } + renderTable(): TemplateResult { if (!this.data) { this.fetch(); @@ -78,12 +130,7 @@ export abstract class Table extends LitElement { return html`
- - + ${this.renderToolbar()}
extends LitElement {
- +
+ ${this.expandable ? html``)} - - ${this.data ? this.renderRows() : this.renderLoading()} - + ${this.data ? this.renderRows() : this.renderLoading()}
` : html``} ${this.columns().map((col) => html`${gettext(col)}
- +
Skip to content => { - return User.me().then(u => u.is_superuser); - }, - }, - { - name: "Administration", - children: [ - { - name: "Overview", - path: ["/administration/overview-ng/"], - }, - { - name: "System Tasks", - path: ["/administration/tasks/"], - }, - { - name: "Applications", - path: ["/administration/applications/"], - }, - { - name: "Sources", - path: ["/administration/sources/"], - }, - { - name: "Providers", - path: ["/administration/providers/"], - }, - { - name: "User Management", - children: [ - { - name: "User", - path: ["/administration/users/"], - }, - { - name: "Groups", - path: ["/administration/groups/"], - }, - ], - }, - { - name: "Outposts", - children: [ - { - name: "Outposts", - path: ["/administration/outposts/"], - }, - { - name: "Service Connections", - path: ["/administration/outposts/service_connections/"], - }, - ], - }, - { - name: "Policies", - children: [ - { - name: "Policies", - path: ["/administration/policies/"], - }, - { - name: "Bindings", - path: ["/administration/policies/bindings/"], - }, - ], - }, - { - name: "Property Mappings", - path: ["/administration/property-mappings/"], - }, - { - name: "Flows", - children: [ - { - name: "Flows", - path: ["/administration/flows/"], - }, - { - name: "Stages", - path: ["/administration/stages/"], - }, - { - name: "Prompts", - path: ["/administration/stages/prompts/"], - }, - { - name: "Invitations", - path: ["/administration/stages/invitations/"], - }, - ], - }, - { - name: "Certificates", - path: ["/administration/crypto/certificates/"], - }, - { - name: "Tokens", - path: ["/administration/tokens/"], - }, - ], - condition: (): Promise => { - return User.me().then(u => u.is_superuser); - }, - }, + new SidebarItem("Library", "/library/"), + new SidebarItem("Monitor", "/audit/audit").when((): Promise => { + return User.me().then(u => u.is_superuser); + }), + new SidebarItem("Administration").children( + new SidebarItem("Overview", "/administration/overview-ng/"), + new SidebarItem("System Tasks", "/administration/tasks/"), + new SidebarItem("Applications", "/administration/applications/").activeWhen( + `^/applications/(?${SLUG_REGEX})/$` + ), + new SidebarItem("Sources", "/administration/sources/").activeWhen( + `^/sources/(?${SLUG_REGEX})/$`, + ), + new SidebarItem("Providers", "/administration/providers/"), + new SidebarItem("Flows").children( + new SidebarItem("Flows", "/administration/flows/").activeWhen(`^/flows/(?${SLUG_REGEX})/$`), + new SidebarItem("Stages", "/administration/stages/"), + new SidebarItem("Prompts", "/administration/stages/prompts/"), + new SidebarItem("Invitations", "/administration/stages/invitations/"), + ), + new SidebarItem("User Management").children( + new SidebarItem("User", "/administration/users/"), + new SidebarItem("Groups", "/administration/groups/") + ), + new SidebarItem("Outposts").children( + new SidebarItem("Outposts", "/administration/outposts/"), + new SidebarItem("Service Connections", "/administration/outposts/service_connections/") + ), + new SidebarItem("Policies", "/administration/policies/"), + new SidebarItem("Property Mappings", "/administration/property-mappings"), + new SidebarItem("Certificates", "/administration/crypto/certificates"), + new SidebarItem("Tokens", "/administration/tokens/"), + ).when((): Promise => { + return User.me().then(u => u.is_superuser); + }) ]; @customElement("ak-interface-admin") diff --git a/web/src/interfaces/Interface.ts b/web/src/interfaces/Interface.ts index 44c42724d..44bf02799 100644 --- a/web/src/interfaces/Interface.ts +++ b/web/src/interfaces/Interface.ts @@ -3,7 +3,7 @@ import { html, LitElement, TemplateResult } from "lit-element"; import { SidebarItem } from "../elements/sidebar/Sidebar"; import "../elements/Messages"; -import "../pages/router/RouterOutlet"; +import "../elements/router/RouterOutlet"; export abstract class Interface extends LitElement { diff --git a/web/src/main.ts b/web/src/main.ts index 134d18994..8fb2f45c2 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -13,18 +13,18 @@ import "./elements/sidebar/SidebarUser"; import "./elements/table/TablePagination"; import "./elements/AdminLoginsChart"; +import "./elements/EmptyState"; import "./elements/cards/AggregateCard"; import "./elements/cards/AggregatePromiseCard"; import "./elements/CodeMirror"; import "./elements/Messages"; import "./elements/Spinner"; import "./elements/Tabs"; +import "./elements/router/RouterOutlet"; import "./pages/generic/FlowShellCard"; import "./pages/generic/SiteShell"; -import "./pages/router/RouterOutlet"; - import "./pages/admin-overview/AdminOverviewPage"; import "./pages/admin-overview/TopApplicationsTable"; import "./pages/applications/ApplicationListPage"; diff --git a/web/src/pages/applications/ApplicationListPage.ts b/web/src/pages/applications/ApplicationListPage.ts index e8d190f97..5ab0e17e5 100644 --- a/web/src/pages/applications/ApplicationListPage.ts +++ b/web/src/pages/applications/ApplicationListPage.ts @@ -1,9 +1,12 @@ import { gettext } from "django"; -import { customElement } from "lit-element"; +import { customElement, html, TemplateResult } from "lit-element"; import { Application } from "../../api/application"; import { PBResponse } from "../../api/client"; import { TablePage } from "../../elements/table/TablePage"; +import "../../elements/buttons/ModalButton"; +import "../../elements/buttons/SpinnerButton"; + @customElement("ak-application-list") export class ApplicationList extends TablePage { pageTitle(): string { @@ -27,13 +30,13 @@ export class ApplicationList extends TablePage { return ["Name", "Slug", "Provider", "Provider Type", ""]; } - row(item: Application): string[] { + row(item: Application): TemplateResult[] { return [ - item.name, - item.slug, - item.provider.toString(), - item.provider.toString(), - ` + html`${item.name}`, + html`${item.slug}`, + html`${item.provider}`, + html`${item.provider}`, + html` Edit diff --git a/web/src/pages/applications/ApplicationViewPage.ts b/web/src/pages/applications/ApplicationViewPage.ts index 12e35095d..dd0be9255 100644 --- a/web/src/pages/applications/ApplicationViewPage.ts +++ b/web/src/pages/applications/ApplicationViewPage.ts @@ -1,54 +1,14 @@ import { gettext } from "django"; import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; import { Application } from "../../api/application"; -import { DefaultClient, PBResponse } from "../../api/client"; -import { PolicyBinding } from "../../api/policy_binding"; +import { DefaultClient } from "../../api/client"; import { COMMON_STYLES } from "../../common/styles"; -import { Table } from "../../elements/table/Table"; import "../../elements/Tabs"; import "../../elements/AdminLoginsChart"; - -@customElement("ak-bound-policies-list") -export class BoundPoliciesList extends Table { - @property() - target?: string; - - apiEndpoint(page: number): Promise> { - return DefaultClient.fetch>(["policies", "bindings"], { - target: this.target || "", - ordering: "order", - page: page, - }); - } - - columns(): string[] { - return ["Policy", "Enabled", "Order", "Timeout", ""]; - } - - row(item: PolicyBinding): string[] { - return [ - item.policy_obj.name, - item.enabled ? "Yes" : "No", - item.order.toString(), - item.timeout.toString(), - ` - - - Edit - -
-
- - - Delete - -
-
- `, - ]; - } -} +import "../../elements/buttons/ModalButton"; +import "../../elements/buttons/SpinnerButton"; +import "../../elements/policies/BoundPoliciesList"; @customElement("ak-application-view") export class ApplicationViewPage extends LitElement { @@ -108,7 +68,13 @@ export class ApplicationViewPage extends LitElement {
- +
+
+ ${gettext("These policies control which users can access this application.")} +
+
+ +
`; diff --git a/web/src/pages/flows/BoundStagesList.ts b/web/src/pages/flows/BoundStagesList.ts new file mode 100644 index 000000000..d5e73148f --- /dev/null +++ b/web/src/pages/flows/BoundStagesList.ts @@ -0,0 +1,97 @@ +import { gettext } from "django"; +import { customElement, html, property, TemplateResult } from "lit-element"; +import { PBResponse } from "../../api/client"; +import { Table } from "../../elements/table/Table"; + +import "../../elements/Tabs"; +import "../../elements/AdminLoginsChart"; +import "../../elements/buttons/ModalButton"; +import "../../elements/buttons/SpinnerButton"; +import "../../elements/policies/BoundPoliciesList"; +import { FlowStageBinding } from "../../api/flow"; + +@customElement("ak-bound-stages-list") +export class BoundStagesList extends Table { + expandable = true; + + @property() + target?: string; + + apiEndpoint(page: number): Promise> { + return FlowStageBinding.list({ + target: this.target || "", + ordering: "order", + page: page, + }); + } + + columns(): string[] { + return ["Order", "Name", "Type", ""]; + } + + row(item: FlowStageBinding): TemplateResult[] { + return [ + html`${item.order}`, + html`${item.stage_obj.name}`, + html`${item.stage_obj.verbose_name}`, + html` + + + Edit + +
+
+ + + Delete + +
+
+ `, + ]; + } + + renderExpanded(item: FlowStageBinding): TemplateResult { + return html` + + +
+
+

${gettext("These policies control when this stage will be applied to the flow.")}

+ + +
+
+ + + `; + } + + renderEmpty(): TemplateResult { + return super.renderEmpty(html` +
+ ${gettext("No stages are currently bound to this flow.")} +
+
+ + + ${gettext("Bind Stage")} + +
+
+
+
`); + } + + renderToolbar(): TemplateResult { + return html` + + + ${gettext("Bind Stage")} + +
+
+ ${super.renderToolbar()} + `; + } +} diff --git a/web/src/pages/flows/FlowViewPage.ts b/web/src/pages/flows/FlowViewPage.ts new file mode 100644 index 000000000..dd7df45f9 --- /dev/null +++ b/web/src/pages/flows/FlowViewPage.ts @@ -0,0 +1,71 @@ +import { gettext } from "django"; +import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { COMMON_STYLES } from "../../common/styles"; +import { Flow } from "../../api/flow"; + +import "../../elements/Tabs"; +import "../../elements/AdminLoginsChart"; +import "../../elements/buttons/ModalButton"; +import "../../elements/buttons/SpinnerButton"; +import "../../elements/policies/BoundPoliciesList"; +import "./BoundStagesList"; + +@customElement("ak-flow-view") +export class FlowViewPage extends LitElement { + @property() + set args(value: { [key: string]: string }) { + this.flowSlug = value.slug; + } + + @property() + set flowSlug(value: string) { + Flow.get(value).then((flow) => (this.flow = flow)); + } + + @property({attribute: false}) + flow?: Flow; + + static get styles(): CSSResult[] { + return COMMON_STYLES.concat( + css` + img.pf-icon { + max-height: 24px; + } + ` + ); + } + + render(): TemplateResult { + if (!this.flow) { + return html``; + } + return html`
+
+

+ + ${this.flow?.name} +

+

${this.flow?.title}

+
+
+ +
+
+ + +
+
+
+
+
+
+ ${gettext("These policies control which users can access this flow.")} +
+
+ + +
+
+
`; + } +} diff --git a/web/src/pages/sources/SourceViewPage.ts b/web/src/pages/sources/SourceViewPage.ts new file mode 100644 index 000000000..89fde1e08 --- /dev/null +++ b/web/src/pages/sources/SourceViewPage.ts @@ -0,0 +1,63 @@ +import { gettext } from "django"; +import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { COMMON_STYLES } from "../../common/styles"; + +import "../../elements/Tabs"; +import "../../elements/AdminLoginsChart"; +import "../../elements/buttons/ModalButton"; +import "../../elements/buttons/SpinnerButton"; +import "../../elements/policies/BoundPoliciesList"; +import { Source } from "../../api/source"; + +@customElement("ak-source-view") +export class SourceViewPage extends LitElement { + @property() + set args(value: { [key: string]: string }) { + this.sourceSlug = value.slug; + } + + @property() + set sourceSlug(value: string) { + Source.get(value).then((source) => (this.source = source)); + } + + @property({attribute: false}) + source?: Source; + + static get styles(): CSSResult[] { + return COMMON_STYLES.concat( + css` + img.pf-icon { + max-height: 24px; + } + ` + ); + } + + render(): TemplateResult { + if (!this.source) { + return html``; + } + return html`
+
+

+ + ${this.source?.name} +

+
+
+ +
+
+
+
+ ${gettext("These policies control which users can access this application.")} +
+
+ + +
+
+
`; + } +} diff --git a/web/src/routes.ts b/web/src/routes.ts index cc5c29ea0..9c1620fde 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -1,10 +1,12 @@ import { html } from "lit-html"; -import { Route, SLUG_REGEX } from "./pages/router/Route"; +import { Route, SLUG_REGEX } from "./elements/router/Route"; import "./pages/LibraryPage"; import "./pages/admin-overview/AdminOverviewPage"; import "./pages/applications/ApplicationListPage"; import "./pages/applications/ApplicationViewPage"; +import "./pages/sources/SourceViewPage"; +import "./pages/flows/FlowViewPage"; export const ROUTES: Route[] = [ // Prevent infinite Shell loops @@ -16,4 +18,10 @@ export const ROUTES: Route[] = [ new Route(new RegExp(`^/applications/(?${SLUG_REGEX})/$`)).then((args) => { return html``; }), + new Route(new RegExp(`^/sources/(?${SLUG_REGEX})/$`)).then((args) => { + return html``; + }), + new Route(new RegExp(`^/flows/(?${SLUG_REGEX})/$`)).then((args) => { + return html``; + }), ];