From e87e11377aba35155ca36197982723b910ca47b0 Mon Sep 17 00:00:00 2001 From: "julio.oliveira" Date: Mon, 15 Jan 2024 16:28:12 -0300 Subject: [PATCH] Fixes #14755: ValueError in web UI after REST API accepts invalid custom-field choice-set data --- netbox/extras/api/serializers.py | 12 +++- netbox/extras/tests/test_api.py | 10 ---- .../test_custom_field_choice_sets_endpoint.py | 59 +++++++++++++++++++ 3 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 netbox/extras/tests/test_custom_field_choice_sets_endpoint.py diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 60a30aed2..50cd022b5 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -179,6 +179,15 @@ class CustomFieldChoiceSetSerializer(ValidatedModelSerializer): 'choices_count', 'created', 'last_updated', ] + def validate_extra_choices(self, value): + for choice in value: + if isinstance(choice, list): + if len(choice) < 2: + raise serializers.ValidationError('Each choice must have 2 elements.') + else: + raise serializers.ValidationError('Extra choice must be a list of two elements.') + return value + # # Custom links @@ -374,7 +383,8 @@ class JournalEntrySerializer(NetBoxModelSerializer): @extend_schema_field(serializers.JSONField(allow_null=True)) def get_assigned_object(self, instance): - serializer = get_serializer_for_model(instance.assigned_object_type.model_class(), prefix=NESTED_SERIALIZER_PREFIX) + serializer = get_serializer_for_model(instance.assigned_object_type.model_class(), + prefix=NESTED_SERIALIZER_PREFIX) context = {'request': self.context['request']} return serializer(instance.assigned_object, context=context).data diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index 93be2d2c4..fd9f5bafa 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -14,14 +14,12 @@ from extras.reports import Report from extras.scripts import BooleanVar, IntegerVar, Script, StringVar from utilities.testing import APITestCase, APIViewTestCases - User = get_user_model() class AppTest(APITestCase): def test_root(self): - url = reverse('extras-api:api-root') response = self.client.get('{}?format=api'.format(url), **self.header) @@ -52,7 +50,6 @@ class WebhookTest(APIViewTestCases.APIViewTestCase): @classmethod def setUpTestData(cls): - webhooks = ( Webhook( name='Webhook 1', @@ -505,7 +502,6 @@ class TagTest(APIViewTestCases.APIViewTestCase): @classmethod def setUpTestData(cls): - tags = ( Tag(name='Tag 1', slug='tag-1'), Tag(name='Tag 2', slug='tag-2'), @@ -632,7 +628,6 @@ class ConfigContextTest(APIViewTestCases.APIViewTestCase): @classmethod def setUpTestData(cls): - config_contexts = ( ConfigContext(name='Config Context 1', weight=100, data={'foo': 123}), ConfigContext(name='Config Context 2', weight=200, data={'bar': 456}), @@ -731,7 +726,6 @@ class ConfigTemplateTest(APIViewTestCases.APIViewTestCase): class ReportTest(APITestCase): - class TestReport(Report): def test_foo(self): @@ -762,9 +756,7 @@ class ReportTest(APITestCase): class ScriptTest(APITestCase): - class TestScript(Script): - class Meta: name = "Test script" @@ -773,7 +765,6 @@ class ScriptTest(APITestCase): var3 = BooleanVar() def run(self, data, commit=True): - self.log_info(data['var1']) self.log_success(data['var2']) self.log_failure(data['var3']) @@ -798,7 +789,6 @@ class ScriptTest(APITestCase): ScriptViewSet._get_script = self.get_test_script def test_get_script(self): - url = reverse('extras-api:script-detail', kwargs={'pk': None}) response = self.client.get(url, **self.header) diff --git a/netbox/extras/tests/test_custom_field_choice_sets_endpoint.py b/netbox/extras/tests/test_custom_field_choice_sets_endpoint.py new file mode 100644 index 000000000..5c9cb53b6 --- /dev/null +++ b/netbox/extras/tests/test_custom_field_choice_sets_endpoint.py @@ -0,0 +1,59 @@ +from django.contrib.auth import get_user_model +from rest_framework.test import APITestCase + +from users.models import Token + +User = get_user_model() + + +class CustomFieldChoiceSetsEndpointTest(APITestCase): + + def setUp(self): + self.super_user = User.objects.create_user(username='testuser', is_staff=True, is_superuser=True) + self.token = Token.objects.create(user=self.super_user) + self.header = {'HTTP_AUTHORIZATION': f'Token {self.token.key}'} + self.url = '/api/extras/custom-field-choice-sets/' + + def test_extra_choices_only_one_choice_element_return_400(self): + payload = { + "name": "test", + "extra_choices": [["choice1"]] + } + + response = self.client.post(self.url, payload, format='json', **self.header) + + self.assertEqual(response.status_code, 400) + + def test_extra_choices_two_wrong_choice_elements_return_400(self): + payload = { + "name": "test", + "extra_choices": [["choice1"], ["choice2"]] + } + + response = self.client.post(self.url, payload, format='json', **self.header) + + self.assertEqual(response.status_code, 400) + + def test_extra_choices_one_is_wrong_other_correct_choice_elements_return_400(self): + payload = { + "name": "test", + "extra_choices": [["1A", "choice1"], ["choice2"]] + } + + response = self.client.post(self.url, payload, format='json', **self.header) + + self.assertEqual(response.status_code, 400) + + def test_extra_choices_correct_choices_return_201(self): + payload = { + 'name': 'Choice Set', + 'extra_choices': [ + ['4A', 'Choice 1'], + ['4B', 'Choice 2'], + ['4C', 'Choice 3'], + ], + } + + response = self.client.post(self.url, payload, format='json', **self.header) + + self.assertEqual(response.status_code, 201)