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:
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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(),
|
||||
|
20
netbox/users/migrations/0003_token_allowed_ips.py
Normal file
20
netbox/users/migrations/0003_token_allowed_ips.py
Normal 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),
|
||||
),
|
||||
]
|
@@ -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
|
||||
|
Reference in New Issue
Block a user