diff --git a/docs/release-notes/version-2.7.md b/docs/release-notes/version-2.7.md index f7460d92e..b34731403 100644 --- a/docs/release-notes/version-2.7.md +++ b/docs/release-notes/version-2.7.md @@ -9,6 +9,7 @@ ### Bug Fixes +* [#2769](https://github.com/netbox-community/netbox/issues/2769) - Improve `prefix_length` validation on available-prefixes API * [#4340](https://github.com/netbox-community/netbox/issues/4340) - Enforce unique constraints for device and virtual machine names in the API * [#4343](https://github.com/netbox-community/netbox/issues/4343) - Fix Markdown support for tables * [#4365](https://github.com/netbox-community/netbox/issues/4365) - Fix exception raised on IP address bulk add view diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index e6d9adecd..c597aaf48 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -154,6 +154,33 @@ class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer): read_only_fields = ['family'] +class PrefixLengthSerializer(serializers.Serializer): + + prefix_length = serializers.IntegerField() + + def to_internal_value(self, data): + requested_prefix = data.get('prefix_length') + if requested_prefix is None: + raise serializers.ValidationError({ + 'prefix_length': 'this field can not be missing' + }) + if not isinstance(requested_prefix, int): + raise serializers.ValidationError({ + 'prefix_length': 'this field must be int type' + }) + + prefix = self.context.get('prefix') + if prefix.family == 4 and requested_prefix > 32: + raise serializers.ValidationError({ + 'prefix_length': 'Invalid prefix length ({}) for IPv4'.format((requested_prefix)) + }) + elif prefix.family == 6 and requested_prefix > 128: + raise serializers.ValidationError({ + 'prefix_length': 'Invalid prefix length ({}) for IPv6'.format((requested_prefix)) + }) + return data + + class AvailablePrefixSerializer(serializers.Serializer): """ Representation of a prefix which does not exist in the database. diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 262ca7908..ae6880209 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -105,45 +105,25 @@ class PrefixViewSet(CustomFieldModelViewSet): if not request.user.has_perm('ipam.add_prefix'): raise PermissionDenied() - # Normalize to a list of objects - requested_prefixes = request.data if isinstance(request.data, list) else [request.data] + # Validate Requested Prefixes' length + serializer = serializers.PrefixLengthSerializer( + data=request.data if isinstance(request.data, list) else [request.data], + many=True, + context={ + 'request': request, + 'prefix': prefix, + } + ) + if not serializer.is_valid(): + return Response( + serializer.errors, + status=status.HTTP_400_BAD_REQUEST + ) + requested_prefixes = serializer.validated_data # Allocate prefixes to the requested objects based on availability within the parent for i, requested_prefix in enumerate(requested_prefixes): - # Validate requested prefix size - prefix_length = requested_prefix.get('prefix_length') - if prefix_length is None: - return Response( - { - "detail": "Item {}: prefix_length field missing".format(i) - }, - status=status.HTTP_400_BAD_REQUEST - ) - try: - prefix_length = int(prefix_length) - except ValueError: - return Response( - { - "detail": "Item {}: Invalid prefix length ({})".format(i, prefix_length), - }, - status=status.HTTP_400_BAD_REQUEST - ) - if prefix.family == 4 and prefix_length > 32: - return Response( - { - "detail": "Item {}: Invalid prefix length ({}) for IPv4".format(i, prefix_length), - }, - status=status.HTTP_400_BAD_REQUEST - ) - elif prefix.family == 6 and prefix_length > 128: - return Response( - { - "detail": "Item {}: Invalid prefix length ({}) for IPv6".format(i, prefix_length), - }, - status=status.HTTP_400_BAD_REQUEST - ) - # Find the first available prefix equal to or larger than the requested size for available_prefix in available_prefixes.iter_cidrs(): if requested_prefix['prefix_length'] >= available_prefix.prefixlen: diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index 99a7eaca4..2b8ddd649 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -611,10 +611,15 @@ class PrefixTest(APITestCase): self.assertEqual(response.data['description'], data['description']) # Try to create one more prefix - response = self.client.post(url, {'prefix_length': 30}, **self.header) + response = self.client.post(url, {'prefix_length': 30}, format='json', **self.header) self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertIn('detail', response.data) + # Try to create invalid prefix type + response = self.client.post(url, {'prefix_length': '30'}, format='json', **self.header) + self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) + self.assertIn('prefix_length', response.data[0]) + def test_create_multiple_available_prefixes(self): prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), is_pool=True)