diff --git a/authentik/admin/templates/administration/property_mapping/test.html b/authentik/admin/templates/administration/property_mapping/test.html
new file mode 100644
index 000000000..d52dcff56
--- /dev/null
+++ b/authentik/admin/templates/administration/property_mapping/test.html
@@ -0,0 +1,28 @@
+{% extends 'generic/form.html' %}
+
+{% load i18n %}
+
+{% block above_form %}
+
{% blocktrans with property_mapping=property_mapping %}Test {{ property_mapping }}{% endblocktrans %}
+{% endblock %}
+
+{% block beneath_form %}
+{% if result %}
+
+{% endif %}
+{% endblock %}
+
+{% block action %}
+{% trans 'Test' %}
+{% endblock %}
diff --git a/authentik/admin/urls.py b/authentik/admin/urls.py
index 680f30f5c..dc02b0073 100644
--- a/authentik/admin/urls.py
+++ b/authentik/admin/urls.py
@@ -238,11 +238,6 @@ urlpatterns = [
name="flow-delete",
),
# Property Mappings
- path(
- "property-mappings/",
- property_mappings.PropertyMappingListView.as_view(),
- name="property-mappings",
- ),
path(
"property-mappings/create/",
property_mappings.PropertyMappingCreateView.as_view(),
@@ -258,6 +253,11 @@ urlpatterns = [
property_mappings.PropertyMappingDeleteView.as_view(),
name="property-mapping-delete",
),
+ path(
+ "property-mappings//test/",
+ property_mappings.PropertyMappingTestView.as_view(),
+ name="property-mapping-test",
+ ),
# Users
path("users/", users.UserListView.as_view(), name="users"),
path("users/create/", users.UserCreateView.as_view(), name="user-create"),
diff --git a/authentik/admin/views/property_mappings.py b/authentik/admin/views/property_mappings.py
index 522b26622..53e18329e 100644
--- a/authentik/admin/views/property_mappings.py
+++ b/authentik/admin/views/property_mappings.py
@@ -1,4 +1,12 @@
"""authentik PropertyMapping administration"""
+from django.contrib.messages import views
+from authentik.admin.forms.policies import PolicyTestForm
+from django.http import HttpResponse
+from json import dumps
+from typing import Any
+from django.db.models import QuerySet
+from django.views.generic import FormView
+from django.views.generic.detail import DetailView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin,
@@ -20,22 +28,6 @@ from authentik.admin.views.utils import (
from authentik.core.models import PropertyMapping
-class PropertyMappingListView(
- LoginRequiredMixin,
- PermissionListMixin,
- UserPaginateListMixin,
- SearchListMixin,
- InheritanceListView,
-):
- """Show list of all property_mappings"""
-
- model = PropertyMapping
- permission_required = "authentik_core.view_propertymapping"
- template_name = "administration/property_mapping/list.html"
- ordering = "name"
- search_fields = ["name", "expression"]
-
-
class PropertyMappingCreateView(
SuccessMessageMixin,
BackSuccessUrlMixin,
@@ -81,3 +73,38 @@ class PropertyMappingDeleteView(
template_name = "generic/delete.html"
success_url = reverse_lazy("authentik_admin:property-mappings")
success_message = _("Successfully deleted Property Mapping")
+
+
+class PropertyMappingTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, FormView):
+ """View to test property mappings"""
+
+ model = PropertyMapping
+ form_class = PolicyTestForm
+ permission_required = "authentik_core.view_propertymapping"
+ template_name = "administration/property_mapping/test.html"
+ object = None
+
+ def get_object(self, queryset=None) -> PropertyMapping:
+ return (
+ PropertyMapping.objects.filter(pk=self.kwargs.get("pk")).select_subclasses().first()
+ )
+
+ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
+ kwargs["property_mapping"] = self.get_object()
+ return super().get_context_data(**kwargs)
+
+ def post(self, *args, **kwargs) -> HttpResponse:
+ self.object = self.get_object()
+ return super().post(*args, **kwargs)
+
+ def form_valid(self, form: PolicyTestForm) -> HttpResponse:
+ mapping = self.get_object()
+ user = form.cleaned_data.get("user")
+
+ context = self.get_context_data(form=form)
+ try:
+ result = mapping.evaluate(user, self.request, **form.cleaned_data.get("context", {}))
+ context["result"] = dumps(result, indent=4)
+ except Exception as exc: # pylint: disable=broad-except
+ context["result"] = str(exc)
+ return self.render_to_response(context)
diff --git a/web/src/pages/property-mappings/PropertyMappingListPage.ts b/web/src/pages/property-mappings/PropertyMappingListPage.ts
index 97cdbe2d4..d895f4594 100644
--- a/web/src/pages/property-mappings/PropertyMappingListPage.ts
+++ b/web/src/pages/property-mappings/PropertyMappingListPage.ts
@@ -54,13 +54,19 @@ export class PropertyMappingListPage extends TablePage {
html`
- Edit
+ ${gettext("Edit")}
+
+
+
+
+
+ ${gettext("Test")}
- Delete
+ ${gettext("Delete")}