sources/oauth: migrate to web

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-04-02 15:15:19 +02:00
parent 22f50aae45
commit 7fad2b6563
15 changed files with 387 additions and 100 deletions

View file

@ -1,16 +1,9 @@
"""authentik URL Configuration""" """authentik URL Configuration"""
from django.urls import path from django.urls import path
from authentik.admin.views import policies, sources, stages from authentik.admin.views import policies, stages
urlpatterns = [ urlpatterns = [
# Sources
path("sources/create/", sources.SourceCreateView.as_view(), name="source-create"),
path(
"sources/<uuid:pk>/update/",
sources.SourceUpdateView.as_view(),
name="source-update",
),
# Policies # Policies
path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"), path("policies/create/", policies.PolicyCreateView.as_view(), name="policy-create"),
path( path(

View file

@ -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")

View file

@ -10,7 +10,6 @@ from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as DjangoUserManager from django.contrib.auth.models import UserManager as DjangoUserManager
from django.db import models from django.db import models
from django.db.models import Q, QuerySet from django.db.models import Q, QuerySet
from django.forms import ModelForm
from django.http import HttpRequest from django.http import HttpRequest
from django.templatetags.static import static from django.templatetags.static import static
from django.utils.functional import cached_property from django.utils.functional import cached_property
@ -280,11 +279,6 @@ class Source(SerializerModel, PolicyBindingModel):
"""Return component used to edit this object""" """Return component used to edit this object"""
raise NotImplementedError raise NotImplementedError
@property
def form(self) -> Type[ModelForm]:
"""Return Form class used to edit this object"""
raise NotImplementedError
@property @property
def ui_login_button(self) -> Optional[UILoginButton]: def ui_login_button(self) -> Optional[UILoginButton]:
"""If source uses a http-based flow, return UI Information about the login """If source uses a http-based flow, return UI Information about the login

View file

@ -1,12 +1,11 @@
"""OAuth Source Serializer""" """OAuth Source Serializer"""
from django.urls.base import reverse_lazy 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.decorators import action
from rest_framework.fields import BooleanField, CharField, SerializerMethodField from rest_framework.fields import BooleanField, CharField, SerializerMethodField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet 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.sources import SourceSerializer
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
@ -61,7 +60,6 @@ class OAuthSourceSerializer(SourceSerializer):
"callback_url", "callback_url",
"type", "type",
] ]
extra_kwargs = {"consumer_secret": {"write_only": True}}
class OAuthSourceViewSet(ModelViewSet): class OAuthSourceViewSet(ModelViewSet):

View file

@ -47,4 +47,11 @@ class Migration(migrations.Migration):
verbose_name="Profile URL", verbose_name="Profile URL",
), ),
), ),
migrations.AlterModelOptions(
name="oauthsource",
options={
"verbose_name": "OAuth Source",
"verbose_name_plural": "OAuth Sources",
},
),
] ]

View file

@ -86,8 +86,8 @@ class OAuthSource(Source):
class Meta: class Meta:
verbose_name = _("Generic OAuth Source") verbose_name = _("OAuth Source")
verbose_name_plural = _("Generic OAuth Sources") verbose_name_plural = _("OAuth Sources")
class GitHubOAuthSource(OAuthSource): class GitHubOAuthSource(OAuthSource):

View file

@ -14561,9 +14561,12 @@ definitions:
format: uuid format: uuid
readOnly: true readOnly: true
managed: 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 type: string
readOnly: true
minLength: 1 minLength: 1
x-nullable: true x-nullable: true
identifier: identifier:
@ -16225,9 +16228,12 @@ definitions:
format: uuid format: uuid
readOnly: true readOnly: true
managed: 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 type: string
readOnly: true
minLength: 1 minLength: 1
x-nullable: true x-nullable: true
name: name:
@ -16275,9 +16281,12 @@ definitions:
format: uuid format: uuid
readOnly: true readOnly: true
managed: 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 type: string
readOnly: true
minLength: 1 minLength: 1
x-nullable: true x-nullable: true
name: name:
@ -16317,9 +16326,12 @@ definitions:
format: uuid format: uuid
readOnly: true readOnly: true
managed: 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 type: string
readOnly: true
minLength: 1 minLength: 1
x-nullable: true x-nullable: true
name: name:
@ -16363,9 +16375,12 @@ definitions:
format: uuid format: uuid
readOnly: true readOnly: true
managed: 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 type: string
readOnly: true
minLength: 1 minLength: 1
x-nullable: true x-nullable: true
name: name:

View file

@ -8,10 +8,6 @@ export class AdminURLManager {
return `/administration/stages/${rest}`; return `/administration/stages/${rest}`;
} }
static sources(rest: string): string {
return `/administration/sources/${rest}`;
}
} }
export class AppURLManager { export class AppURLManager {

86
web/src/pages/TestPage.ts Normal file
View file

@ -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`<form novalidate class="pf-c-form">
<div class="pf-c-form__group">
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="form-expandable-field-groups-label1">
<span class="pf-c-form__label-text">Label 1</span>
<span class="pf-c-form__label-required" aria-hidden="true">&#42;</span>
</label>
<button class="pf-c-form__group-label-help" aria-label="More info">
<i class="pficon pf-icon-help" aria-hidden="true"></i>
</button>
</div>
<div class="pf-c-form__group-control">
<input class="pf-c-form-control" type="text" id="form-expandable-field-groups-label1"
name="form-expandable-field-groups-label1" required />
</div>
</div>
<div class="pf-c-form__group">
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="form-expandable-field-groups-label2">
<span class="pf-c-form__label-text">Label 2</span>
<span class="pf-c-form__label-required" aria-hidden="true">&#42;</span>
</label>
<button class="pf-c-form__group-label-help" aria-label="More info">
<i class="pficon pf-icon-help" aria-hidden="true"></i>
</button>
</div>
<div class="pf-c-form__group-control">
<input class="pf-c-form-control" type="text" id="form-expandable-field-groups-label2"
name="form-expandable-field-groups-label2" required />
</div>
</div>
<ak-form-group>
<span slot="header">
foo
</span>
<div slot="body">
<div class="pf-c-form__group">
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="form-expandable-field-groups-field-group2-label1">
<span class="pf-c-form__label-text">Label 1</span>
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
</label>
<button class="pf-c-form__group-label-help" aria-label="More info">
<i class="pficon pf-icon-help" aria-hidden="true"></i>
</button>
</div>
<div class="pf-c-form__group-control">
<input class="pf-c-form-control" type="text" id="form-expandable-field-groups-field-group2-label1" name="form-expandable-field-groups-field-group2-label1" required="">
</div>
</div>
<div class="pf-c-form__group">
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="form-expandable-field-groups-field-group2-label2">
<span class="pf-c-form__label-text">Label 2</span>
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
</label>
<button class="pf-c-form__group-label-help" aria-label="More info">
<i class="pficon pf-icon-help" aria-hidden="true"></i>
</button>
</div>
<div class="pf-c-form__group-control">
<input class="pf-c-form-control" type="text" id="form-expandable-field-groups-field-group2-label2" name="form-expandable-field-groups-field-group2-label2" required="">
</div>
</div>
</div>
</ak-form-group>
</form>`;
}
}

View file

@ -16,6 +16,7 @@ import { DEFAULT_CONFIG } from "../../api/Config";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import "./ldap/LDAPSourceForm"; import "./ldap/LDAPSourceForm";
import "./saml/SAMLSourceForm"; import "./saml/SAMLSourceForm";
import "./oauth/OAuthSourceForm";
@customElement("ak-source-list") @customElement("ak-source-list")
export class SourceListPage extends TablePage<Source> { export class SourceListPage extends TablePage<Source> {
@ -76,6 +77,7 @@ export class SourceListPage extends TablePage<Source> {
.typeMap=${{ .typeMap=${{
"ldap": "ak-source-ldap-form", "ldap": "ak-source-ldap-form",
"saml": "ak-source-saml-form", "saml": "ak-source-saml-form",
"oauth": "ak-source-oauth-form",
}}> }}>
</ak-proxy-form> </ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary"> <button slot="trigger" class="pf-c-button pf-m-secondary">

View file

@ -11,18 +11,19 @@ import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css";
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import AKGlobal from "../../../authentik.css"; import AKGlobal from "../../../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.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/buttons/SpinnerButton";
import "../../../elements/buttons/ActionButton"; import "../../../elements/buttons/ActionButton";
import "../../../elements/CodeMirror"; import "../../../elements/CodeMirror";
import "../../../elements/Tabs"; import "../../../elements/Tabs";
import "../../../elements/events/ObjectChangelog"; import "../../../elements/events/ObjectChangelog";
import "../../../elements/forms/ModalForm";
import "./LDAPSourceForm";
import { Page } from "../../../elements/Page"; import { Page } from "../../../elements/Page";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { LDAPSource, SourcesApi } from "authentik-api"; import { LDAPSource, SourcesApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminURLManager } from "../../../api/legacy";
import { EVENT_REFRESH } from "../../../constants"; import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-source-ldap-view") @customElement("ak-source-ldap-view")
@ -50,7 +51,7 @@ export class LDAPSourceViewPage extends Page {
source!: LDAPSource; source!: LDAPSource;
static get styles(): CSSResult[] { 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() { constructor() {
@ -103,12 +104,21 @@ export class LDAPSourceViewPage extends Page {
</dl> </dl>
</div> </div>
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">
<ak-modal-button href="${AdminURLManager.sources(`${this.source.pk}/update/`)}"> <ak-forms-modal>
<ak-spinner-button slot="trigger" class="pf-m-primary"> <span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext("Update LDAP Source")}
</span>
<ak-source-ldap-form
slot="form"
.sourceSlug=${this.source.slug}>
</ak-source-ldap-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${gettext("Edit")} ${gettext("Edit")}
</ak-spinner-button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -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<OAuthSource> {
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<OAuthSource> => {
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`
<ak-form-group>
<span slot="header">
${gettext("URL settings")}
</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${gettext("Authorization URL")}
?required=${true}
name="authorizationUrl">
<input type="text" value="${ifDefined(this.source?.authorizationUrl)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${gettext("URL the user is redirect to to consent the authorization.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Access token URL")}
?required=${true}
name="accessTokenUrl">
<input type="text" value="${ifDefined(this.source?.accessTokenUrl)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${gettext("URL used by authentik to retrieve tokens.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Profile URL")}
?required=${true}
name="profileUrl">
<input type="text" value="${ifDefined(this.source?.profileUrl)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${gettext("URL used by authentik to get user information.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Request token URL")}
name="requestTokenUrl">
<input type="text" value="${ifDefined(this.source?.requestTokenUrl)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${gettext("URL used to request the initial token. This URL is only required for OAuth 1.")}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${gettext("Name")}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.source?.name)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Slug")}
?required=${true}
name="slug">
<input type="text" value="${ifDefined(this.source?.slug)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="enabled">
<div class="pf-c-check">
<input type="checkbox" class="pf-c-check__input" ?checked=${this.source?.enabled || true}>
<label class="pf-c-check__label">
${gettext("Enabled")}
</label>
</div>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header">
${gettext("Protocol settings")}
</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${gettext("Consumer key")}
?required=${true}
name="consumerKey">
<input type="text" value="${ifDefined(this.source?.consumerKey)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Consumer secret")}
?required=${true}
name="consumerSecret">
<input type="text" value="${ifDefined(this.source?.consumerSecret)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Provider type")}
name="providerType">
<select class="pf-c-form-control" @change=${(ev: Event) => {
const el = (ev.target as HTMLSelectElement);
const selected = el.selectedOptions[0];
if ("data-urls-custom" in selected.attributes) {
this.showUrlOptions = true;
} else {
this.showUrlOptions = false;
}
}}>
${until(new SourcesApi(DEFAULT_CONFIG).sourcesOauthSourceTypes().then(types => {
return types.map(type => {
return html`<option ?data-urls-custom=${type.urlsCustomizable} value=${type.slug} ?selected=${this.source?.providerType === type.slug}>${type.name}</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("Keypair which is used to sign outgoing requests. Leave empty to disable signing.")}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
${this.renderUrlOptions()}
<ak-form-group>
<span slot="header">
${gettext("Flow settings")}
</span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${gettext("Authentication flow")}
?required=${true}
name="authenticationFlow">
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowDesignationEnum.Authentication,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.source?.authenticationFlow === flow.pk;
if (!this.source?.authenticationFlow && flow.slug === "default-source-authentication") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("Flow to use when authenticating existing users.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Enrollment flow")}
?required=${true}
name="enrollmentFlow">
<select class="pf-c-form-control">
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
ordering: "pk",
designation: FlowDesignationEnum.Enrollment,
}).then(flows => {
return flows.results.map(flow => {
let selected = this.source?.enrollmentFlow === flow.pk;
if (!this.source?.enrollmentFlow && flow.slug === "default-source-enrollment") {
selected = true;
}
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
});
}))}
</select>
<p class="pf-c-form__helper-text">${gettext("Flow to use when enrolling new users.")}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View file

@ -11,16 +11,17 @@ import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css";
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import AKGlobal from "../../../authentik.css"; import AKGlobal from "../../../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.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/buttons/SpinnerButton";
import "../../../elements/CodeMirror"; import "../../../elements/CodeMirror";
import "../../../elements/Tabs"; import "../../../elements/Tabs";
import "../../../elements/events/ObjectChangelog"; import "../../../elements/events/ObjectChangelog";
import "../../../elements/forms/ModalForm";
import "./OAuthSourceForm";
import { Page } from "../../../elements/Page"; import { Page } from "../../../elements/Page";
import { OAuthSource, SourcesApi } from "authentik-api"; import { OAuthSource, SourcesApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminURLManager } from "../../../api/legacy";
import { EVENT_REFRESH } from "../../../constants"; import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-source-oauth-view") @customElement("ak-source-oauth-view")
@ -48,7 +49,7 @@ export class OAuthSourceViewPage extends Page {
source?: OAuthSource; source?: OAuthSource;
static get styles(): CSSResult[] { 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() { constructor() {
@ -121,12 +122,21 @@ export class OAuthSourceViewPage extends Page {
</dl> </dl>
</div> </div>
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">
<ak-modal-button href="${AdminURLManager.sources(`${this.source.pk}/update/`)}"> <ak-forms-modal>
<ak-spinner-button slot="trigger" class="pf-m-primary"> <span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext("Update OAuth Source")}
</span>
<ak-source-oauth-form
slot="form"
.sourceSlug=${this.source.slug}>
</ak-source-oauth-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${gettext("Edit")} ${gettext("Edit")}
</ak-spinner-button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -12,16 +12,18 @@ import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css";
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import AKGlobal from "../../../authentik.css"; import AKGlobal from "../../../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.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/buttons/SpinnerButton";
import "../../../elements/CodeMirror"; import "../../../elements/CodeMirror";
import "../../../elements/Tabs"; import "../../../elements/Tabs";
import "../../../elements/events/ObjectChangelog"; import "../../../elements/events/ObjectChangelog";
import "../../../elements/forms/ModalForm";
import "./SAMLSourceForm";
import { Page } from "../../../elements/Page"; import { Page } from "../../../elements/Page";
import { SAMLSource, SourcesApi } from "authentik-api"; import { SAMLSource, SourcesApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../../api/Config"; import { DEFAULT_CONFIG } from "../../../api/Config";
import { AdminURLManager, AppURLManager } from "../../../api/legacy"; import { AppURLManager } from "../../../api/legacy";
import { EVENT_REFRESH } from "../../../constants"; import { EVENT_REFRESH } from "../../../constants";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
@ -50,7 +52,7 @@ export class SAMLSourceViewPage extends Page {
source?: SAMLSource; source?: SAMLSource;
static get styles(): CSSResult[] { 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() { constructor() {
@ -107,12 +109,21 @@ export class SAMLSourceViewPage extends Page {
</dl> </dl>
</div> </div>
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">
<ak-modal-button href="${AdminURLManager.sources(`${this.source.pk}/update/`)}"> <ak-forms-modal>
<ak-spinner-button slot="trigger" class="pf-m-primary"> <span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext("Update SAML Source")}
</span>
<ak-source-saml-form
slot="form"
.sourceSlug=${this.source.slug}>
</ak-source-saml-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${gettext("Edit")} ${gettext("Edit")}
</ak-spinner-button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -30,6 +30,8 @@ import "./pages/users/UserListPage";
import "./pages/users/UserViewPage"; import "./pages/users/UserViewPage";
import "./pages/user-settings/UserSettingsPage"; import "./pages/user-settings/UserSettingsPage";
import "./pages/TestPage";
export const ROUTES: Route[] = [ export const ROUTES: Route[] = [
// Prevent infinite Shell loops // Prevent infinite Shell loops
new Route(new RegExp("^/$")).redirect("/library"), new Route(new RegExp("^/$")).redirect("/library"),
@ -74,4 +76,5 @@ export const ROUTES: Route[] = [
new Route(new RegExp("^/outpost/service-connections$"), html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`), new Route(new RegExp("^/outpost/service-connections$"), html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`),
new Route(new RegExp("^/crypto/certificates$"), html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`), new Route(new RegExp("^/crypto/certificates$"), html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`),
new Route(new RegExp("^/user$"), html`<ak-user-settings></ak-user-settings>`), new Route(new RegExp("^/user$"), html`<ak-user-settings></ak-user-settings>`),
new Route(new RegExp("^/test$"), html`<ak-test-page></ak-test-page>`),
]; ];