From 4f27a97e105c784e758da46c1d96ab8381b385db Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sun, 11 Apr 2021 23:05:19 +0200 Subject: [PATCH] *: add validator to ensure JSON Fields only receive dicts Signed-off-by: Jens Langhammer --- authentik/core/api/groups.py | 4 ++++ authentik/core/api/users.py | 5 +++-- authentik/core/api/utils.py | 15 ++++++++++++++- authentik/core/tests/test_api_utils.py | 15 +++++++++++++++ authentik/outposts/api/outposts.py | 4 ++-- authentik/policies/api/exec.py | 4 ++-- authentik/stages/invitation/api.py | 4 ++++ authentik/stages/invitation/tests.py | 4 +++- 8 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 authentik/core/tests/test_api_utils.py diff --git a/authentik/core/api/groups.py b/authentik/core/api/groups.py index 2ca259fb4..e3d725159 100644 --- a/authentik/core/api/groups.py +++ b/authentik/core/api/groups.py @@ -1,13 +1,17 @@ """Groups API Viewset""" +from rest_framework.fields import JSONField from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet +from authentik.core.api.utils import is_dict from authentik.core.models import Group class GroupSerializer(ModelSerializer): """Group Serializer""" + attributes = JSONField(validators=[is_dict]) + class Meta: model = Group diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index 550645872..a5b572b04 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -4,7 +4,7 @@ from django.utils.http import urlencode from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method from guardian.utils import get_anonymous_user from rest_framework.decorators import action -from rest_framework.fields import CharField, SerializerMethodField +from rest_framework.fields import CharField, JSONField, SerializerMethodField from rest_framework.request import Request from rest_framework.response import Response from rest_framework.serializers import BooleanField, ModelSerializer @@ -12,7 +12,7 @@ from rest_framework.viewsets import ModelViewSet from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h from authentik.api.decorators import permission_required -from authentik.core.api.utils import LinkSerializer, PassiveSerializer +from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict from authentik.core.middleware import ( SESSION_IMPERSONATE_ORIGINAL_USER, SESSION_IMPERSONATE_USER, @@ -26,6 +26,7 @@ class UserSerializer(ModelSerializer): is_superuser = BooleanField(read_only=True) avatar = CharField(read_only=True) + attributes = JSONField(validators=[is_dict]) class Meta: diff --git a/authentik/core/api/utils.py b/authentik/core/api/utils.py index 70155b06e..1a2586399 100644 --- a/authentik/core/api/utils.py +++ b/authentik/core/api/utils.py @@ -1,7 +1,20 @@ """API Utilities""" +from typing import Any + from django.db.models import Model from rest_framework.fields import CharField, IntegerField -from rest_framework.serializers import Serializer, SerializerMethodField +from rest_framework.serializers import ( + Serializer, + SerializerMethodField, + ValidationError, +) + + +def is_dict(value: Any): + """Ensure a value is a dictionary, useful for JSONFields""" + if isinstance(value, dict): + return + raise ValidationError("Value must be a dictionary.") class PassiveSerializer(Serializer): diff --git a/authentik/core/tests/test_api_utils.py b/authentik/core/tests/test_api_utils.py new file mode 100644 index 000000000..00736de28 --- /dev/null +++ b/authentik/core/tests/test_api_utils.py @@ -0,0 +1,15 @@ +"""Test API Utils""" +from rest_framework.exceptions import ValidationError +from rest_framework.test import APITestCase + +from authentik.core.api.utils import is_dict + + +class TestAPIUtils(APITestCase): + """Test API Utils""" + + def test_is_dict(self): + """Test is_dict""" + self.assertIsNone(is_dict({})) + with self.assertRaises(ValidationError): + is_dict("foo") diff --git a/authentik/outposts/api/outposts.py b/authentik/outposts/api/outposts.py index d1589b7ac..dbe2595af 100644 --- a/authentik/outposts/api/outposts.py +++ b/authentik/outposts/api/outposts.py @@ -8,14 +8,14 @@ from rest_framework.serializers import JSONField, ModelSerializer from rest_framework.viewsets import ModelViewSet from authentik.core.api.providers import ProviderSerializer -from authentik.core.api.utils import PassiveSerializer +from authentik.core.api.utils import PassiveSerializer, is_dict from authentik.outposts.models import Outpost, default_outpost_config class OutpostSerializer(ModelSerializer): """Outpost Serializer""" - _config = JSONField() + _config = JSONField(validators=[is_dict]) providers_obj = ProviderSerializer(source="providers", many=True, read_only=True) class Meta: diff --git a/authentik/policies/api/exec.py b/authentik/policies/api/exec.py index bb987080d..7518b12c3 100644 --- a/authentik/policies/api/exec.py +++ b/authentik/policies/api/exec.py @@ -2,7 +2,7 @@ from rest_framework.fields import BooleanField, CharField, JSONField, ListField from rest_framework.relations import PrimaryKeyRelatedField -from authentik.core.api.utils import PassiveSerializer +from authentik.core.api.utils import PassiveSerializer, is_dict from authentik.core.models import User @@ -10,7 +10,7 @@ class PolicyTestSerializer(PassiveSerializer): """Test policy execution for a user with context""" user = PrimaryKeyRelatedField(queryset=User.objects.all()) - context = JSONField(required=False) + context = JSONField(required=False, validators=[is_dict]) class PolicyTestResultSerializer(PassiveSerializer): diff --git a/authentik/stages/invitation/api.py b/authentik/stages/invitation/api.py index 1783bd76e..f55912de9 100644 --- a/authentik/stages/invitation/api.py +++ b/authentik/stages/invitation/api.py @@ -1,7 +1,9 @@ """Invitation Stage API Views""" +from rest_framework.fields import JSONField from rest_framework.serializers import ModelSerializer from rest_framework.viewsets import ModelViewSet +from authentik.core.api.utils import is_dict from authentik.flows.api.stages import StageSerializer from authentik.stages.invitation.models import Invitation, InvitationStage @@ -27,6 +29,8 @@ class InvitationStageViewSet(ModelViewSet): class InvitationSerializer(ModelSerializer): """Invitation Serializer""" + fixed_data = JSONField(validators=[is_dict]) + class Meta: model = Invitation diff --git a/authentik/stages/invitation/tests.py b/authentik/stages/invitation/tests.py index 121f76269..8d7ea5de8 100644 --- a/authentik/stages/invitation/tests.py +++ b/authentik/stages/invitation/tests.py @@ -142,7 +142,9 @@ class TestInvitationsAPI(APITestCase): def test_invite_create(self): """Test Invitations creation endpoint""" response = self.client.post( - reverse("authentik_api:invitation-list"), {"identifier": "test-token"} + reverse("authentik_api:invitation-list"), + {"identifier": "test-token", "fixed_data": {}}, + format="json" ) self.assertEqual(response.status_code, 201) self.assertEqual(Invitation.objects.first().created_by, self.user)