1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Merge branch 'feature' into issue_9536

This commit is contained in:
Jeremy Stretch
2022-06-23 10:35:27 -04:00
committed by GitHub
182 changed files with 3327 additions and 1107 deletions

View File

@@ -58,9 +58,13 @@ class UserAdmin(UserAdmin_):
class TokenAdmin(admin.ModelAdmin):
form = forms.TokenAdminForm
list_display = [
'key', 'user', 'created', 'expires', 'last_used', 'write_enabled', 'description'
'key', 'user', 'created', 'expires', 'last_used', 'write_enabled', 'description', 'list_allowed_ips'
]
def list_allowed_ips(self, obj):
return obj.allowed_ips or 'Any'
list_allowed_ips.short_description = "Allowed IPs"
#
# Permissions

View File

@@ -51,7 +51,7 @@ class TokenAdminForm(forms.ModelForm):
class Meta:
fields = [
'user', 'key', 'write_enabled', 'expires', 'description'
'user', 'key', 'write_enabled', 'expires', 'description', 'allowed_ips'
]
model = Token

View File

@@ -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,10 +64,19 @@ 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(),
required=False,
allow_empty=True,
default=[]
)
class Meta:
model = Token
fields = ('id', 'url', 'display', 'user', 'created', 'expires', 'key', 'write_enabled', 'description')
fields = (
'id', 'url', 'display', 'user', 'created', 'expires', 'key', 'write_enabled', 'description',
'allowed_ips',
)
def to_internal_value(self, data):
if 'key' not in data:

View File

@@ -1,7 +1,9 @@
from django import forms
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm
from django.contrib.postgres.forms import SimpleArrayField
from django.utils.html import mark_safe
from ipam.formfields import IPNetworkFormField
from netbox.preferences import PREFERENCES
from utilities.forms import BootstrapMixin, DateTimePicker, StaticSelect
from utilities.utils import flatten_dict
@@ -99,11 +101,18 @@ class TokenForm(BootstrapMixin, forms.ModelForm):
required=False,
help_text="If no key is provided, one will be generated automatically."
)
allowed_ips = SimpleArrayField(
base_field=IPNetworkFormField(),
required=False,
label='Allowed IPs',
help_text='Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for no restrictions. '
'Ex: "10.1.1.0/24, 192.168.10.16/32, 2001:DB8:1::/64"',
)
class Meta:
model = Token
fields = [
'key', 'write_enabled', 'expires', 'description',
'key', 'write_enabled', 'expires', 'description', 'allowed_ips',
]
widgets = {
'expires': DateTimePicker(),

View File

@@ -0,0 +1,20 @@
# Generated by Django 3.2.12 on 2022-04-19 12:37
import django.contrib.postgres.fields
from django.db import migrations
import ipam.fields
class Migration(migrations.Migration):
dependencies = [
('users', '0002_standardize_id_fields'),
]
operations = [
migrations.AddField(
model_name='token',
name='allowed_ips',
field=django.contrib.postgres.fields.ArrayField(base_field=ipam.fields.IPNetworkField(), blank=True, null=True, size=None),
),
]

View File

@@ -9,13 +9,14 @@ 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
from utilities.querysets import RestrictedQuerySet
from utilities.utils import flatten_dict
from .constants import *
__all__ = (
'ObjectPermission',
'Token',
@@ -220,6 +221,14 @@ class Token(models.Model):
max_length=200,
blank=True
)
allowed_ips = ArrayField(
base_field=IPNetworkField(),
blank=True,
null=True,
verbose_name='Allowed IPs',
help_text='Allowed IPv4/IPv6 networks from where the token can be used. Leave blank for no restrictions. '
'Ex: "10.1.1.0/24, 192.168.10.16/32, 2001:DB8:1::/64"',
)
class Meta:
pass
@@ -244,6 +253,19 @@ class Token(models.Model):
return False
return True
def validate_client_ip(self, client_ip):
"""
Validate the API client IP address against the source IP restrictions (if any) set on the token.
"""
if not self.allowed_ips:
return True
for ip_network in self.allowed_ips:
if client_ip in IPNetwork(ip_network):
return True
return False
#
# Permissions