providers/oauth2: migrate scope mapping to web

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-03-31 23:07:57 +02:00
parent 656fe00302
commit 6a69425688
9 changed files with 126 additions and 103 deletions

View File

@ -1,13 +1,7 @@
"""authentik URL Configuration""" """authentik URL Configuration"""
from django.urls import path from django.urls import path
from authentik.admin.views import ( from authentik.admin.views import policies, providers, sources, stages
policies,
property_mappings,
providers,
sources,
stages,
)
from authentik.providers.saml.views.metadata import MetadataImportView from authentik.providers.saml.views.metadata import MetadataImportView
urlpatterns = [ urlpatterns = [
@ -48,15 +42,4 @@ urlpatterns = [
stages.StageUpdateView.as_view(), stages.StageUpdateView.as_view(),
name="stage-update", name="stage-update",
), ),
# Property Mappings
path(
"property-mappings/create/",
property_mappings.PropertyMappingCreateView.as_view(),
name="property-mapping-create",
),
path(
"property-mappings/<uuid:pk>/update/",
property_mappings.PropertyMappingUpdateView.as_view(),
name="property-mapping-update",
),
] ]

View File

@ -1,41 +0,0 @@
"""authentik PropertyMapping 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 PropertyMapping
class PropertyMappingCreateView(
SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
InheritanceCreateView,
):
"""Create new PropertyMapping"""
model = PropertyMapping
permission_required = "authentik_core.add_propertymapping"
success_url = "/"
template_name = "generic/create.html"
success_message = _("Successfully created Property Mapping")
class PropertyMappingUpdateView(
SuccessMessageMixin,
LoginRequiredMixin,
PermissionRequiredMixin,
InheritanceUpdateView,
):
"""Update property_mapping"""
model = PropertyMapping
permission_required = "authentik_core.change_propertymapping"
success_url = "/"
template_name = "generic/update.html"
success_message = _("Successfully updated Property Mapping")

View File

@ -35,18 +35,12 @@ class PropertyMappingTestResultSerializer(PassiveSerializer):
class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer): class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer):
"""PropertyMapping Serializer""" """PropertyMapping Serializer"""
object_type = SerializerMethodField(method_name="get_type") object_type = SerializerMethodField()
def get_type(self, obj): def get_object_type(self, obj: PropertyMapping) -> str:
"""Get object type so that we know which API Endpoint to use to get the full object""" """Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace("propertymapping", "") return obj._meta.object_name.lower().replace("propertymapping", "")
def to_representation(self, instance: PropertyMapping):
# pyright: reportGeneralTypeIssues=false
if instance.__class__ == PropertyMapping:
return super().to_representation(instance)
return instance.serializer(instance=instance).data
class Meta: class Meta:
model = PropertyMapping model = PropertyMapping
@ -89,8 +83,7 @@ class PropertyMappingViewSet(
{ {
"name": verbose_name(subclass), "name": verbose_name(subclass),
"description": subclass.__doc__, "description": subclass.__doc__,
"link": reverse("authentik_admin:property-mapping-create") "link": subclass.component,
+ f"?type={subclass.__name__}",
} }
) )
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -382,8 +382,8 @@ class PropertyMapping(SerializerModel, ManagedModel):
objects = InheritanceManager() objects = InheritanceManager()
@property @property
def form(self) -> Type[ModelForm]: def component(self) -> str:
"""Return Form class used to edit this object""" """Return component used to edit this object"""
raise NotImplementedError raise NotImplementedError
@property @property

View File

@ -112,10 +112,8 @@ class ScopeMapping(PropertyMapping):
) )
@property @property
def form(self) -> Type[ModelForm]: def component(self) -> str:
from authentik.providers.oauth2.forms import ScopeMappingForm return "ak-service-connection-docker-form"
return ScopeMappingForm
@property @property
def serializer(self) -> Type[Serializer]: def serializer(self) -> Type[Serializer]:

View File

@ -1,14 +0,0 @@
{% extends "generic/form.html" %}
{% load i18n %}
{% block beneath_form %}
<div class="pf-c-form__group ">
<label for="" class="pf-c-form__label"></label>
<div class="c-form__horizontal-group">
<p>
Expression using Python. See <a href="https://goauthentik.io/docs/property-mappings/expression/">here</a> for a list of all variables.
</p>
</div>
</div>
{% endblock %}

View File

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

View File

@ -8,13 +8,16 @@ import "../../elements/buttons/Dropdown";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import "../../elements/forms/DeleteForm"; import "../../elements/forms/DeleteForm";
import "../../elements/forms/ModalForm"; import "../../elements/forms/ModalForm";
import "../../elements/forms/ProxyForm";
import "./PropertyMappingTestForm"; import "./PropertyMappingTestForm";
import "./PropertyMappingScopeForm";
import "./PropertyMappingLDAPForm";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { PAGE_SIZE } from "../../constants"; import { PAGE_SIZE } from "../../constants";
import { PropertyMapping, PropertymappingsApi } from "authentik-api"; import { PropertyMapping, PropertymappingsApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy"; import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-property-mapping-list") @customElement("ak-property-mapping-list")
export class PropertyMappingListPage extends TablePage<PropertyMapping> { export class PropertyMappingListPage extends TablePage<PropertyMapping> {
@ -60,12 +63,28 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
html`${item.name}`, html`${item.name}`,
html`${item.verboseName}`, html`${item.verboseName}`,
html` html`
<ak-modal-button href="${AdminURLManager.propertyMappings(`${item.pk}/update/`)}"> <ak-forms-modal>
<ak-spinner-button slot="trigger" class="pf-m-secondary"> <span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext(`Update ${item.verboseName}`)}
</span>
<ak-proxy-form
slot="form"
.args=${{
"mappingUUID": item.pk
}}
type=${ifDefined(item.objectType)}
.typeMap=${{
"scopemapping": "ak-property-mapping-scope-form",
"ldap": "ak-property-mapping-ldap-form",
}}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${gettext("Edit")} ${gettext("Edit")}
</ak-spinner-button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}> <ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
<span slot="submit"> <span slot="submit">
${gettext("Test")} ${gettext("Test")}
@ -105,12 +124,22 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
${until(new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTypes({}).then((types) => { ${until(new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsAllTypes({}).then((types) => {
return types.map((type) => { return types.map((type) => {
return html`<li> return html`<li>
<ak-modal-button href="${type.link}"> <ak-forms-modal>
<button slot="trigger" class="pf-c-dropdown__menu-item">${type.name}<br> <span slot="submit">
${gettext("Create")}
</span>
<span slot="header">
${gettext(`Create ${type.name}`)}
</span>
<ak-proxy-form
slot="form"
type=${type.link}>
</ak-proxy-form>
<button slot="trigger" class="pf-c-dropdown__menu-item">
${type.name}<br>
<small>${type.description}</small> <small>${type.description}</small>
</button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
</li>`; </li>`;
}); });
}), html`<ak-spinner></ak-spinner>`)} }), html`<ak-spinner></ak-spinner>`)}

View File

@ -0,0 +1,79 @@
import { ScopeMapping, PropertymappingsApi } 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 { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/forms/HorizontalFormElement";
@customElement("ak-property-mapping-scope-form")
export class PropertyMappingScopeForm extends Form<ScopeMapping> {
set mappingUUID(value: string) {
new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsScopeRead({
pmUuid: value,
}).then(mapping => {
this.mapping = mapping;
});
}
@property({attribute: false})
mapping?: ScopeMapping;
getSuccessMessage(): string {
if (this.mapping) {
return gettext("Successfully updated mapping.");
} else {
return gettext("Successfully created mapping.");
}
}
send = (data: ScopeMapping): Promise<ScopeMapping> => {
if (this.mapping) {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsScopeUpdate({
pmUuid: this.mapping.pk || "",
data: data
});
} else {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsScopeCreate({
data: data
});
}
};
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.mapping?.name)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Scope name")}
?required=${true}
name="scopeName">
<input type="text" value="${ifDefined(this.mapping?.scopeName)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${gettext("Scope which the client can specify to access these properties.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Description")}
?required=${true}
name="description">
<input type="text" value="${ifDefined(this.mapping?.description)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${gettext("Description shown to the user when consenting. If left empty, the user won't be informed.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Expression")}
name="expression">
<ak-codemirror mode="python" value="${this.mapping?.expression}">
</ak-codemirror>
<p class="pf-c-form__helper-text">
Expression using Python. See <a href="https://goauthentik.io/docs/property-mappings/expression/">here</a> for a list of all variables.
</p>
</ak-form-element-horizontal>
</form>`;
}
}