Fixes #2769: improve prefix_length validations

This commit is contained in:
kobayashi 2020-03-16 03:20:15 -04:00
parent 9466802a95
commit 7ef9a6c0a7
4 changed files with 49 additions and 36 deletions

View File

@ -9,6 +9,7 @@
### Bug Fixes ### 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 * [#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 * [#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 * [#4365](https://github.com/netbox-community/netbox/issues/4365) - Fix exception raised on IP address bulk add view

View File

@ -154,6 +154,33 @@ class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer):
read_only_fields = ['family'] 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): class AvailablePrefixSerializer(serializers.Serializer):
""" """
Representation of a prefix which does not exist in the database. Representation of a prefix which does not exist in the database.

View File

@ -105,45 +105,25 @@ class PrefixViewSet(CustomFieldModelViewSet):
if not request.user.has_perm('ipam.add_prefix'): if not request.user.has_perm('ipam.add_prefix'):
raise PermissionDenied() raise PermissionDenied()
# Normalize to a list of objects # Validate Requested Prefixes' length
requested_prefixes = request.data if isinstance(request.data, list) else [request.data] 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 # Allocate prefixes to the requested objects based on availability within the parent
for i, requested_prefix in enumerate(requested_prefixes): 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 # Find the first available prefix equal to or larger than the requested size
for available_prefix in available_prefixes.iter_cidrs(): for available_prefix in available_prefixes.iter_cidrs():
if requested_prefix['prefix_length'] >= available_prefix.prefixlen: if requested_prefix['prefix_length'] >= available_prefix.prefixlen:

View File

@ -611,10 +611,15 @@ class PrefixTest(APITestCase):
self.assertEqual(response.data['description'], data['description']) self.assertEqual(response.data['description'], data['description'])
# Try to create one more prefix # 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.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertIn('detail', response.data) 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): def test_create_multiple_available_prefixes(self):
prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), is_pool=True) prefix = Prefix.objects.create(prefix=IPNetwork('192.0.2.0/28'), is_pool=True)