From d3f2f987e00325dd3f4cc7a91cd1a7f6e8ffccc6 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Wed, 31 Mar 2021 23:31:24 +0200 Subject: [PATCH] providers/saml: migrate saml property mappings to web Signed-off-by: Jens Langhammer --- authentik/core/api/propertymappings.py | 10 ++- .../core/tests/test_property_mapping_api.py | 13 ++- authentik/lib/expression/evaluator.py | 2 +- authentik/policies/expression/tests.py | 2 +- authentik/providers/oauth2/api/scope.py | 12 +-- authentik/providers/saml/api.py | 13 +-- authentik/providers/saml/forms.py | 37 --------- authentik/providers/saml/models.py | 6 +- .../providers/saml/property_mapping_form.html | 14 ---- authentik/sources/ldap/api.py | 13 +-- .../PropertyMappingListPage.ts | 2 + .../PropertyMappingSAMLForm.ts | 82 +++++++++++++++++++ 12 files changed, 119 insertions(+), 87 deletions(-) delete mode 100644 authentik/providers/saml/templates/providers/saml/property_mapping_form.html create mode 100644 web/src/pages/property-mappings/PropertyMappingSAMLForm.ts diff --git a/authentik/core/api/propertymappings.py b/authentik/core/api/propertymappings.py index cd0cc4827..01f895c44 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 4912e17ed..48b524b0f 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 27db18023..3c6c7087c 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 ceb80bd3a..4fc0df92f 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 3c4d6a077..6ddc12310 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 052de8b5c..e92e8ba41 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 d70b27b61..6d258b4f2 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 77e900444..9be9559f5 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 030095abe..000000000 --- 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. -

-
-
-{% endblock %} diff --git a/authentik/sources/ldap/api.py b/authentik/sources/ldap/api.py index 01aa37d1c..cb89f0e44 100644 --- a/authentik/sources/ldap/api.py +++ b/authentik/sources/ldap/api.py @@ -8,11 +8,11 @@ from rest_framework.decorators import action from rest_framework.fields import DateTimeField from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet +from authentik.core.api.propertymappings import PropertyMappingSerializer from authentik.core.api.sources import SourceSerializer -from authentik.core.api.utils import MetaNameSerializer, PassiveSerializer +from authentik.core.api.utils import PassiveSerializer from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource @@ -70,18 +70,13 @@ class LDAPSourceViewSet(ModelViewSet): ) -class LDAPPropertyMappingSerializer(ModelSerializer, MetaNameSerializer): +class LDAPPropertyMappingSerializer(PropertyMappingSerializer): """LDAP PropertyMapping Serializer""" class Meta: model = LDAPPropertyMapping - fields = [ - "pk", - "name", - "expression", + fields = PropertyMappingSerializer.Meta.fields + [ "object_field", - "verbose_name", - "verbose_name_plural", ] diff --git a/web/src/pages/property-mappings/PropertyMappingListPage.ts b/web/src/pages/property-mappings/PropertyMappingListPage.ts index 68da5e081..ff3087b32 100644 --- a/web/src/pages/property-mappings/PropertyMappingListPage.ts +++ b/web/src/pages/property-mappings/PropertyMappingListPage.ts @@ -12,6 +12,7 @@ import "../../elements/forms/ProxyForm"; import "./PropertyMappingTestForm"; import "./PropertyMappingScopeForm"; import "./PropertyMappingLDAPForm"; +import "./PropertyMappingSAMLForm"; import { TableColumn } from "../../elements/table/Table"; import { until } from "lit-html/directives/until"; import { PAGE_SIZE } from "../../constants"; @@ -79,6 +80,7 @@ export class PropertyMappingListPage extends TablePage { .typeMap=${{ "scopemapping": "ak-property-mapping-scope-form", "ldap": "ak-property-mapping-ldap-form", + "saml": "ak-property-mapping-saml-form", }}>