*: add validator to ensure JSON Fields only receive dicts

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-04-11 23:05:19 +02:00
parent a0daaabfde
commit 4f27a97e10
8 changed files with 47 additions and 8 deletions

View file

@ -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

View file

@ -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:

View file

@ -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):

View file

@ -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")

View file

@ -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:

View file

@ -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):

View file

@ -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

View file

@ -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)