Просмотр исходного кода

Introduce IPNetworkSerializer to serialize allowed token IPs

jeremystretch 3 лет назад
Родитель
Сommit
3c15419bd0

+ 2 - 1
docs/release-notes/version-3.3.md

@@ -13,6 +13,8 @@
 
 #### PoE Interface Attributes ([#1099](https://github.com/netbox-community/netbox/issues/1099))
 
+#### Restrict API Tokens by Client IP ([#8233](https://github.com/netbox-community/netbox/issues/8233))
+
 ### Enhancements
 
 * [#1202](https://github.com/netbox-community/netbox/issues/1202) - Support overlapping assignment of NAT IP addresses
@@ -21,7 +23,6 @@
 * [#7120](https://github.com/netbox-community/netbox/issues/7120) - Add `termination_date` field to Circuit
 * [#7744](https://github.com/netbox-community/netbox/issues/7744) - Add `status` field to Location
 * [#8222](https://github.com/netbox-community/netbox/issues/8222) - Enable the assignment of a VM to a specific host device within a cluster
-* [#8233](https://github.com/netbox-community/netbox/issues/8233) - Restrict API token access by source IP
 * [#8471](https://github.com/netbox-community/netbox/issues/8471) - Add `status` field to Cluster
 * [#8495](https://github.com/netbox-community/netbox/issues/8495) - Enable custom field grouping
 * [#8995](https://github.com/netbox-community/netbox/issues/8995) - Enable arbitrary ordering of REST API results

+ 2 - 1
netbox/netbox/api/__init__.py

@@ -1,4 +1,4 @@
-from .fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
+from .fields import *
 from .routers import NetBoxRouter
 from .serializers import BulkOperationSerializer, ValidatedModelSerializer, WritableNestedSerializer
 
@@ -7,6 +7,7 @@ __all__ = (
     'BulkOperationSerializer',
     'ChoiceField',
     'ContentTypeField',
+    'IPNetworkSerializer',
     'NetBoxRouter',
     'SerializedPKRelatedField',
     'ValidatedModelSerializer',

+ 19 - 2
netbox/netbox/api/fields.py

@@ -1,12 +1,18 @@
 from collections import OrderedDict
 
-import pytz
-from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ObjectDoesNotExist
+from netaddr import IPNetwork
 from rest_framework import serializers
 from rest_framework.exceptions import ValidationError
 from rest_framework.relations import PrimaryKeyRelatedField, RelatedField
 
+__all__ = (
+    'ChoiceField',
+    'ContentTypeField',
+    'IPNetworkSerializer',
+    'SerializedPKRelatedField',
+)
+
 
 class ChoiceField(serializers.Field):
     """
@@ -104,6 +110,17 @@ class ContentTypeField(RelatedField):
         return f"{obj.app_label}.{obj.model}"
 
 
+class IPNetworkSerializer(serializers.Serializer):
+    """
+    Representation of an IP network value (e.g. 192.0.2.0/24).
+    """
+    def to_representation(self, instance):
+        return str(instance)
+
+    def to_internal_value(self, value):
+        return IPNetwork(value)
+
+
 class SerializedPKRelatedField(PrimaryKeyRelatedField):
     """
     Extends PrimaryKeyRelatedField to return a serialized object on read. This is useful for representing related

+ 2 - 1
netbox/users/api/serializers.py

@@ -2,7 +2,7 @@ from django.contrib.auth.models import Group, User
 from django.contrib.contenttypes.models import ContentType
 from rest_framework import serializers
 
-from netbox.api import ContentTypeField, SerializedPKRelatedField, ValidatedModelSerializer
+from netbox.api import ContentTypeField, IPNetworkSerializer, SerializedPKRelatedField, ValidatedModelSerializer
 from users.models import ObjectPermission, Token
 from .nested_serializers import *
 
@@ -64,6 +64,7 @@ class TokenSerializer(ValidatedModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='users-api:token-detail')
     key = serializers.CharField(min_length=40, max_length=40, allow_blank=True, required=False)
     user = NestedUserSerializer()
+    allowed_ips = serializers.ListField(child=IPNetworkSerializer())
 
     class Meta:
         model = Token

+ 2 - 4
netbox/users/models.py

@@ -4,12 +4,12 @@ import os
 from django.contrib.auth.models import Group, User
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.fields import ArrayField
-from django.core.exceptions import ValidationError
 from django.core.validators import MinLengthValidator
 from django.db import models
 from django.db.models.signals import post_save
 from django.dispatch import receiver
 from django.utils import timezone
+from netaddr import IPNetwork
 
 from ipam.fields import IPNetworkField
 from netbox.config import get_config
@@ -17,8 +17,6 @@ from utilities.querysets import RestrictedQuerySet
 from utilities.utils import flatten_dict
 from .constants import *
 
-import ipaddress
-
 __all__ = (
     'ObjectPermission',
     'Token',
@@ -259,7 +257,7 @@ class Token(models.Model):
             return True
 
         for ip_network in self.allowed_ips:
-            if client_ip in ipaddress.ip_network(ip_network):
+            if client_ip in IPNetwork(ip_network):
                 return True
 
         return False

+ 2 - 2
netbox/utilities/request.py

@@ -1,4 +1,4 @@
-import ipaddress
+from netaddr import IPAddress
 
 __all__ = (
     'get_client_ip',
@@ -19,7 +19,7 @@ def get_client_ip(request, additional_headers=()):
         if header in request.META:
             client_ip = request.META[header].split(',')[0]
             try:
-                return ipaddress.ip_address(client_ip)
+                return IPAddress(client_ip)
             except ValueError:
                 raise ValueError(f"Invalid IP address set for {header}: {client_ip}")