diff --git a/authentik/core/api/propertymappings.py b/authentik/core/api/propertymappings.py index 17a062a46..b3970f832 100644 --- a/authentik/core/api/propertymappings.py +++ b/authentik/core/api/propertymappings.py @@ -1,17 +1,34 @@ """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 from rest_framework.decorators import action +from rest_framework.exceptions import PermissionDenied +from rest_framework.fields import CharField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.viewsets import GenericViewSet -from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer +from authentik.api.decorators import permission_required +from authentik.core.api.utils import ( + MetaNameSerializer, + PassiveSerializer, + TypeCreateSerializer, +) from authentik.core.models import PropertyMapping from authentik.lib.templatetags.authentik_utils import verbose_name from authentik.lib.utils.reflection import all_subclasses +from authentik.policies.api.exec import PolicyTestSerializer + + +class PropertyMappingTestResultSerializer(PassiveSerializer): + """Result of a Property-mapping test""" + + result = CharField(read_only=True) class PropertyMappingSerializer(ModelSerializer, MetaNameSerializer): @@ -76,3 +93,37 @@ class PropertyMappingViewSet( } ) return Response(TypeCreateSerializer(data, many=True).data) + + @permission_required("authentik_core.view_propertymapping") + @swagger_auto_schema( + request_body=PolicyTestSerializer(), + responses={200: PropertyMappingTestResultSerializer}, + ) + @action(detail=True, methods=["POST"]) + # pylint: disable=unused-argument, invalid-name + def test(self, request: Request, pk: str) -> Response: + """Test Property Mapping""" + mapping: PropertyMapping = self.get_object() + test_params = PolicyTestSerializer(data=request.data) + if not test_params.is_valid(): + return Response(test_params.errors, status=400) + + # User permission check, only allow mapping testing for users that are readable + users = get_objects_for_user(request.user, "authentik_core.view_user").filter( + pk=test_params.validated_data["user"].pk + ) + if not users.exists(): + raise PermissionDenied() + + response_data = {} + try: + result = mapping.evaluate( + users.first(), + self.request, + **test_params.validated_data.get("context", {}), + ) + response_data["result"] = dumps(result) + except Exception as exc: # pylint: disable=broad-except + response_data["result"] = str(exc) + response = PropertyMappingTestResultSerializer(response_data) + return Response(response.data) diff --git a/authentik/core/tests/test_api.py b/authentik/core/tests/test_api.py new file mode 100644 index 000000000..88ea051bd --- /dev/null +++ b/authentik/core/tests/test_api.py @@ -0,0 +1,33 @@ +"""Test property mappings API""" +from json import dumps + +from django.urls import reverse +from rest_framework.test import APITestCase + +from authentik.core.models import PropertyMapping, User + + +class TestPropertyMappingAPI(APITestCase): + """Test property mappings API""" + + def setUp(self) -> None: + super().setUp() + self.mapping = PropertyMapping.objects.create( + name="dummy", expression="""return {'foo': 'bar'}""" + ) + self.user = User.objects.get(username="akadmin") + self.client.force_login(self.user) + + def test_test_call(self): + """Test Policy's test endpoint""" + response = self.client.post( + reverse( + "authentik_api:propertymapping-test", kwargs={"pk": self.mapping.pk} + ), + data={ + "user": self.user.pk, + }, + ) + self.assertJSONEqual( + response.content.decode(), {"result": dumps({"foo": "bar"})} + ) diff --git a/swagger.yaml b/swagger.yaml index 9dbabb7db..b7a05f9e3 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -7984,6 +7984,43 @@ paths: required: true type: string format: uuid + /propertymappings/all/{pm_uuid}/test/: + post: + operationId: propertymappings_all_test + description: Test Property Mapping + parameters: + - name: data + in: body + required: true + schema: + $ref: '#/definitions/PolicyTest' + responses: + '200': + description: '' + schema: + $ref: '#/definitions/PropertyMappingTestResult' + '400': + description: Invalid input. + schema: + $ref: '#/definitions/ValidationError' + '403': + description: Authentication credentials were invalid, absent or insufficient. + schema: + $ref: '#/definitions/GenericError' + '404': + description: Object does not exist or caller has insufficient permissions + to access it. + schema: + $ref: '#/definitions/APIException' + tags: + - propertymappings + parameters: + - name: pm_uuid + in: path + description: A UUID string identifying this Property Mapping. + required: true + type: string + format: uuid /propertymappings/ldap/: get: operationId: propertymappings_ldap_list @@ -16843,6 +16880,14 @@ definitions: title: Verbose name plural type: string readOnly: true + PropertyMappingTestResult: + type: object + properties: + result: + title: Result + type: string + readOnly: true + minLength: 1 LDAPPropertyMapping: required: - name