From 7fad2b6563fc0c8a6ab7cd9ba3cc4664cb0c69de Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Fri, 2 Apr 2021 15:15:19 +0200 Subject: [PATCH] sources/oauth: migrate to web Signed-off-by: Jens Langhammer --- authentik/admin/urls.py | 9 +- authentik/admin/views/sources.py | 43 ---- authentik/core/models.py | 6 - authentik/sources/oauth/api/source.py | 4 +- .../migrations/0002_auto_20200520_1108.py | 7 + authentik/sources/oauth/models.py | 4 +- swagger.yaml | 35 ++- web/src/api/legacy.ts | 4 - web/src/pages/TestPage.ts | 86 ++++++++ web/src/pages/sources/SourcesListPage.ts | 2 + .../pages/sources/ldap/LDAPSourceViewPage.ts | 26 ++- .../pages/sources/oauth/OAuthSourceForm.ts | 205 ++++++++++++++++++ .../sources/oauth/OAuthSourceViewPage.ts | 26 ++- .../pages/sources/saml/SAMLSourceViewPage.ts | 27 ++- web/src/routes.ts | 3 + 15 files changed, 387 insertions(+), 100 deletions(-) delete mode 100644 authentik/admin/views/sources.py create mode 100644 web/src/pages/TestPage.ts create mode 100644 web/src/pages/sources/oauth/OAuthSourceForm.ts diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py index 4fe385013..1f07cd81d 100644 --- a/authentik/admin/urls.py +++ b/authentik/admin/urls.py @@ -1,16 +1,9 @@ """authentik URL Configuration""" from django.urls import path -from authentik.admin.views import policies, sources, stages +from authentik.admin.views import policies, stages urlpatterns = [ - # Sources - path("sources/create/", sources.SourceCreateView.as_view(), name="source-create"), - path( - "sources//update/", - sources.SourceUpdateView.as_view(), - name="source-update", - ), # Policies path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"), path( diff --git a/authentik/admin/views/sources.py b/authentik/admin/views/sources.py deleted file mode 100644 index 6191cf61f..000000000 --- a/authentik/admin/views/sources.py +++ /dev/null @@ -1,43 +0,0 @@ -"""authentik Source administration""" -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.utils.translation import gettext as _ -from guardian.mixins import PermissionRequiredMixin - -from authentik.admin.views.utils import InheritanceCreateView, InheritanceUpdateView -from authentik.core.models import Source - - -class SourceCreateView( - SuccessMessageMixin, - LoginRequiredMixin, - DjangoPermissionRequiredMixin, - InheritanceCreateView, -): - """Create new Source""" - - model = Source - permission_required = "authentik_core.add_source" - - success_url = "/" - template_name = "generic/create.html" - success_message = _("Successfully created Source") - - -class SourceUpdateView( - SuccessMessageMixin, - LoginRequiredMixin, - PermissionRequiredMixin, - InheritanceUpdateView, -): - """Update source""" - - model = Source - permission_required = "authentik_core.change_source" - - success_url = "/" - template_name = "generic/update.html" - success_message = _("Successfully updated Source") diff --git a/authentik/core/models.py b/authentik/core/models.py index 900573364..28900030a 100644 --- a/authentik/core/models.py +++ b/authentik/core/models.py @@ -10,7 +10,6 @@ from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import UserManager as DjangoUserManager from django.db import models from django.db.models import Q, QuerySet -from django.forms import ModelForm from django.http import HttpRequest from django.templatetags.static import static from django.utils.functional import cached_property @@ -280,11 +279,6 @@ class Source(SerializerModel, PolicyBindingModel): """Return component used to edit this object""" raise NotImplementedError - @property - def form(self) -> Type[ModelForm]: - """Return Form class used to edit this object""" - raise NotImplementedError - @property def ui_login_button(self) -> Optional[UILoginButton]: """If source uses a http-based flow, return UI Information about the login diff --git a/authentik/sources/oauth/api/source.py b/authentik/sources/oauth/api/source.py index 72ae3be92..0d4b65029 100644 --- a/authentik/sources/oauth/api/source.py +++ b/authentik/sources/oauth/api/source.py @@ -1,12 +1,11 @@ """OAuth Source Serializer""" from django.urls.base import reverse_lazy -from drf_yasg.utils import swagger_auto_schema +from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method from rest_framework.decorators import action from rest_framework.fields import BooleanField, CharField, SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet -from drf_yasg.utils import swagger_serializer_method from authentik.core.api.sources import SourceSerializer from authentik.core.api.utils import PassiveSerializer @@ -61,7 +60,6 @@ class OAuthSourceSerializer(SourceSerializer): "callback_url", "type", ] - extra_kwargs = {"consumer_secret": {"write_only": True}} class OAuthSourceViewSet(ModelViewSet): diff --git a/authentik/sources/oauth/migrations/0002_auto_20200520_1108.py b/authentik/sources/oauth/migrations/0002_auto_20200520_1108.py index 7452ef5b3..6c3bba15a 100644 --- a/authentik/sources/oauth/migrations/0002_auto_20200520_1108.py +++ b/authentik/sources/oauth/migrations/0002_auto_20200520_1108.py @@ -47,4 +47,11 @@ class Migration(migrations.Migration): verbose_name="Profile URL", ), ), + migrations.AlterModelOptions( + name="oauthsource", + options={ + "verbose_name": "OAuth Source", + "verbose_name_plural": "OAuth Sources", + }, + ), ] diff --git a/authentik/sources/oauth/models.py b/authentik/sources/oauth/models.py index 2efa8f308..8fc39f4de 100644 --- a/authentik/sources/oauth/models.py +++ b/authentik/sources/oauth/models.py @@ -86,8 +86,8 @@ class OAuthSource(Source): class Meta: - verbose_name = _("Generic OAuth Source") - verbose_name_plural = _("Generic OAuth Sources") + verbose_name = _("OAuth Source") + verbose_name_plural = _("OAuth Sources") class GitHubOAuthSource(OAuthSource): diff --git a/swagger.yaml b/swagger.yaml index 621c52f38..1f12b29d7 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -14561,9 +14561,12 @@ definitions: format: uuid readOnly: true managed: - title: Managed + title: Managed by authentik + description: Objects which are managed by authentik. These objects are created + and updated automatically. This is flag only indicates that an object can + be overwritten by migrations. You can still modify the objects via the API, + but expect changes to be overwritten in a later update. type: string - readOnly: true minLength: 1 x-nullable: true identifier: @@ -16225,9 +16228,12 @@ definitions: format: uuid readOnly: true managed: - title: Managed + title: Managed by authentik + description: Objects which are managed by authentik. These objects are created + and updated automatically. This is flag only indicates that an object can + be overwritten by migrations. You can still modify the objects via the API, + but expect changes to be overwritten in a later update. type: string - readOnly: true minLength: 1 x-nullable: true name: @@ -16275,9 +16281,12 @@ definitions: format: uuid readOnly: true managed: - title: Managed + title: Managed by authentik + description: Objects which are managed by authentik. These objects are created + and updated automatically. This is flag only indicates that an object can + be overwritten by migrations. You can still modify the objects via the API, + but expect changes to be overwritten in a later update. type: string - readOnly: true minLength: 1 x-nullable: true name: @@ -16317,9 +16326,12 @@ definitions: format: uuid readOnly: true managed: - title: Managed + title: Managed by authentik + description: Objects which are managed by authentik. These objects are created + and updated automatically. This is flag only indicates that an object can + be overwritten by migrations. You can still modify the objects via the API, + but expect changes to be overwritten in a later update. type: string - readOnly: true minLength: 1 x-nullable: true name: @@ -16363,9 +16375,12 @@ definitions: format: uuid readOnly: true managed: - title: Managed + title: Managed by authentik + description: Objects which are managed by authentik. These objects are created + and updated automatically. This is flag only indicates that an object can + be overwritten by migrations. You can still modify the objects via the API, + but expect changes to be overwritten in a later update. type: string - readOnly: true minLength: 1 x-nullable: true name: diff --git a/web/src/api/legacy.ts b/web/src/api/legacy.ts index 156bf7fe5..47d430d8d 100644 --- a/web/src/api/legacy.ts +++ b/web/src/api/legacy.ts @@ -8,10 +8,6 @@ export class AdminURLManager { return `/administration/stages/${rest}`; } - static sources(rest: string): string { - return `/administration/sources/${rest}`; - } - } export class AppURLManager { diff --git a/web/src/pages/TestPage.ts b/web/src/pages/TestPage.ts new file mode 100644 index 000000000..00043f98b --- /dev/null +++ b/web/src/pages/TestPage.ts @@ -0,0 +1,86 @@ +import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import AKGlobal from "../authentik.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import "../elements/forms/FormGroup"; + +@customElement("ak-test-page") +export class TestPage extends LitElement { + + static get styles(): CSSResult[] { + return [PFBase, PFForm, PFButton, PFFormControl, AKGlobal]; + } + + render(): TemplateResult { + return html`
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+ + + foo + +
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+ +
+
+
+
+
`; + } + +} diff --git a/web/src/pages/sources/SourcesListPage.ts b/web/src/pages/sources/SourcesListPage.ts index 440348f18..875bc9cb0 100644 --- a/web/src/pages/sources/SourcesListPage.ts +++ b/web/src/pages/sources/SourcesListPage.ts @@ -16,6 +16,7 @@ import { DEFAULT_CONFIG } from "../../api/Config"; import { ifDefined } from "lit-html/directives/if-defined"; import "./ldap/LDAPSourceForm"; import "./saml/SAMLSourceForm"; +import "./oauth/OAuthSourceForm"; @customElement("ak-source-list") export class SourceListPage extends TablePage { @@ -76,6 +77,7 @@ export class SourceListPage extends TablePage { .typeMap=${{ "ldap": "ak-source-ldap-form", "saml": "ak-source-saml-form", + "oauth": "ak-source-oauth-form", }}> + diff --git a/web/src/pages/sources/oauth/OAuthSourceForm.ts b/web/src/pages/sources/oauth/OAuthSourceForm.ts new file mode 100644 index 000000000..93f24ba3c --- /dev/null +++ b/web/src/pages/sources/oauth/OAuthSourceForm.ts @@ -0,0 +1,205 @@ +import { OAuthSource, SourcesApi, FlowsApi, FlowDesignationEnum } from "authentik-api"; +import { gettext } from "django"; +import { customElement, property } from "lit-element"; +import { html, TemplateResult } from "lit-html"; +import { DEFAULT_CONFIG } from "../../../api/Config"; +import { Form } from "../../../elements/forms/Form"; +import "../../../elements/forms/FormGroup"; +import "../../../elements/forms/HorizontalFormElement"; +import { ifDefined } from "lit-html/directives/if-defined"; +import { until } from "lit-html/directives/until"; + +@customElement("ak-source-oauth-form") +export class OAuthSourceForm extends Form { + + set sourceSlug(value: string) { + new SourcesApi(DEFAULT_CONFIG).sourcesOauthRead({ + slug: value, + }).then(source => { + this.source = source; + this.showUrlOptions = source.type?.urlsCustomizable || false; + }); + } + + @property({attribute: false}) + source?: OAuthSource; + + @property({type: Boolean}) + showUrlOptions = false; + + getSuccessMessage(): string { + if (this.source) { + return gettext("Successfully updated source."); + } else { + return gettext("Successfully created source."); + } + } + + send = (data: OAuthSource): Promise => { + if (this.source) { + return new SourcesApi(DEFAULT_CONFIG).sourcesOauthUpdate({ + slug: this.source.slug, + data: data + }); + } else { + return new SourcesApi(DEFAULT_CONFIG).sourcesOauthCreate({ + data: data + }); + } + }; + + renderUrlOptions(): TemplateResult { + if (!this.showUrlOptions) { + return html``; + } + return html` + + + ${gettext("URL settings")} + +
+ + +

${gettext("URL the user is redirect to to consent the authorization.")}

+
+ + +

${gettext("URL used by authentik to retrieve tokens.")}

+
+ + +

${gettext("URL used by authentik to get user information.")}

+
+ + +

${gettext("URL used to request the initial token. This URL is only required for OAuth 1.")}

+
+
+
`; + } + + renderForm(): TemplateResult { + return html`
+ + + + + + + +
+ + +
+
+ + + + ${gettext("Protocol settings")} + +
+ + + + + + + + +

${gettext("Keypair which is used to sign outgoing requests. Leave empty to disable signing.")}

+
+
+
+ ${this.renderUrlOptions()} + + + ${gettext("Flow settings")} + +
+ + +

${gettext("Flow to use when authenticating existing users.")}

+
+ + +

${gettext("Flow to use when enrolling new users.")}

+
+
+
+
`; + } + +} diff --git a/web/src/pages/sources/oauth/OAuthSourceViewPage.ts b/web/src/pages/sources/oauth/OAuthSourceViewPage.ts index 88bda19a5..671485510 100644 --- a/web/src/pages/sources/oauth/OAuthSourceViewPage.ts +++ b/web/src/pages/sources/oauth/OAuthSourceViewPage.ts @@ -11,16 +11,17 @@ import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import AKGlobal from "../../../authentik.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import "../../../elements/buttons/ModalButton"; import "../../../elements/buttons/SpinnerButton"; import "../../../elements/CodeMirror"; import "../../../elements/Tabs"; import "../../../elements/events/ObjectChangelog"; +import "../../../elements/forms/ModalForm"; +import "./OAuthSourceForm"; import { Page } from "../../../elements/Page"; import { OAuthSource, SourcesApi } from "authentik-api"; import { DEFAULT_CONFIG } from "../../../api/Config"; -import { AdminURLManager } from "../../../api/legacy"; import { EVENT_REFRESH } from "../../../constants"; @customElement("ak-source-oauth-view") @@ -48,7 +49,7 @@ export class OAuthSourceViewPage extends Page { source?: OAuthSource; static get styles(): CSSResult[] { - return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal]; + return [PFBase, PFPage, PFButton, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal]; } constructor() { @@ -121,12 +122,21 @@ export class OAuthSourceViewPage extends Page { diff --git a/web/src/pages/sources/saml/SAMLSourceViewPage.ts b/web/src/pages/sources/saml/SAMLSourceViewPage.ts index de6b650c3..6ad4338db 100644 --- a/web/src/pages/sources/saml/SAMLSourceViewPage.ts +++ b/web/src/pages/sources/saml/SAMLSourceViewPage.ts @@ -12,16 +12,18 @@ import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import AKGlobal from "../../../authentik.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; -import "../../../elements/buttons/ModalButton"; import "../../../elements/buttons/SpinnerButton"; import "../../../elements/CodeMirror"; import "../../../elements/Tabs"; import "../../../elements/events/ObjectChangelog"; +import "../../../elements/forms/ModalForm"; +import "./SAMLSourceForm"; import { Page } from "../../../elements/Page"; import { SAMLSource, SourcesApi } from "authentik-api"; import { DEFAULT_CONFIG } from "../../../api/Config"; -import { AdminURLManager, AppURLManager } from "../../../api/legacy"; +import { AppURLManager } from "../../../api/legacy"; import { EVENT_REFRESH } from "../../../constants"; import { ifDefined } from "lit-html/directives/if-defined"; @@ -50,7 +52,7 @@ export class SAMLSourceViewPage extends Page { source?: SAMLSource; static get styles(): CSSResult[] { - return [PFBase, PFPage, PFFlex, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal]; + return [PFBase, PFPage, PFFlex, PFButton, PFDisplay, PFGallery, PFContent, PFCard, PFDescriptionList, PFSizing, AKGlobal]; } constructor() { @@ -107,12 +109,21 @@ export class SAMLSourceViewPage extends Page { diff --git a/web/src/routes.ts b/web/src/routes.ts index 1b7934a98..1fa6759e9 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -30,6 +30,8 @@ import "./pages/users/UserListPage"; import "./pages/users/UserViewPage"; import "./pages/user-settings/UserSettingsPage"; +import "./pages/TestPage"; + export const ROUTES: Route[] = [ // Prevent infinite Shell loops new Route(new RegExp("^/$")).redirect("/library"), @@ -74,4 +76,5 @@ export const ROUTES: Route[] = [ new Route(new RegExp("^/outpost/service-connections$"), html``), new Route(new RegExp("^/crypto/certificates$"), html``), new Route(new RegExp("^/user$"), html``), + new Route(new RegExp("^/test$"), html``), ];