web/admin: migrate policybinding form
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
3124b0f39c
commit
e476186cbc
|
@ -1,43 +0,0 @@
|
|||
"""admin tests"""
|
||||
from uuid import uuid4
|
||||
|
||||
from django import forms
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from authentik.admin.views.policies_bindings import PolicyBindingCreateView
|
||||
from authentik.core.models import Application
|
||||
from authentik.policies.forms import PolicyBindingForm
|
||||
|
||||
|
||||
class TestPolicyBindingView(TestCase):
|
||||
"""Generic admin tests"""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def test_without_get_param(self):
|
||||
"""Test PolicyBindingCreateView without get params"""
|
||||
request = self.factory.get("/")
|
||||
view = PolicyBindingCreateView(request=request)
|
||||
self.assertEqual(view.get_initial(), {})
|
||||
|
||||
def test_with_params_invalid(self):
|
||||
"""Test PolicyBindingCreateView with invalid get params"""
|
||||
request = self.factory.get("/", {"target": uuid4()})
|
||||
view = PolicyBindingCreateView(request=request)
|
||||
self.assertEqual(view.get_initial(), {})
|
||||
|
||||
def test_with_params(self):
|
||||
"""Test PolicyBindingCreateView with get params"""
|
||||
target = Application.objects.create(name="test")
|
||||
request = self.factory.get("/", {"target": target.pk.hex})
|
||||
view = PolicyBindingCreateView(request=request)
|
||||
self.assertEqual(view.get_initial(), {"target": target, "order": 0})
|
||||
|
||||
self.assertTrue(
|
||||
isinstance(
|
||||
PolicyBindingForm(initial={"target": "foo"}).fields["target"].widget,
|
||||
forms.HiddenInput,
|
||||
)
|
||||
)
|
|
@ -4,7 +4,6 @@ from django.urls import path
|
|||
from authentik.admin.views import (
|
||||
outposts_service_connections,
|
||||
policies,
|
||||
policies_bindings,
|
||||
property_mappings,
|
||||
providers,
|
||||
sources,
|
||||
|
@ -27,17 +26,6 @@ urlpatterns = [
|
|||
policies.PolicyUpdateView.as_view(),
|
||||
name="policy-update",
|
||||
),
|
||||
# Policy bindings
|
||||
path(
|
||||
"policies/bindings/create/",
|
||||
policies_bindings.PolicyBindingCreateView.as_view(),
|
||||
name="policy-binding-create",
|
||||
),
|
||||
path(
|
||||
"policies/bindings/<uuid:pk>/update/",
|
||||
policies_bindings.PolicyBindingUpdateView.as_view(),
|
||||
name="policy-binding-update",
|
||||
),
|
||||
# Providers
|
||||
path(
|
||||
"providers/create/",
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
"""authentik PolicyBinding administration"""
|
||||
from typing import Any
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.db.models import Max
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import UpdateView
|
||||
from guardian.mixins import PermissionRequiredMixin
|
||||
|
||||
from authentik.lib.views import CreateAssignPermView
|
||||
from authentik.policies.forms import PolicyBindingForm
|
||||
from authentik.policies.models import PolicyBinding, PolicyBindingModel
|
||||
|
||||
|
||||
class PolicyBindingCreateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
DjangoPermissionRequiredMixin,
|
||||
CreateAssignPermView,
|
||||
):
|
||||
"""Create new PolicyBinding"""
|
||||
|
||||
model = PolicyBinding
|
||||
permission_required = "authentik_policies.add_policybinding"
|
||||
form_class = PolicyBindingForm
|
||||
|
||||
template_name = "generic/create.html"
|
||||
success_url = reverse_lazy("authentik_core:if-admin")
|
||||
success_message = _("Successfully created PolicyBinding")
|
||||
|
||||
def get_initial(self) -> dict[str, Any]:
|
||||
if "target" in self.request.GET:
|
||||
initial_target_pk = self.request.GET["target"]
|
||||
targets = PolicyBindingModel.objects.filter(
|
||||
pk=initial_target_pk
|
||||
).select_subclasses()
|
||||
if not targets.exists():
|
||||
return {}
|
||||
max_order = PolicyBinding.objects.filter(target=targets.first()).aggregate(
|
||||
Max("order")
|
||||
)["order__max"]
|
||||
if not isinstance(max_order, int):
|
||||
max_order = -1
|
||||
return {"target": targets.first(), "order": max_order + 1}
|
||||
return super().get_initial()
|
||||
|
||||
|
||||
class PolicyBindingUpdateView(
|
||||
SuccessMessageMixin,
|
||||
LoginRequiredMixin,
|
||||
PermissionRequiredMixin,
|
||||
UpdateView,
|
||||
):
|
||||
"""Update policybinding"""
|
||||
|
||||
model = PolicyBinding
|
||||
permission_required = "authentik_policies.change_policybinding"
|
||||
form_class = PolicyBindingForm
|
||||
|
||||
template_name = "generic/update.html"
|
||||
success_url = reverse_lazy("authentik_core:if-admin")
|
||||
success_message = _("Successfully updated PolicyBinding")
|
|
@ -29,6 +29,9 @@ def get_attrs(obj: SerializerModel) -> dict[str, Any]:
|
|||
for to_remove_name in to_remove:
|
||||
if to_remove_name in data:
|
||||
data.pop(to_remove_name)
|
||||
for key in list(data.keys()):
|
||||
if key.endswith("_obj"):
|
||||
data.pop(key)
|
||||
return data
|
||||
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ class PolicyBindingModelForeignKey(PrimaryKeyRelatedField):
|
|||
# checks the PK of PolicyBindingModel (for example),
|
||||
# but we get given the Primary Key of the inheriting class
|
||||
for model in self.get_queryset().select_subclasses().all():
|
||||
if str(model.pk) == data:
|
||||
if str(model.pk) == str(data):
|
||||
return model
|
||||
# as a fallback we still try a direct lookup
|
||||
return self.get_queryset().get_subclass(pk=data)
|
||||
|
@ -82,7 +82,13 @@ class PolicyBindingSerializer(ModelSerializer):
|
|||
|
||||
def validate(self, data: OrderedDict) -> OrderedDict:
|
||||
"""Check that either policy, group or user is set."""
|
||||
count = sum([bool(data["policy"]), bool(data["group"]), bool(data["user"])])
|
||||
count = sum(
|
||||
[
|
||||
bool(data.get("policy", None)),
|
||||
bool(data.get("group", None)),
|
||||
bool(data.get("user", None)),
|
||||
]
|
||||
)
|
||||
invalid = count > 1
|
||||
empty = count < 1
|
||||
if invalid:
|
||||
|
|
|
@ -4,10 +4,6 @@ export class AdminURLManager {
|
|||
return `/administration/policies/${rest}`;
|
||||
}
|
||||
|
||||
static policyBindings(rest: string): string {
|
||||
return `/administration/policies/bindings/${rest}`;
|
||||
}
|
||||
|
||||
static providers(rest: string): string {
|
||||
return `/administration/providers/${rest}`;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ import { AdminURLManager } from "../../api/legacy";
|
|||
|
||||
import "../../elements/forms/ModalForm";
|
||||
import "../groups/GroupForm";
|
||||
import "../users/UserForm";
|
||||
import "./PolicyBindingForm";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
|
||||
@customElement("ak-bound-policies-list")
|
||||
export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||
|
@ -43,11 +46,11 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
|
||||
getPolicyUserGroupRow(item: PolicyBinding): string {
|
||||
if (item.policy) {
|
||||
return gettext(`Policy ${item.policy.name}`);
|
||||
return gettext(`Policy ${item.policyObj?.name}`);
|
||||
} else if (item.group) {
|
||||
return gettext(`Group ${item.group.name}`);
|
||||
return gettext(`Group ${item.groupObj?.name}`);
|
||||
} else if (item.user) {
|
||||
return gettext(`User ${item.user.name}`);
|
||||
return gettext(`User ${item.userObj?.name}`);
|
||||
} else {
|
||||
return gettext("");
|
||||
}
|
||||
|
@ -55,7 +58,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
|
||||
getObjectEditButton(item: PolicyBinding): TemplateResult {
|
||||
if (item.policy) {
|
||||
return html`<ak-modal-button href="${AdminURLManager.policies(`${item.policy?.policyUuid}/update/`)}">
|
||||
return html`<ak-modal-button href="${AdminURLManager.policies(`${item.policy}/update/`)}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
||||
${gettext("Edit Policy")}
|
||||
</ak-spinner-button>
|
||||
|
@ -69,19 +72,26 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
<span slot="header">
|
||||
${gettext("Update Group")}
|
||||
</span>
|
||||
<ak-group-form slot="form" .group=${item.group}>
|
||||
<ak-group-form slot="form" .group=${item.groupObj}>
|
||||
</ak-group-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${gettext("Edit Group")}
|
||||
</button>
|
||||
</ak-forms-modal>`;
|
||||
} else if (item.user) {
|
||||
return html`<ak-modal-button href="${AdminURLManager.policies(`${item.user?.id}/update/`)}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
||||
${gettext("Edit User")}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>`;
|
||||
return html`<ak-forms-modal>
|
||||
<span slot="submit">
|
||||
${gettext("Update")}
|
||||
</span>
|
||||
<span slot="header">
|
||||
${gettext("Update User")}
|
||||
</span>
|
||||
<ak-user-form slot="form" .user=${item.userObj}>
|
||||
</ak-user-form>
|
||||
<button slot="trigger" class="pf-m-secondary pf-c-button">
|
||||
${gettext("Edit")}
|
||||
</button>
|
||||
</ak-forms-modal>`;
|
||||
} else {
|
||||
return html``;
|
||||
}
|
||||
|
@ -95,12 +105,19 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
html`${item.timeout}`,
|
||||
html`
|
||||
${this.getObjectEditButton(item)}
|
||||
<ak-modal-button href="${AdminURLManager.policyBindings(`${item.pk}/update/`)}">
|
||||
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">
|
||||
${gettext("Update")}
|
||||
</span>
|
||||
<span slot="header">
|
||||
${gettext("Update Binding")}
|
||||
</span>
|
||||
<ak-policy-binding-form slot="form" .binding=${item} targetPk=${ifDefined(this.target)}>
|
||||
</ak-policy-binding-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
||||
${gettext("Edit Binding")}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-forms-delete
|
||||
.obj=${item}
|
||||
objectLabel=${gettext("Policy binding")}
|
||||
|
@ -122,12 +139,19 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
${gettext("No policies are currently bound to this object.")}
|
||||
</div>
|
||||
<div slot="primary">
|
||||
<ak-modal-button href=${AdminURLManager.policyBindings(`create/?target=${this.target}`)}>
|
||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||
${gettext("Bind Policy")}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">
|
||||
${gettext("Create")}
|
||||
</span>
|
||||
<span slot="header">
|
||||
${gettext("Create Binding")}
|
||||
</span>
|
||||
<ak-policy-binding-form slot="form" targetPk=${ifDefined(this.target)}>
|
||||
</ak-policy-binding-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${gettext("Create Binding")}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
</div>
|
||||
</ak-empty-state>`);
|
||||
}
|
||||
|
@ -154,12 +178,19 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
|
|||
}), html`<ak-spinner></ak-spinner>`)}
|
||||
</ul>
|
||||
</ak-dropdown>
|
||||
<ak-modal-button href=${AdminURLManager.policyBindings(`create/?target=${this.target}`)}>
|
||||
<ak-spinner-button slot="trigger" class="pf-m-primary">
|
||||
${gettext("Bind Policy")}
|
||||
</ak-spinner-button>
|
||||
<div slot="modal"></div>
|
||||
</ak-modal-button>
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">
|
||||
${gettext("Create")}
|
||||
</span>
|
||||
<span slot="header">
|
||||
${gettext("Create Binding")}
|
||||
</span>
|
||||
<ak-policy-binding-form slot="form" targetPk=${ifDefined(this.target)}>
|
||||
</ak-policy-binding-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${gettext("Create Binding")}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
${super.renderToolbar()}
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
import { CoreApi, PoliciesApi, Policy, PolicyBinding } 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 { until } from "lit-html/directives/until";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import { groupBy } from "../../utils";
|
||||
import "../../elements/forms/HorizontalFormElement";
|
||||
|
||||
@customElement("ak-policy-binding-form")
|
||||
export class PolicyBindingForm extends Form<PolicyBinding> {
|
||||
|
||||
@property({attribute: false})
|
||||
binding?: PolicyBinding;
|
||||
|
||||
@property()
|
||||
targetPk?: string;
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.binding) {
|
||||
return gettext("Successfully updated binding.");
|
||||
} else {
|
||||
return gettext("Successfully created binding.");
|
||||
}
|
||||
}
|
||||
|
||||
async customValidate(form: PolicyBinding): Promise<PolicyBinding> {
|
||||
return form;
|
||||
}
|
||||
|
||||
send = (data: PolicyBinding): Promise<PolicyBinding> => {
|
||||
if (this.binding) {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUpdate({
|
||||
policyBindingUuid: this.binding.pk || "",
|
||||
data: data
|
||||
});
|
||||
} else {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsCreate({
|
||||
data: data
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
groupPolicies(policies: Policy[]): TemplateResult {
|
||||
return html`
|
||||
${groupBy<Policy>(policies, (p => p.verboseName || "")).map(([group, policies]) => {
|
||||
return html`<optgroup label=${group}>
|
||||
${policies.map(p => {
|
||||
const selected = (this.binding?.policy === p.pk);
|
||||
return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>${p.name}</option>`;
|
||||
})}
|
||||
</optgroup>`;
|
||||
})}
|
||||
`;
|
||||
}
|
||||
|
||||
getOrder(): Promise<number> {
|
||||
if (this.binding) {
|
||||
return Promise.resolve(this.binding.order);
|
||||
}
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
|
||||
target: this.targetPk || "",
|
||||
}).then(bindings => {
|
||||
const orders = bindings.results.map(binding => binding.order);
|
||||
return Math.max(...orders) + 1;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-form-element-horizontal
|
||||
label=${gettext("Policy")}
|
||||
name="policy">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.binding?.policy === undefined}>---------</option>
|
||||
${until(new PoliciesApi(DEFAULT_CONFIG).policiesAllList({
|
||||
ordering: "pk"
|
||||
}).then(policies => {
|
||||
return this.groupPolicies(policies.results);
|
||||
}), html``)}
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${gettext("Group")}
|
||||
name="group">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.binding?.group === undefined}>---------</option>
|
||||
${until(new CoreApi(DEFAULT_CONFIG).coreGroupsList({
|
||||
ordering: "pk"
|
||||
}).then(groups => {
|
||||
return groups.results.map(group => {
|
||||
return html`<option value=${ifDefined(group.pk)} ?selected=${group.pk === this.binding?.group}>${group.name}</option>`;
|
||||
});
|
||||
}), html``)}
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${gettext("User")}
|
||||
name="user">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.binding?.user === undefined}>---------</option>
|
||||
${until(new CoreApi(DEFAULT_CONFIG).coreUsersList({
|
||||
ordering: "pk"
|
||||
}).then(users => {
|
||||
return users.results.map(user => {
|
||||
return html`<option value=${ifDefined(user.pk)} ?selected=${user.pk === this.binding?.user}>${user.name}</option>`;
|
||||
});
|
||||
}), html``)}
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<input required name="target" type="hidden" value=${ifDefined(this.binding?.target || this.targetPk)}>
|
||||
<ak-form-element-horizontal name="enabled">
|
||||
<div class="pf-c-check">
|
||||
<input type="checkbox" class="pf-c-check__input" ?checked=${this.binding?.enabled || true}>
|
||||
<label class="pf-c-check__label">
|
||||
${gettext("Enabled")}
|
||||
</label>
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${gettext("Order")}
|
||||
?required=${true}
|
||||
name="order">
|
||||
<input type="number" value="${until(this.getOrder(), this.binding?.order)}" class="pf-c-form-control" required>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${gettext("Timeout")}
|
||||
?required=${true}
|
||||
name="timeout">
|
||||
<input type="number" value="${this.binding?.timeout || 30}" class="pf-c-form-control" required>
|
||||
</ak-form-element-horizontal>
|
||||
</form>`;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue