From 95df7c7f302d11231cff74be0385e0a25b6fa37d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Feb 2021 09:22:59 +0100 Subject: [PATCH 01/13] build(deps): bump construct-style-sheets-polyfill in /web (#540) Bumps [construct-style-sheets-polyfill](https://github.com/calebdwilliams/adoptedStyleSheets) from 2.4.6 to 2.4.9. - [Release notes](https://github.com/calebdwilliams/adoptedStyleSheets/releases) - [Changelog](https://github.com/calebdwilliams/construct-style-sheets/blob/master/CHANGELOG.md) - [Commits](https://github.com/calebdwilliams/adoptedStyleSheets/compare/v2.4.6...v2.4.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- web/package-lock.json | 6 +++--- web/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index f87a1f300..e56dabacb 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -899,9 +899,9 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "construct-style-sheets-polyfill": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-2.4.6.tgz", - "integrity": "sha512-lU0to7dFDjKslMF+M5NUa4s0RQMBRVyZMXvD/vp7vmjdEPgziTkHSfZHQxfoIvVWajWRJUVJMLfrMwcx8fTh4A==" + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/construct-style-sheets-polyfill/-/construct-style-sheets-polyfill-2.4.9.tgz", + "integrity": "sha512-kPXZXxsp7CTr/Vs29+omUA29wTrFplkdY6jqxyv0DDWC5Ro79WmwpboH2M9KiOclbtn8r81GCFtc7+t7OjRnCw==" }, "copy-descriptor": { "version": "0.1.1", diff --git a/web/package.json b/web/package.json index 333f056d5..fd0fc3eb2 100644 --- a/web/package.json +++ b/web/package.json @@ -18,7 +18,7 @@ "@types/codemirror": "0.0.108", "chart.js": "^2.9.4", "codemirror": "^5.59.2", - "construct-style-sheets-polyfill": "^2.4.6", + "construct-style-sheets-polyfill": "^2.4.9", "flowchart.js": "^1.15.0", "lit-element": "^2.4.0", "lit-html": "^1.3.0", From cd5631ec7688382416e5fb6a49fa429ca194a7c7 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sun, 7 Feb 2021 22:37:41 +0100 Subject: [PATCH 02/13] admin: fix link in source list --- authentik/admin/templates/administration/source/list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authentik/admin/templates/administration/source/list.html b/authentik/admin/templates/administration/source/list.html index e9af3c44e..f29089409 100644 --- a/authentik/admin/templates/administration/source/list.html +++ b/authentik/admin/templates/administration/source/list.html @@ -63,7 +63,7 @@ {% for source in object_list %} - +
{{ source.name }}
{% if not source.enabled %} {% trans 'Disabled' %} From 3ced67b151008a5cfa4cb61025405d2120812414 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 10:15:59 +0100 Subject: [PATCH 03/13] sources/*: simplify source api --- authentik/admin/forms/source.py | 19 ------------------ authentik/core/api/sources.py | 25 +++++++++++++----------- authentik/sources/ldap/api.py | 6 +++--- authentik/sources/oauth/api.py | 8 +++----- authentik/sources/oauth/forms.py | 8 ++++++-- authentik/sources/saml/api.py | 8 +++----- authentik/sources/saml/forms.py | 8 ++++++-- swagger.yaml | 33 ++++++++++++++++++++++++++++---- web/src/api/Client.ts | 7 +++++++ web/src/api/Policies.ts | 6 ++++-- web/src/api/Providers.ts | 4 ++-- web/src/api/Sources.ts | 10 ++++++++-- 12 files changed, 85 insertions(+), 57 deletions(-) delete mode 100644 authentik/admin/forms/source.py diff --git a/authentik/admin/forms/source.py b/authentik/admin/forms/source.py deleted file mode 100644 index 0a9b50065..000000000 --- a/authentik/admin/forms/source.py +++ /dev/null @@ -1,19 +0,0 @@ -"""authentik core source form fields""" - -SOURCE_FORM_FIELDS = [ - "name", - "slug", - "enabled", - "authentication_flow", - "enrollment_flow", -] -SOURCE_SERIALIZER_FIELDS = [ - "pk", - "name", - "slug", - "enabled", - "authentication_flow", - "enrollment_flow", - "verbose_name", - "verbose_name_plural", -] diff --git a/authentik/core/api/sources.py b/authentik/core/api/sources.py index 8e41a7816..3ab5f10a9 100644 --- a/authentik/core/api/sources.py +++ b/authentik/core/api/sources.py @@ -2,7 +2,6 @@ from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.viewsets import ReadOnlyModelViewSet -from authentik.admin.forms.source import SOURCE_SERIALIZER_FIELDS from authentik.core.api.utils import MetaNameSerializer from authentik.core.models import Source @@ -10,22 +9,26 @@ from authentik.core.models import Source class SourceSerializer(ModelSerializer, MetaNameSerializer): """Source Serializer""" - __type__ = SerializerMethodField(method_name="get_type") + object_type = SerializerMethodField() - def get_type(self, obj): + def get_object_type(self, obj): """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 + return obj._meta.object_name.lower().replace("provider", "") class Meta: model = Source - fields = SOURCE_SERIALIZER_FIELDS + ["__type__"] + fields = SOURCE_SERIALIZER_FIELDS = [ + "pk", + "name", + "slug", + "enabled", + "authentication_flow", + "enrollment_flow", + "object_type", + "verbose_name", + "verbose_name_plural", + ] class SourceViewSet(ReadOnlyModelViewSet): diff --git a/authentik/sources/ldap/api.py b/authentik/sources/ldap/api.py index bd95686e3..c22de70e2 100644 --- a/authentik/sources/ldap/api.py +++ b/authentik/sources/ldap/api.py @@ -2,17 +2,17 @@ from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet -from authentik.admin.forms.source import SOURCE_SERIALIZER_FIELDS +from authentik.core.api.sources import SourceSerializer from authentik.core.api.utils import MetaNameSerializer from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource -class LDAPSourceSerializer(ModelSerializer, MetaNameSerializer): +class LDAPSourceSerializer(SourceSerializer): """LDAP Source Serializer""" class Meta: model = LDAPSource - fields = SOURCE_SERIALIZER_FIELDS + [ + fields = SourceSerializer.Meta.fields + [ "server_uri", "bind_cn", "bind_password", diff --git a/authentik/sources/oauth/api.py b/authentik/sources/oauth/api.py index e9718480a..c58149005 100644 --- a/authentik/sources/oauth/api.py +++ b/authentik/sources/oauth/api.py @@ -1,18 +1,16 @@ """OAuth Source Serializer""" -from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet -from authentik.admin.forms.source import SOURCE_SERIALIZER_FIELDS -from authentik.core.api.utils import MetaNameSerializer +from authentik.core.api.sources import SourceSerializer from authentik.sources.oauth.models import OAuthSource -class OAuthSourceSerializer(ModelSerializer, MetaNameSerializer): +class OAuthSourceSerializer(SourceSerializer): """OAuth Source Serializer""" class Meta: model = OAuthSource - fields = SOURCE_SERIALIZER_FIELDS + [ + fields = SourceSerializer.Meta.fields + [ "provider_type", "request_token_url", "authorization_url", diff --git a/authentik/sources/oauth/forms.py b/authentik/sources/oauth/forms.py index 7018c096d..69ee0e41d 100644 --- a/authentik/sources/oauth/forms.py +++ b/authentik/sources/oauth/forms.py @@ -2,7 +2,6 @@ from django import forms -from authentik.admin.forms.source import SOURCE_FORM_FIELDS from authentik.flows.models import Flow, FlowDesignation from authentik.sources.oauth.models import OAuthSource from authentik.sources.oauth.types.manager import MANAGER @@ -27,7 +26,12 @@ class OAuthSourceForm(forms.ModelForm): class Meta: model = OAuthSource - fields = SOURCE_FORM_FIELDS + [ + fields = [ + "name", + "slug", + "enabled", + "authentication_flow", + "enrollment_flow", "provider_type", "request_token_url", "authorization_url", diff --git a/authentik/sources/saml/api.py b/authentik/sources/saml/api.py index 408ad3b1d..37da99206 100644 --- a/authentik/sources/saml/api.py +++ b/authentik/sources/saml/api.py @@ -1,19 +1,17 @@ """SAMLSource API Views""" -from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet -from authentik.admin.forms.source import SOURCE_FORM_FIELDS -from authentik.core.api.utils import MetaNameSerializer +from authentik.core.api.sources import SourceSerializer from authentik.sources.saml.models import SAMLSource -class SAMLSourceSerializer(ModelSerializer, MetaNameSerializer): +class SAMLSourceSerializer(SourceSerializer): """SAMLSource Serializer""" class Meta: model = SAMLSource - fields = SOURCE_FORM_FIELDS + [ + fields = SourceSerializer.Meta.fields + [ "issuer", "sso_url", "slo_url", diff --git a/authentik/sources/saml/forms.py b/authentik/sources/saml/forms.py index bd2fdcf9f..b33ae455c 100644 --- a/authentik/sources/saml/forms.py +++ b/authentik/sources/saml/forms.py @@ -2,7 +2,6 @@ from django import forms -from authentik.admin.forms.source import SOURCE_FORM_FIELDS from authentik.crypto.models import CertificateKeyPair from authentik.flows.models import Flow, FlowDesignation from authentik.sources.saml.models import SAMLSource @@ -28,7 +27,12 @@ class SAMLSourceForm(forms.ModelForm): class Meta: model = SAMLSource - fields = SOURCE_FORM_FIELDS + [ + fields = [ + "name", + "slug", + "enabled", + "authentication_flow", + "enrollment_flow", "issuer", "sso_url", "slo_url", diff --git a/swagger.yaml b/swagger.yaml index ac497df95..799164642 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -9157,6 +9157,10 @@ definitions: type: string format: uuid x-nullable: true + object_type: + title: Object type + type: string + readOnly: true verbose_name: title: Verbose name type: string @@ -9165,10 +9169,6 @@ definitions: title: Verbose name plural type: string readOnly: true - __type__: - title: 'type ' - type: string - readOnly: true LDAPSource: description: LDAP Source Serializer required: @@ -9213,6 +9213,10 @@ definitions: type: string format: uuid x-nullable: true + object_type: + title: Object type + type: string + readOnly: true verbose_name: title: Verbose name type: string @@ -9344,6 +9348,10 @@ definitions: type: string format: uuid x-nullable: true + object_type: + title: Object type + type: string + readOnly: true verbose_name: title: Verbose name type: string @@ -9397,6 +9405,11 @@ definitions: - sso_url type: object properties: + pk: + title: Pbm uuid + type: string + format: uuid + readOnly: true name: title: Name description: Source's display Name. @@ -9425,6 +9438,18 @@ definitions: type: string format: uuid x-nullable: true + object_type: + title: Object type + type: string + readOnly: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true issuer: title: Issuer description: Also known as Entity ID. Defaults the Metadata URL. diff --git a/web/src/api/Client.ts b/web/src/api/Client.ts index 3556b0c88..4632fa17d 100644 --- a/web/src/api/Client.ts +++ b/web/src/api/Client.ts @@ -7,6 +7,13 @@ export interface QueryArguments { [key: string]: number | string | boolean | null; } +export interface BaseInheritanceModel { + + verbose_name: string; + verbose_name_plural: string; + +} + export class Client { makeUrl(url: string[], query?: QueryArguments): string { let builtUrl = `/api/${VERSION}/${url.join("/")}/`; diff --git a/web/src/api/Policies.ts b/web/src/api/Policies.ts index 1c3a26738..3273ddb77 100644 --- a/web/src/api/Policies.ts +++ b/web/src/api/Policies.ts @@ -1,12 +1,14 @@ -import { DefaultClient, PBResponse, QueryArguments } from "./Client"; +import { DefaultClient, BaseInheritanceModel, PBResponse, QueryArguments } from "./Client"; -export class Policy { +export class Policy implements BaseInheritanceModel { pk: string; name: string; constructor() { throw Error(); } + verbose_name: string; + verbose_name_plural: string; static get(pk: string): Promise { return DefaultClient.fetch(["policies", "all", pk]); diff --git a/web/src/api/Providers.ts b/web/src/api/Providers.ts index 3811db82d..7444aeb0c 100644 --- a/web/src/api/Providers.ts +++ b/web/src/api/Providers.ts @@ -1,6 +1,6 @@ -import { DefaultClient, PBResponse, QueryArguments } from "./Client"; +import { BaseInheritanceModel, DefaultClient, PBResponse, QueryArguments } from "./Client"; -export class Provider { +export class Provider implements BaseInheritanceModel { pk: number; name: string; authorization_flow: string; diff --git a/web/src/api/Sources.ts b/web/src/api/Sources.ts index 346ecf3c6..c2d184dcc 100644 --- a/web/src/api/Sources.ts +++ b/web/src/api/Sources.ts @@ -1,6 +1,6 @@ -import { DefaultClient, PBResponse, QueryArguments } from "./Client"; +import { BaseInheritanceModel, DefaultClient, PBResponse, QueryArguments } from "./Client"; -export class Source { +export class Source implements BaseInheritanceModel { pk: string; name: string; slug: string; @@ -11,6 +11,8 @@ export class Source { constructor() { throw Error(); } + verbose_name: string; + verbose_name_plural: string; static get(slug: string): Promise { return DefaultClient.fetch(["sources", "all", slug]); @@ -19,4 +21,8 @@ export class Source { static list(filter?: QueryArguments): Promise> { return DefaultClient.fetch>(["sources", "all"], filter); } + + static adminUrl(rest: string): string { + return `/administration/sources/${rest}`; + } } From f8abe3e210f2779462cfa1dd19474a8f0fa2ee59 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 11:50:26 +0100 Subject: [PATCH 04/13] providers/oauth2: add unofficial groups attribute to default profile claim --- authentik/providers/oauth2/managed.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/authentik/providers/oauth2/managed.py b/authentik/providers/oauth2/managed.py index 61e9efd04..873640942 100644 --- a/authentik/providers/oauth2/managed.py +++ b/authentik/providers/oauth2/managed.py @@ -23,6 +23,8 @@ return { "family_name": "", "preferred_username": user.username, "nickname": user.username, + # groups is not part of the official userinfo schema, but is a quasi-standard + "groups": [group.name for group in user.ak_groups.all()], } """ From fe4b2d1a34942d33dc30abfe24ed8ac4ea5d12a5 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 11:51:38 +0100 Subject: [PATCH 05/13] providers/oauth2: add authorized scopes to AUTHORIZE_APPLICATION event --- authentik/providers/oauth2/views/authorize.py | 1 + web/src/pages/events/EventInfo.ts | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/authentik/providers/oauth2/views/authorize.py b/authentik/providers/oauth2/views/authorize.py index c5dbda69e..7393ee6bc 100644 --- a/authentik/providers/oauth2/views/authorize.py +++ b/authentik/providers/oauth2/views/authorize.py @@ -253,6 +253,7 @@ class OAuthFulfillmentStage(StageView): EventAction.AUTHORIZE_APPLICATION, authorized_application=application, flow=self.executor.plan.flow_pk, + scopes=", ".join(self.params.scope), ).from_http(self.request) return redirect(self.create_response_uri()) except (ClientIdError, RedirectUriError) as error: diff --git a/web/src/pages/events/EventInfo.ts b/web/src/pages/events/EventInfo.ts index 2a1f73c22..a92c97efe 100644 --- a/web/src/pages/events/EventInfo.ts +++ b/web/src/pages/events/EventInfo.ts @@ -65,13 +65,13 @@ export class EventInfo extends LitElement { case "model_updated": case "model_deleted": return html` -

${gettext("Affected model:")}


+

${gettext("Affected model:")}

${this.getModelInfo(this.event.context.model as EventContext)} `; case "authorize_application": return html`
-

${gettext("Authorized application:")}


+

${gettext("Authorized application:")}

${this.getModelInfo(this.event.context.authorized_application as EventContext)}
@@ -83,14 +83,15 @@ export class EventInfo extends LitElement { }), html``)}
-
`; + + ${this.defaultResponse()}`; case "login_failed": return html`

${gettext(`Attempted to log in as ${this.event.context.username}`)}

${this.defaultResponse()}`; case "token_view": return html` -

${gettext("Token:")}


+

${gettext("Token:")}

${this.getModelInfo(this.event.context.token as EventContext)}`; case "property_mapping_exception": return html`
From 9fac51f8c7e4524f04c066cde53d617d285f585c Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 17:43:42 +0100 Subject: [PATCH 06/13] outpost: downgrade recws for now see https://github.com/recws-org/recws/issues/29 --- outpost/go.mod | 2 +- outpost/pkg/ak/api_ws.go | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/outpost/go.mod b/outpost/go.mod index d1c621008..8e7413075 100644 --- a/outpost/go.mod +++ b/outpost/go.mod @@ -24,7 +24,7 @@ require ( github.com/pelletier/go-toml v1.8.1 // indirect github.com/pkg/errors v0.9.1 github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f // indirect - github.com/recws-org/recws v1.2.2 + github.com/recws-org/recws v1.2.1 github.com/sirupsen/logrus v1.7.0 github.com/spf13/afero v1.5.1 // indirect github.com/spf13/cast v1.3.1 // indirect diff --git a/outpost/pkg/ak/api_ws.go b/outpost/pkg/ak/api_ws.go index b1286150c..93eace7f3 100644 --- a/outpost/pkg/ak/api_ws.go +++ b/outpost/pkg/ak/api_ws.go @@ -69,23 +69,10 @@ func (ac *APIController) Shutdown() { } func (ac *APIController) startWSHandler() { - notConnectedBackoff := 1 logger := ac.logger.WithField("loop", "ws-handler") for { if !ac.wsConn.IsConnected() { - notConnectedWait := time.Duration(notConnectedBackoff) * time.Second - logger.WithField("wait", notConnectedWait).Info("Not connected, trying again...") - time.Sleep(notConnectedWait) - notConnectedBackoff += notConnectedBackoff - // Limit backoff to max 60 seconds - if notConnectedBackoff >= 60 { - notConnectedBackoff = 60 - } - ac.wsConn.CloseAndReconnect() continue - } else { - // When we're connected, reset backoff to 1 - notConnectedBackoff = 1 } var wsMsg websocketMessage err := ac.wsConn.ReadJSON(&wsMsg) From efc46f52e61732b5e5d60a665dd510a328401eb0 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 19:01:10 +0100 Subject: [PATCH 07/13] outposts: move health to API --- authentik/api/v2/urls.py | 6 +- authentik/outposts/api/__init__.py | 0 .../outpost_service_connections.py} | 22 +-- authentik/outposts/api/outposts.py | 71 +++++++ authentik/outposts/models.py | 9 - .../templates/outposts/deployment_modal.html | 43 ----- authentik/providers/oauth2/api.py | 4 +- authentik/providers/saml/api.py | 4 +- swagger.yaml | 179 +++++++++++++++++- 9 files changed, 265 insertions(+), 73 deletions(-) create mode 100644 authentik/outposts/api/__init__.py rename authentik/outposts/{api.py => api/outpost_service_connections.py} (73%) create mode 100644 authentik/outposts/api/outposts.py delete mode 100644 authentik/outposts/templates/outposts/deployment_modal.html diff --git a/authentik/api/v2/urls.py b/authentik/api/v2/urls.py index 34eb63470..71e27a9cf 100644 --- a/authentik/api/v2/urls.py +++ b/authentik/api/v2/urls.py @@ -29,11 +29,12 @@ from authentik.flows.api import ( FlowViewSet, StageViewSet, ) -from authentik.outposts.api import ( +from authentik.outposts.api.outpost_service_connections import ( DockerServiceConnectionViewSet, KubernetesServiceConnectionViewSet, - OutpostViewSet, + ServiceConnectionViewSet, ) +from authentik.outposts.api.outposts import OutpostViewSet from authentik.policies.api import ( PolicyBindingViewSet, PolicyCacheViewSet, @@ -88,6 +89,7 @@ router.register("core/users", UserViewSet) router.register("core/tokens", TokenViewSet) router.register("outposts/outposts", OutpostViewSet) +router.register("outposts/service_connections/all", ServiceConnectionViewSet) router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet) router.register( "outposts/service_connections/kubernetes", KubernetesServiceConnectionViewSet diff --git a/authentik/outposts/api/__init__.py b/authentik/outposts/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/outposts/api.py b/authentik/outposts/api/outpost_service_connections.py similarity index 73% rename from authentik/outposts/api.py rename to authentik/outposts/api/outpost_service_connections.py index d6815a955..46f142ae6 100644 --- a/authentik/outposts/api.py +++ b/authentik/outposts/api/outpost_service_connections.py @@ -1,30 +1,28 @@ """Outpost API Views""" -from rest_framework.serializers import JSONField, ModelSerializer +from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet from authentik.outposts.models import ( DockerServiceConnection, KubernetesServiceConnection, - Outpost, + OutpostServiceConnection, ) -class OutpostSerializer(ModelSerializer): - """Outpost Serializer""" - - _config = JSONField() +class ServiceConnectionSerializer(ModelSerializer): + """ServiceConnection Serializer""" class Meta: - model = Outpost - fields = ["pk", "name", "providers", "service_connection", "_config"] + model = OutpostServiceConnection + fields = ["pk", "name"] -class OutpostViewSet(ModelViewSet): - """Outpost Viewset""" +class ServiceConnectionViewSet(ModelViewSet): + """ServiceConnection Viewset""" - queryset = Outpost.objects.all() - serializer_class = OutpostSerializer + queryset = OutpostServiceConnection.objects.all() + serializer_class = ServiceConnectionSerializer class DockerServiceConnectionSerializer(ModelSerializer): diff --git a/authentik/outposts/api/outposts.py b/authentik/outposts/api/outposts.py new file mode 100644 index 000000000..c3c054620 --- /dev/null +++ b/authentik/outposts/api/outposts.py @@ -0,0 +1,71 @@ +"""Outpost API Views""" +from django.db.models import Model +from drf_yasg2.utils import swagger_auto_schema +from rest_framework.decorators import action +from rest_framework.fields import BooleanField, CharField, DateTimeField +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.serializers import JSONField, ModelSerializer, Serializer +from rest_framework.viewsets import ModelViewSet + +from authentik.core.api.providers import ProviderSerializer +from authentik.outposts.models import Outpost + + +class OutpostSerializer(ModelSerializer): + """Outpost Serializer""" + + _config = JSONField() + providers = ProviderSerializer(many=True, read_only=True) + + class Meta: + + model = Outpost + fields = [ + "pk", + "name", + "providers", + "service_connection", + "token_identifier", + "_config", + ] + + +class OutpostHealthSerializer(Serializer): + """Outpost health status""" + + last_seen = DateTimeField(read_only=True) + version = CharField(read_only=True) + version_should = CharField(read_only=True) + version_outdated = BooleanField(read_only=True) + + def create(self, validated_data: dict) -> Model: + raise NotImplementedError + + def update(self, instance: Model, validated_data: dict) -> Model: + raise NotImplementedError + + +class OutpostViewSet(ModelViewSet): + """Outpost Viewset""" + + queryset = Outpost.objects.all() + serializer_class = OutpostSerializer + + @swagger_auto_schema(responses={200: OutpostHealthSerializer(many=True)}) + @action(methods=["GET"], detail=True) + # pylint: disable=invalid-name, unused-argument + def health(self, request: Request, pk: int) -> Response: + """Get outposts current health""" + outpost: Outpost = self.get_object() + states = [] + for state in outpost.state: + states.append( + { + "last_seen": state.last_seen, + "version": state.version, + "version_should": state.version_should, + "version_outdated": state.version_outdated, + } + ) + return Response(OutpostHealthSerializer(states, many=True).data) diff --git a/authentik/outposts/models.py b/authentik/outposts/models.py index be9a359fb..eb2056dff 100644 --- a/authentik/outposts/models.py +++ b/authentik/outposts/models.py @@ -9,7 +9,6 @@ from django.core.cache import cache from django.db import models, transaction from django.db.models.base import Model from django.forms.models import ModelForm -from django.http import HttpRequest from django.utils.translation import gettext_lazy as _ from docker.client import DockerClient from docker.errors import DockerException @@ -33,7 +32,6 @@ from authentik.crypto.models import CertificateKeyPair from authentik.lib.config import CONFIG from authentik.lib.models import InheritanceForeignKey from authentik.lib.sentry import SentryIgnoredException -from authentik.lib.utils.template import render_to_string from authentik.outposts.docker_tls import DockerInlineTLS OUR_VERSION = parse(__version__) @@ -378,13 +376,6 @@ class Outpost(models.Model): objects.append(provider) return objects - def html_deployment_view(self, request: HttpRequest) -> Optional[str]: - """return template and context modal to view token and other config info""" - return render_to_string( - "outposts/deployment_modal.html", - {"outpost": self, "full_url": request.build_absolute_uri("/")}, - ) - def __str__(self) -> str: return f"Outpost {self.name}" diff --git a/authentik/outposts/templates/outposts/deployment_modal.html b/authentik/outposts/templates/outposts/deployment_modal.html deleted file mode 100644 index cbd29db90..000000000 --- a/authentik/outposts/templates/outposts/deployment_modal.html +++ /dev/null @@ -1,43 +0,0 @@ -{% load i18n %} - - - -
-
-

{% trans 'Outpost Deployment Info' %}

-
-
- -
- diff --git a/authentik/providers/oauth2/api.py b/authentik/providers/oauth2/api.py index c58270764..13edd2c66 100644 --- a/authentik/providers/oauth2/api.py +++ b/authentik/providers/oauth2/api.py @@ -59,11 +59,11 @@ class OAuth2ProviderViewSet(ModelViewSet): queryset = OAuth2Provider.objects.all() serializer_class = OAuth2ProviderSerializer - @action(methods=["GET"], detail=True) @swagger_auto_schema(responses={200: OAuth2ProviderSetupURLs(many=False)}) + @action(methods=["GET"], detail=True) # pylint: disable=invalid-name def setup_urls(self, request: Request, pk: int) -> str: - """Return metadata as XML string""" + """Get Providers setup URLs""" provider = get_object_or_404(OAuth2Provider, pk=pk) data = { "issuer": provider.get_issuer(request), diff --git a/authentik/providers/saml/api.py b/authentik/providers/saml/api.py index 11d02bf62..1163afe95 100644 --- a/authentik/providers/saml/api.py +++ b/authentik/providers/saml/api.py @@ -54,10 +54,10 @@ class SAMLProviderViewSet(ModelViewSet): queryset = SAMLProvider.objects.all() serializer_class = SAMLProviderSerializer - @action(methods=["GET"], detail=True) @swagger_auto_schema(responses={200: SAMLMetadataSerializer(many=False)}) + @action(methods=["GET"], detail=True) # pylint: disable=invalid-name - def metadata(self, request: Request, pk: int) -> str: + def metadata(self, request: Request, pk: int) -> Response: """Return metadata as XML string""" provider = get_object_or_404(SAMLProvider, pk=pk) metadata = DescriptorDownloadView.get_metadata(request, provider) diff --git a/swagger.yaml b/swagger.yaml index 799164642..75d42d415 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -1911,6 +1911,28 @@ paths: required: true type: string format: uuid + /outposts/outposts/{uuid}/health/: + get: + operationId: outposts_outposts_health + description: Outpost Viewset + parameters: [] + responses: + '200': + description: '' + schema: + description: '' + type: array + items: + $ref: '#/definitions/OutpostHealth' + tags: + - outposts + parameters: + - name: uuid + in: path + description: A UUID string identifying this outpost. + required: true + type: string + format: uuid /outposts/proxy/: get: operationId: outposts_proxy_list @@ -2037,6 +2059,133 @@ paths: description: A unique integer value identifying this Proxy Provider. required: true type: integer + /outposts/service_connections/all/: + get: + operationId: outposts_service_connections_all_list + description: ServiceConnection Viewset + parameters: + - name: ordering + in: query + description: Which field to use when ordering the results. + required: false + type: string + - name: search + in: query + description: A search term. + required: false + type: string + - name: page + in: query + description: A page number within the paginated result set. + required: false + type: integer + - name: page_size + in: query + description: Number of results to return per page. + required: false + type: integer + responses: + '200': + description: '' + schema: + required: + - count + - results + type: object + properties: + count: + type: integer + next: + type: string + format: uri + x-nullable: true + previous: + type: string + format: uri + x-nullable: true + results: + type: array + items: + $ref: '#/definitions/ServiceConnection' + tags: + - outposts + post: + operationId: outposts_service_connections_all_create + description: ServiceConnection Viewset + parameters: + - name: data + in: body + required: true + schema: + $ref: '#/definitions/ServiceConnection' + responses: + '201': + description: '' + schema: + $ref: '#/definitions/ServiceConnection' + tags: + - outposts + parameters: [] + /outposts/service_connections/all/{uuid}/: + get: + operationId: outposts_service_connections_all_read + description: ServiceConnection Viewset + parameters: [] + responses: + '200': + description: '' + schema: + $ref: '#/definitions/ServiceConnection' + tags: + - outposts + put: + operationId: outposts_service_connections_all_update + description: ServiceConnection Viewset + parameters: + - name: data + in: body + required: true + schema: + $ref: '#/definitions/ServiceConnection' + responses: + '200': + description: '' + schema: + $ref: '#/definitions/ServiceConnection' + tags: + - outposts + patch: + operationId: outposts_service_connections_all_partial_update + description: ServiceConnection Viewset + parameters: + - name: data + in: body + required: true + schema: + $ref: '#/definitions/ServiceConnection' + responses: + '200': + description: '' + schema: + $ref: '#/definitions/ServiceConnection' + tags: + - outposts + delete: + operationId: outposts_service_connections_all_delete + description: ServiceConnection Viewset + parameters: [] + responses: + '204': + description: '' + tags: + - outposts + parameters: + - name: uuid + in: path + description: A UUID string identifying this Outpost Service-Connection. + required: true + type: string + format: uuid /outposts/service_connections/docker/: get: operationId: outposts_service_connections_docker_list @@ -8005,7 +8154,6 @@ definitions: description: Outpost Serializer required: - name - - providers - _config type: object properties: @@ -8019,10 +8167,11 @@ definitions: type: string minLength: 1 providers: + description: '' type: array items: - type: integer - uniqueItems: true + $ref: '#/definitions/Provider' + readOnly: true service_connection: title: Service connection description: Select Service-Connection authentik should use to manage this @@ -8033,6 +8182,15 @@ definitions: _config: title: config type: object + OutpostHealth: + description: '' + type: object + properties: + last_seen: + title: Last seen + type: string + format: date-time + readOnly: true OpenIDConnectConfiguration: title: Oidc configuration description: rest_framework Serializer for OIDC Configuration @@ -8170,6 +8328,21 @@ definitions: description: User/Group Attribute used for the user part of the HTTP-Basic Header. If not set, the user's Email address is used. type: string + ServiceConnection: + description: ServiceConnection Serializer + required: + - name + type: object + properties: + pk: + title: Uuid + type: string + format: uuid + readOnly: true + name: + title: Name + type: string + minLength: 1 DockerServiceConnection: description: DockerServiceConnection Serializer required: From 5d460a2537c513b1565927a783354425f474f3b3 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 19:02:39 +0100 Subject: [PATCH 08/13] admin: remove outposts list --- .../administration/outpost/list.html | 149 ------------------ .../administration/property_mapping/list.html | 139 ---------------- .../admin/templatetags/admin_reflection.py | 28 ---- authentik/admin/urls.py | 5 - authentik/admin/views/outposts.py | 28 +--- 5 files changed, 5 insertions(+), 344 deletions(-) delete mode 100644 authentik/admin/templates/administration/outpost/list.html delete mode 100644 authentik/admin/templates/administration/property_mapping/list.html diff --git a/authentik/admin/templates/administration/outpost/list.html b/authentik/admin/templates/administration/outpost/list.html deleted file mode 100644 index 2af849395..000000000 --- a/authentik/admin/templates/administration/outpost/list.html +++ /dev/null @@ -1,149 +0,0 @@ -{% extends "administration/base.html" %} - -{% load i18n %} -{% load humanize %} -{% load authentik_utils %} -{% load admin_reflection %} - -{% block content %} -
-
-

- - {% trans 'Outposts' %} -

-

{% trans "Outposts are deployments of authentik components to support different environments and protocols, like reverse proxies." %}

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - {% trans 'Create' %} - -
-
- -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - - - {% for outpost in object_list %} - - - - {% with states=outpost.state %} - {% if states|length > 0 %} - - - {% else %} - - - {% endif %} - {% endwith %} - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Providers' %}{% trans 'Health' %}{% trans 'Version' %}
- {{ outpost.name }} - - - {{ outpost.providers.all.select_subclasses|join:", " }} - - - {% for state in states %} -
- {% if state.last_seen %} - {{ state.last_seen|naturaltime }} - {% else %} - {% trans 'Unhealthy' %} - {% endif %} -
- {% endfor %} -
- {% for state in states %} -
- {% if not state.version %} - - {% elif state.version_outdated %} - {% blocktrans with is=state.version should=state.version_should %}{{ is }}, should be {{ should }}{% endblocktrans %} - {% else %} - {{ state.version }} - {% endif %} -
- {% endfor %} -
- - - - - - - {% trans 'Edit' %} - -
-
- - - {% trans 'Delete' %} - -
-
- {% get_htmls outpost as htmls %} - {% for html in htmls %} - {{ html|safe }} - {% endfor %} -
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

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

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

- - {% trans 'Property Mappings' %} -

-

{% trans "Control how authentik exposes and interprets information." %} -

-
-
-
-
- {% if object_list %} -
-
- {% include 'partials/toolbar_search.html' %} -
- - - - - -
- {% include 'partials/pagination.html' %} -
-
- - - - - - - - - - {% for property_mapping in object_list %} - - - - - - {% endfor %} - -
{% trans 'Name' %}{% trans 'Type' %}
- - {{ property_mapping.name }} - - - - {{ property_mapping|verbose_name }} - - - - - {% trans 'Edit' %} - -
-
- - - {% trans 'Delete' %} - -
-
-
-
- {% include 'partials/pagination.html' %} -
- {% else %} -
-
- {% include 'partials/toolbar_search.html' %} -
-
-
-
- -

- {% trans 'No Property Mappings.' %} -

-
- {% if request.GET.search != "" %} - {% trans "Your search query doesn't match any property mappings." %} - {% else %} - {% trans 'Currently no property mappings exist. Click the button below to create one.' %} - {% endif %} -
- - - - -
-
- {% endif %} -
-
-{% endblock %} diff --git a/authentik/admin/templatetags/admin_reflection.py b/authentik/admin/templatetags/admin_reflection.py index 4f4dae144..848e9c1f8 100644 --- a/authentik/admin/templatetags/admin_reflection.py +++ b/authentik/admin/templatetags/admin_reflection.py @@ -1,7 +1,6 @@ """authentik admin templatetags""" from django import template from django.db.models import Model -from django.utils.html import mark_safe from structlog.stdlib import get_logger register = template.Library() @@ -33,30 +32,3 @@ def get_links(model_instance): pass return links - - -@register.simple_tag(takes_context=True) -def get_htmls(context, model_instance): - """Find all html_ methods on an object instance, run them and return as dict""" - prefix = "html_" - htmls = [] - - if not isinstance(model_instance, Model): - LOGGER.warning("Model is not instance of Model", model_instance=model_instance) - return htmls - - try: - for name in dir(model_instance): - if not name.startswith(prefix): - continue - value = getattr(model_instance, name) - if not callable(value): - continue - if name.startswith(prefix): - html = value(context.get("request")) - if html: - htmls.append(mark_safe(html)) - except NotImplementedError: - pass - - return htmls diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 577bdfbc6..0974fd370 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -311,11 +311,6 @@ urlpatterns = [ name="certificatekeypair-delete", ), # Outposts - path( - "outposts/", - outposts.OutpostListView.as_view(), - name="outposts", - ), path( "outposts/create/", outposts.OutpostCreateView.as_view(), diff --git a/authentik/admin/views/outposts.py b/authentik/admin/views/outposts.py index 1e54ca5e8..0b93715cf 100644 --- a/authentik/admin/views/outposts.py +++ b/authentik/admin/views/outposts.py @@ -9,36 +9,18 @@ from django.contrib.auth.mixins import ( from django.contrib.messages.views import SuccessMessageMixin from django.urls import reverse_lazy from django.utils.translation import gettext as _ -from django.views.generic import ListView, UpdateView -from guardian.mixins import PermissionListMixin, PermissionRequiredMixin +from django.views.generic import UpdateView +from guardian.mixins import PermissionRequiredMixin from authentik.admin.views.utils import ( BackSuccessUrlMixin, DeleteMessageView, - SearchListMixin, - UserPaginateListMixin, ) from authentik.lib.views import CreateAssignPermView from authentik.outposts.forms import OutpostForm from authentik.outposts.models import Outpost, OutpostConfig -class OutpostListView( - LoginRequiredMixin, - PermissionListMixin, - UserPaginateListMixin, - SearchListMixin, - ListView, -): - """Show list of all outposts""" - - model = Outpost - permission_required = "authentik_outposts.view_outpost" - ordering = "name" - template_name = "administration/outpost/list.html" - search_fields = ["name", "_config"] - - class OutpostCreateView( SuccessMessageMixin, BackSuccessUrlMixin, @@ -53,7 +35,7 @@ class OutpostCreateView( permission_required = "authentik_outposts.add_outpost" template_name = "generic/create.html" - success_url = reverse_lazy("authentik_admin:outposts") + success_url = reverse_lazy("authentik_core:shell") success_message = _("Successfully created Outpost") def get_initial(self) -> Dict[str, Any]: @@ -78,7 +60,7 @@ class OutpostUpdateView( permission_required = "authentik_outposts.change_outpost" template_name = "generic/update.html" - success_url = reverse_lazy("authentik_admin:outposts") + success_url = reverse_lazy("authentik_core:shell") success_message = _("Successfully updated Outpost") @@ -89,5 +71,5 @@ class OutpostDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessa permission_required = "authentik_outposts.delete_outpost" template_name = "generic/delete.html" - success_url = reverse_lazy("authentik_admin:outposts") + success_url = reverse_lazy("authentik_core:shell") success_message = _("Successfully deleted Outpost") From 820f658b490adaf0ea779690cd2120c60a852bc8 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 19:04:19 +0100 Subject: [PATCH 09/13] web: add outpost list page --- authentik/admin/views/outposts.py | 5 +- authentik/outposts/api/outposts.py | 7 ++ swagger.yaml | 31 +++++- web/src/api/Outposts.ts | 39 +++++++ web/src/interfaces/AdminInterface.ts | 2 +- web/src/pages/outposts/OutpostHealth.ts | 49 +++++++++ web/src/pages/outposts/OutpostListPage.ts | 121 ++++++++++++++++++++++ web/src/routes.ts | 2 + 8 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 web/src/api/Outposts.ts create mode 100644 web/src/pages/outposts/OutpostHealth.ts create mode 100644 web/src/pages/outposts/OutpostListPage.ts diff --git a/authentik/admin/views/outposts.py b/authentik/admin/views/outposts.py index 0b93715cf..c9772ce8b 100644 --- a/authentik/admin/views/outposts.py +++ b/authentik/admin/views/outposts.py @@ -12,10 +12,7 @@ from django.utils.translation import gettext as _ from django.views.generic import UpdateView from guardian.mixins import PermissionRequiredMixin -from authentik.admin.views.utils import ( - BackSuccessUrlMixin, - DeleteMessageView, -) +from authentik.admin.views.utils import BackSuccessUrlMixin, DeleteMessageView from authentik.lib.views import CreateAssignPermView from authentik.outposts.forms import OutpostForm from authentik.outposts.models import Outpost, OutpostConfig diff --git a/authentik/outposts/api/outposts.py b/authentik/outposts/api/outposts.py index c3c054620..db9b08ce9 100644 --- a/authentik/outposts/api/outposts.py +++ b/authentik/outposts/api/outposts.py @@ -51,6 +51,13 @@ class OutpostViewSet(ModelViewSet): queryset = Outpost.objects.all() serializer_class = OutpostSerializer + filterset_fields = { + "providers": ["isnull"], + } + search_fields = [ + "name", + "providers__name", + ] @swagger_auto_schema(responses={200: OutpostHealthSerializer(many=True)}) @action(methods=["GET"], detail=True) diff --git a/swagger.yaml b/swagger.yaml index 75d42d415..75b1575e3 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -1789,6 +1789,11 @@ paths: operationId: outposts_outposts_list description: Outpost Viewset parameters: + - name: providers__isnull + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -1914,11 +1919,11 @@ paths: /outposts/outposts/{uuid}/health/: get: operationId: outposts_outposts_health - description: Outpost Viewset + description: Get outposts current health parameters: [] responses: '200': - description: '' + description: Outpost health status schema: description: '' type: array @@ -4457,7 +4462,7 @@ paths: /providers/oauth2/{id}/setup_urls/: get: operationId: providers_oauth2_setup_urls - description: Return metadata as XML string + description: Get Providers setup URLs parameters: [] responses: '200': @@ -8179,11 +8184,15 @@ definitions: type: string format: uuid x-nullable: true + token_identifier: + title: Token identifier + type: string + readOnly: true _config: title: config type: object OutpostHealth: - description: '' + description: Outpost health status type: object properties: last_seen: @@ -8191,6 +8200,20 @@ definitions: type: string format: date-time readOnly: true + version: + title: Version + type: string + readOnly: true + minLength: 1 + version_should: + title: Version should + type: string + readOnly: true + minLength: 1 + version_outdated: + title: Version outdated + type: boolean + readOnly: true OpenIDConnectConfiguration: title: Oidc configuration description: rest_framework Serializer for OIDC Configuration diff --git a/web/src/api/Outposts.ts b/web/src/api/Outposts.ts new file mode 100644 index 000000000..53a172769 --- /dev/null +++ b/web/src/api/Outposts.ts @@ -0,0 +1,39 @@ +import { DefaultClient, PBResponse, QueryArguments } from "./Client"; +import { Provider } from "./Providers"; + +export interface OutpostHealth { + last_seen: number; + version: string; + version_should: string; + version_outdated: boolean; +} + +export class Outpost { + + pk: string; + name: string; + providers: Provider[]; + service_connection?: string; + _config: QueryArguments; + token_identifier: string; + + constructor() { + throw Error(); + } + + static get(pk: string): Promise { + return DefaultClient.fetch(["outposts", "outposts", pk]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["outposts", "outposts"], filter); + } + + static health(pk: string): Promise { + return DefaultClient.fetch(["outposts", "outposts", pk, "health"]); + } + + static adminUrl(rest: string): string { + return `/administration/outposts/${rest}`; + } +} diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts index a6482ea20..00c437176 100644 --- a/web/src/interfaces/AdminInterface.ts +++ b/web/src/interfaces/AdminInterface.ts @@ -27,7 +27,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ `^/sources/(?${SLUG_REGEX})$`, ), new SidebarItem("Providers", "/providers"), - new SidebarItem("Outposts", "/administration/outposts/"), + new SidebarItem("Outposts", "/outposts"), new SidebarItem("Outpost Service Connections", "/administration/outpost_service_connections/"), ).when((): Promise => { return User.me().then(u => u.is_superuser); diff --git a/web/src/pages/outposts/OutpostHealth.ts b/web/src/pages/outposts/OutpostHealth.ts new file mode 100644 index 000000000..7d15542f2 --- /dev/null +++ b/web/src/pages/outposts/OutpostHealth.ts @@ -0,0 +1,49 @@ +import { gettext } from "django"; +import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { until } from "lit-html/directives/until"; +import { Outpost } from "../../api/Outposts"; +import { COMMON_STYLES } from "../../common/styles"; + +@customElement("ak-outpost-health") +export class OutpostHealth extends LitElement { + + @property() + outpostId?: string; + + static get styles(): CSSResult[] { + return COMMON_STYLES; + } + + render(): TemplateResult { + if (!this.outpostId) { + return html``; + } + return html`
    ${until(Outpost.health(this.outpostId).then((oh) => { + if (oh.length === 0) { + return html`
  • +
      +
    • +  ${gettext("Not available")} +
    • +
    +
  • `; + } + return oh.map((h) => { + return html`
  • +
      +
    • +  ${gettext(`Last seen: ${new Date(h.last_seen * 1000).toLocaleTimeString()}`)} +
    • +
    • + ${h.version_outdated ? + html`  + ${gettext(`${h.version}, should be ${h.version_should}`)}` : + html` ${gettext(`Version: ${h.version}`)}`} +
    • +
    +
  • `; + }); + }), html``)}
`; + } + +} diff --git a/web/src/pages/outposts/OutpostListPage.ts b/web/src/pages/outposts/OutpostListPage.ts new file mode 100644 index 000000000..7caa96d9a --- /dev/null +++ b/web/src/pages/outposts/OutpostListPage.ts @@ -0,0 +1,121 @@ +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { PBResponse } from "../../api/Client"; +import { Outpost } from "../../api/Outposts"; +import { TableColumn } from "../../elements/table/Table"; +import { TablePage } from "../../elements/table/TablePage"; + +import "./OutpostHealth"; +import "../../elements/buttons/SpinnerButton"; +import "../../elements/buttons/ModalButton"; + +@customElement("ak-outpost-list") +export class OutpostListPage extends TablePage { + pageTitle(): string { + return "Outposts"; + } + pageDescription(): string | undefined { + return "Outposts are deployments of authentik components to support different environments and protocols, like reverse proxies."; + } + pageIcon(): string { + return "pf-icon pf-icon-zone"; + } + searchEnabled(): boolean { + return true; + } + apiEndpoint(page: number): Promise> { + return Outpost.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + columns(): TableColumn[] { + return [ + new TableColumn("Name", "name"), + new TableColumn("Providers"), + new TableColumn("Health and Version"), + new TableColumn(""), + ]; + } + + @property() + order = "name"; + + row(item: Outpost): TemplateResult[] { + return [ + html`${item.name}`, + html`
    ${item.providers.map((p) => { + return html`
  • ${p.name}
  • `; + })}
`, + html``, + html` + + + ${gettext("Edit")} + +
+
+ + + ${gettext("Delete")} + +
+
+ + +
+
+

${gettext('Outpost Deployment Info')}

+
+ + +
+
`, + ]; + } + + renderToolbar(): TemplateResult { + return html` + + + ${gettext("Create")} + +
+
+ ${super.renderToolbar()} + `; + } +} diff --git a/web/src/routes.ts b/web/src/routes.ts index 32c97b71b..3237b3046 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -14,6 +14,7 @@ import "./pages/events/RuleListPage"; import "./pages/providers/ProviderListPage"; import "./pages/providers/ProviderViewPage"; import "./pages/property-mappings/PropertyMappingListPage"; +import "./pages/outposts/OutpostListPage"; export const ROUTES: Route[] = [ // Prevent infinite Shell loops @@ -42,4 +43,5 @@ export const ROUTES: Route[] = [ new Route(new RegExp("^/events/transports$"), html``), new Route(new RegExp("^/events/rules$"), html``), new Route(new RegExp("^/property-mappings$"), html``), + new Route(new RegExp("^/outposts$"), html``), ]; From f020b7938411e9a6d22801bec98e8ad5946c13bb Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 19:07:25 +0100 Subject: [PATCH 10/13] admin: remove old code --- .../outpost_service_connection/list.html | 1 - .../templates/administration/source/list.html | 5 --- .../templates/administration/stage/list.html | 5 --- .../administration/stage_prompt/list.html | 5 --- authentik/admin/templatetags/__init__.py | 0 .../admin/templatetags/admin_reflection.py | 34 ------------------- 6 files changed, 50 deletions(-) delete mode 100644 authentik/admin/templatetags/__init__.py delete mode 100644 authentik/admin/templatetags/admin_reflection.py diff --git a/authentik/admin/templates/administration/outpost_service_connection/list.html b/authentik/admin/templates/administration/outpost_service_connection/list.html index 79cee25d0..b43dcf2dc 100644 --- a/authentik/admin/templates/administration/outpost_service_connection/list.html +++ b/authentik/admin/templates/administration/outpost_service_connection/list.html @@ -3,7 +3,6 @@ {% load i18n %} {% load humanize %} {% load authentik_utils %} -{% load admin_reflection %} {% block content %}
diff --git a/authentik/admin/templates/administration/source/list.html b/authentik/admin/templates/administration/source/list.html index f29089409..777a6ded2 100644 --- a/authentik/admin/templates/administration/source/list.html +++ b/authentik/admin/templates/administration/source/list.html @@ -2,7 +2,6 @@ {% load i18n %} {% load authentik_utils %} -{% load admin_reflection %} {% block content %}
@@ -93,10 +92,6 @@
- {% get_links source as links %} - {% for name, href in links %} - {% trans name %} - {% endfor %} {% endfor %} diff --git a/authentik/admin/templates/administration/stage/list.html b/authentik/admin/templates/administration/stage/list.html index 4fe3d48ff..def5e20ba 100644 --- a/authentik/admin/templates/administration/stage/list.html +++ b/authentik/admin/templates/administration/stage/list.html @@ -2,7 +2,6 @@ {% load i18n %} {% load authentik_utils %} -{% load admin_reflection %} {% block content %}
@@ -88,10 +87,6 @@
- {% get_links stage as links %} - {% for name, href in links.items %} - {% trans name %} - {% endfor %} {% endfor %} diff --git a/authentik/admin/templates/administration/stage_prompt/list.html b/authentik/admin/templates/administration/stage_prompt/list.html index 45ca2e5bc..41b435e68 100644 --- a/authentik/admin/templates/administration/stage_prompt/list.html +++ b/authentik/admin/templates/administration/stage_prompt/list.html @@ -2,7 +2,6 @@ {% load i18n %} {% load authentik_utils %} -{% load admin_reflection %} {% block content %}
@@ -90,10 +89,6 @@
- {% get_links prompt as links %} - {% for name, href in links.items %} - {% trans name %} - {% endfor %} {% endfor %} diff --git a/authentik/admin/templatetags/__init__.py b/authentik/admin/templatetags/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/authentik/admin/templatetags/admin_reflection.py b/authentik/admin/templatetags/admin_reflection.py deleted file mode 100644 index 848e9c1f8..000000000 --- a/authentik/admin/templatetags/admin_reflection.py +++ /dev/null @@ -1,34 +0,0 @@ -"""authentik admin templatetags""" -from django import template -from django.db.models import Model -from structlog.stdlib import get_logger - -register = template.Library() -LOGGER = get_logger() - - -@register.simple_tag() -def get_links(model_instance): - """Find all link_ methods on an object instance, run them and return as dict""" - prefix = "link_" - links = {} - - if not isinstance(model_instance, Model): - LOGGER.warning("Model is not instance of Model", model_instance=model_instance) - return links - - try: - for name in dir(model_instance): - if not name.startswith(prefix): - continue - value = getattr(model_instance, name) - if not callable(value): - continue - human_name = name.replace(prefix, "").replace("_", " ").capitalize() - link = value() - if link: - links[human_name] = link - except NotImplementedError: - pass - - return links From 43bab840ecb682c76a7a958ce24ae39ca087ecff Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 19:08:39 +0100 Subject: [PATCH 11/13] web: fix sidebar being active when stage prompts is selected --- authentik/admin/urls.py | 8 ++++---- web/src/interfaces/AdminInterface.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 0974fd370..fb116ccc8 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -169,22 +169,22 @@ urlpatterns = [ ), # Stage Prompts path( - "stages/prompts/", + "stages_prompts/", stages_prompts.PromptListView.as_view(), name="stage-prompts", ), path( - "stages/prompts/create/", + "stages_prompts/create/", stages_prompts.PromptCreateView.as_view(), name="stage-prompt-create", ), path( - "stages/prompts//update/", + "stages_prompts//update/", stages_prompts.PromptUpdateView.as_view(), name="stage-prompt-update", ), path( - "stages/prompts//delete/", + "stages_prompts//delete/", stages_prompts.PromptDeleteView.as_view(), name="stage-prompt-delete", ), diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts index 00c437176..f38154d62 100644 --- a/web/src/interfaces/AdminInterface.ts +++ b/web/src/interfaces/AdminInterface.ts @@ -41,7 +41,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ 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("Prompts", "/administration/stages_prompts/"), new SidebarItem("Invitations", "/administration/stages/invitations/"), ).when((): Promise => { return User.me().then(u => u.is_superuser); From b64ecbde2265d001133a7a464860eeb5cf473894 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 19:42:49 +0100 Subject: [PATCH 12/13] web: fix linting --- web/src/pages/outposts/OutpostListPage.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/src/pages/outposts/OutpostListPage.ts b/web/src/pages/outposts/OutpostListPage.ts index 7caa96d9a..ab30ec8f9 100644 --- a/web/src/pages/outposts/OutpostListPage.ts +++ b/web/src/pages/outposts/OutpostListPage.ts @@ -65,14 +65,14 @@ export class OutpostListPage extends TablePage {
-

${gettext('Outpost Deployment Info')}

+

${gettext("Outpost Deployment Info")}

`, From 78bcb90a1e149ffae372d3e6ac2f5a374b6efa3e Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 8 Feb 2021 19:51:46 +0100 Subject: [PATCH 13/13] outposts: ensure Outpost API is backwards compatible --- authentik/outposts/api/outposts.py | 3 ++- swagger.yaml | 6 ++++++ web/src/api/Outposts.ts | 3 ++- web/src/pages/outposts/OutpostListPage.ts | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/authentik/outposts/api/outposts.py b/authentik/outposts/api/outposts.py index db9b08ce9..9ab0ae326 100644 --- a/authentik/outposts/api/outposts.py +++ b/authentik/outposts/api/outposts.py @@ -16,7 +16,7 @@ class OutpostSerializer(ModelSerializer): """Outpost Serializer""" _config = JSONField() - providers = ProviderSerializer(many=True, read_only=True) + providers_obj = ProviderSerializer(source="providers", many=True, read_only=True) class Meta: @@ -25,6 +25,7 @@ class OutpostSerializer(ModelSerializer): "pk", "name", "providers", + "providers_obj", "service_connection", "token_identifier", "_config", diff --git a/swagger.yaml b/swagger.yaml index 75b1575e3..1bfcdb6ec 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -8159,6 +8159,7 @@ definitions: description: Outpost Serializer required: - name + - providers - _config type: object properties: @@ -8172,6 +8173,11 @@ definitions: type: string minLength: 1 providers: + type: array + items: + type: integer + uniqueItems: true + providers_obj: description: '' type: array items: diff --git a/web/src/api/Outposts.ts b/web/src/api/Outposts.ts index 53a172769..9205b8911 100644 --- a/web/src/api/Outposts.ts +++ b/web/src/api/Outposts.ts @@ -12,7 +12,8 @@ export class Outpost { pk: string; name: string; - providers: Provider[]; + providers: number[]; + providers_obj: Provider[]; service_connection?: string; _config: QueryArguments; token_identifier: string; diff --git a/web/src/pages/outposts/OutpostListPage.ts b/web/src/pages/outposts/OutpostListPage.ts index ab30ec8f9..59b0a9250 100644 --- a/web/src/pages/outposts/OutpostListPage.ts +++ b/web/src/pages/outposts/OutpostListPage.ts @@ -46,7 +46,7 @@ export class OutpostListPage extends TablePage { row(item: Outpost): TemplateResult[] { return [ html`${item.name}`, - html`
    ${item.providers.map((p) => { + html`
      ${item.providers_obj.map((p) => { return html`
    • ${p.name}
    • `; })}
    `, html``,