diff --git a/authentik/core/api/propertymappings.py b/authentik/core/api/propertymappings.py index cd0cc48272..01f895c442 100644 --- a/authentik/core/api/propertymappings.py +++ b/authentik/core/api/propertymappings.py @@ -1,7 +1,6 @@ """PropertyMapping API Views""" from json import dumps -from django.urls import reverse from drf_yasg.utils import swagger_auto_schema from guardian.shortcuts import get_objects_for_user from rest_framework import mixins @@ -19,6 +18,7 @@ from authentik.core.api.utils import ( PassiveSerializer, TypeCreateSerializer, ) +from authentik.core.expression import PropertyMappingEvaluator from authentik.core.models import PropertyMapping from authentik.lib.templatetags.authentik_utils import verbose_name from authentik.lib.utils.reflection import all_subclasses @@ -41,6 +41,12 @@ class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer): """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", "") + def validate_expression(self, expression: str) -> str: + """Test Syntax""" + evaluator = PropertyMappingEvaluator() + evaluator.validate(expression) + return expression + class Meta: model = PropertyMapping @@ -109,7 +115,7 @@ class PropertyMappingViewSet( if not users.exists(): raise PermissionDenied() - response_data = {"successful": True} + response_data = {"successful": True, "result": ""} try: result = mapping.evaluate( users.first(), diff --git a/authentik/core/tests/test_property_mapping_api.py b/authentik/core/tests/test_property_mapping_api.py index 4912e17ed7..48b524b0fe 100644 --- a/authentik/core/tests/test_property_mapping_api.py +++ b/authentik/core/tests/test_property_mapping_api.py @@ -2,8 +2,10 @@ from json import dumps from django.urls import reverse +from rest_framework.serializers import ValidationError from rest_framework.test import APITestCase +from authentik.core.api.propertymappings import PropertyMappingSerializer from authentik.core.models import PropertyMapping, User @@ -19,7 +21,7 @@ class TestPropertyMappingAPI(APITestCase): self.client.force_login(self.user) def test_test_call(self): - """Test Policy's test endpoint""" + """Test PropertMappings's test endpoint""" response = self.client.post( reverse( "authentik_api:propertymapping-test", kwargs={"pk": self.mapping.pk} @@ -32,3 +34,12 @@ class TestPropertyMappingAPI(APITestCase): response.content.decode(), {"result": dumps({"foo": "bar"}), "successful": True}, ) + + def test_validate(self): + """Test PropertyMappings's validation""" + # Because the root property-mapping has no write operation, we just instantiate + # a serializer and test inline + expr = "return True" + self.assertEqual(PropertyMappingSerializer().validate_expression(expr), expr) + with self.assertRaises(ValidationError): + print(PropertyMappingSerializer().validate_expression("/")) diff --git a/authentik/lib/expression/evaluator.py b/authentik/lib/expression/evaluator.py index 27db18023a..3c6c7087ca 100644 --- a/authentik/lib/expression/evaluator.py +++ b/authentik/lib/expression/evaluator.py @@ -3,8 +3,8 @@ import re from textwrap import indent from typing import Any, Iterable, Optional -from django.core.exceptions import ValidationError from requests import Session +from rest_framework.serializers import ValidationError from sentry_sdk.hub import Hub from sentry_sdk.tracing import Span from structlog.stdlib import get_logger diff --git a/authentik/policies/expression/tests.py b/authentik/policies/expression/tests.py index ceb80bd3a7..4fc0df92fb 100644 --- a/authentik/policies/expression/tests.py +++ b/authentik/policies/expression/tests.py @@ -1,7 +1,7 @@ """evaluator tests""" -from django.core.exceptions import ValidationError from django.test import TestCase from guardian.shortcuts import get_anonymous_user +from rest_framework.serializers import ValidationError from authentik.policies.exceptions import PolicyException from authentik.policies.expression.evaluator import PolicyEvaluator diff --git a/authentik/providers/oauth2/api/scope.py b/authentik/providers/oauth2/api/scope.py index 3c4d6a0777..6ddc123104 100644 --- a/authentik/providers/oauth2/api/scope.py +++ b/authentik/providers/oauth2/api/scope.py @@ -1,25 +1,19 @@ """OAuth2Provider API Views""" -from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet -from authentik.core.api.utils import MetaNameSerializer +from authentik.core.api.propertymappings import PropertyMappingSerializer from authentik.providers.oauth2.models import ScopeMapping -class ScopeMappingSerializer(ModelSerializer, MetaNameSerializer): +class ScopeMappingSerializer(PropertyMappingSerializer): """ScopeMapping Serializer""" class Meta: model = ScopeMapping - fields = [ - "pk", - "name", + fields = PropertyMappingSerializer.Meta.fields + [ "scope_name", "description", - "expression", - "verbose_name", - "verbose_name_plural", ] diff --git a/authentik/providers/saml/api.py b/authentik/providers/saml/api.py index 052de8b5c8..e92e8ba411 100644 --- a/authentik/providers/saml/api.py +++ b/authentik/providers/saml/api.py @@ -4,11 +4,11 @@ from rest_framework.decorators import action from rest_framework.fields import ReadOnlyField from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer, Serializer +from rest_framework.serializers import Serializer from rest_framework.viewsets import ModelViewSet +from authentik.core.api.propertymappings import PropertyMappingSerializer from authentik.core.api.providers import ProviderSerializer -from authentik.core.api.utils import MetaNameSerializer from authentik.core.models import Provider from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.views.metadata import DescriptorDownloadView @@ -67,20 +67,15 @@ class SAMLProviderViewSet(ModelViewSet): return Response({"metadata": ""}) -class SAMLPropertyMappingSerializer(ModelSerializer, MetaNameSerializer): +class SAMLPropertyMappingSerializer(PropertyMappingSerializer): """SAMLPropertyMapping Serializer""" class Meta: model = SAMLPropertyMapping - fields = [ - "pk", - "name", + fields = PropertyMappingSerializer.Meta.fields + [ "saml_name", "friendly_name", - "expression", - "verbose_name", - "verbose_name_plural", ] diff --git a/authentik/providers/saml/forms.py b/authentik/providers/saml/forms.py index d70b27b61b..6d258b4f24 100644 --- a/authentik/providers/saml/forms.py +++ b/authentik/providers/saml/forms.py @@ -6,11 +6,8 @@ from defusedxml.ElementTree import fromstring from django import forms from django.core.exceptions import ValidationError from django.core.validators import FileExtensionValidator -from django.utils.html import mark_safe from django.utils.translation import gettext_lazy as _ -from authentik.admin.fields import CodeMirrorWidget -from authentik.core.expression import PropertyMappingEvaluator from authentik.crypto.models import CertificateKeyPair from authentik.flows.models import Flow, FlowDesignation from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider @@ -59,40 +56,6 @@ class SAMLProviderForm(forms.ModelForm): } -class SAMLPropertyMappingForm(forms.ModelForm): - """SAML Property Mapping form""" - - template_name = "providers/saml/property_mapping_form.html" - - def clean_expression(self): - """Test Syntax""" - expression = self.cleaned_data.get("expression") - evaluator = PropertyMappingEvaluator() - evaluator.validate(expression) - return expression - - class Meta: - - model = SAMLPropertyMapping - fields = ["name", "saml_name", "friendly_name", "expression"] - widgets = { - "name": forms.TextInput(), - "saml_name": forms.TextInput(), - "friendly_name": forms.TextInput(), - "expression": CodeMirrorWidget(mode="python"), - } - help_texts = { - "saml_name": mark_safe( - _( - "URN OID used by SAML. This is optional. " - 'Reference.' - " If this property mapping is used for NameID Property, " - "this field is discarded." - ) - ), - } - - class SAMLProviderImportForm(forms.Form): """Create a SAML Provider from SP Metadata.""" diff --git a/authentik/providers/saml/models.py b/authentik/providers/saml/models.py index 77e900444a..9be9559f56 100644 --- a/authentik/providers/saml/models.py +++ b/authentik/providers/saml/models.py @@ -192,10 +192,8 @@ class SAMLPropertyMapping(PropertyMapping): friendly_name = models.TextField(default=None, blank=True, null=True) @property - def form(self) -> Type[ModelForm]: - from authentik.providers.saml.forms import SAMLPropertyMappingForm - - return SAMLPropertyMappingForm + def component(self) -> str: + return "ak-property-mapping-saml-form" @property def serializer(self) -> Type[Serializer]: diff --git a/authentik/providers/saml/templates/providers/saml/property_mapping_form.html b/authentik/providers/saml/templates/providers/saml/property_mapping_form.html deleted file mode 100644 index 030095abe4..0000000000 --- a/authentik/providers/saml/templates/providers/saml/property_mapping_form.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "generic/form.html" %} - -{% load i18n %} - -{% block beneath_form %} -
- Expression using Python. See here for a list of all variables. -
-