From 92bdaa2120b2cbb4944d7e679aa516e14b214274 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 7 Dec 2023 20:15:30 +0530 Subject: [PATCH 1/8] Fixes IPv6 detection from headers (#14456) * fixes client ip detection for v6 * adds test for get_client_ip * Employ urlparse() to strip port numbers from IPs --------- Co-authored-by: Jeremy Stretch --- netbox/utilities/request.py | 18 ++++++++++++----- netbox/utilities/tests/test_request.py | 28 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 netbox/utilities/tests/test_request.py diff --git a/netbox/utilities/request.py b/netbox/utilities/request.py index 0f8ee9cae..a5ca145e9 100644 --- a/netbox/utilities/request.py +++ b/netbox/utilities/request.py @@ -1,4 +1,5 @@ -from netaddr import IPAddress +from netaddr import AddrFormatError, IPAddress +from urllib.parse import urlparse __all__ = ( 'get_client_ip', @@ -17,11 +18,18 @@ def get_client_ip(request, additional_headers=()): ) for header in HTTP_HEADERS: if header in request.META: - client_ip = request.META[header].split(',')[0].partition(':')[0] + ip = request.META[header].split(',')[0].strip() try: - return IPAddress(client_ip) - except ValueError: - raise ValueError(f"Invalid IP address set for {header}: {client_ip}") + return IPAddress(ip) + except AddrFormatError: + # Parse the string with urlparse() to remove port number or any other cruft + ip = urlparse(f'//{ip}').hostname + + try: + return IPAddress(ip) + except AddrFormatError: + # We did our best + raise ValueError(f"Invalid IP address set for {header}: {ip}") # Could not determine the client IP address from request headers return None diff --git a/netbox/utilities/tests/test_request.py b/netbox/utilities/tests/test_request.py new file mode 100644 index 000000000..69f677323 --- /dev/null +++ b/netbox/utilities/tests/test_request.py @@ -0,0 +1,28 @@ +from django.test import TestCase, RequestFactory + +from netaddr import IPAddress +from utilities.request import get_client_ip + + +class GetClientIPTests(TestCase): + def setUp(self): + self.factory = RequestFactory() + + def test_ipv4_address(self): + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='192.168.1.1') + self.assertEqual(get_client_ip(request), IPAddress('192.168.1.1')) + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='192.168.1.1:8080') + self.assertEqual(get_client_ip(request), IPAddress('192.168.1.1')) + + def test_ipv6_address(self): + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='2001:db8::8a2e:370:7334') + self.assertEqual(get_client_ip(request), IPAddress('2001:db8::8a2e:370:7334')) + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='[2001:db8::8a2e:370:7334]') + self.assertEqual(get_client_ip(request), IPAddress('2001:db8::8a2e:370:7334')) + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='[2001:db8::8a2e:370:7334]:8080') + self.assertEqual(get_client_ip(request), IPAddress('2001:db8::8a2e:370:7334')) + + def test_invalid_ip_address(self): + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='invalid_ip') + with self.assertRaises(ValueError): + get_client_ip(request) From e59ee3e01e29408609a4b91486fccc65456f7c7f Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 5 Dec 2023 10:29:17 -0500 Subject: [PATCH 2/8] Fixes #14397: Pass a mutable copy of request data when provisioning available IPs --- netbox/ipam/api/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 662b393de..8e815817f 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -1,3 +1,5 @@ +from copy import deepcopy + from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.db import transaction from django.shortcuts import get_object_or_404 @@ -290,7 +292,7 @@ class AvailableObjectsView(ObjectValidationMixin, APIView): ) # Prepare object data for deserialization - requested_objects = self.prep_object_data(requested_objects, available_objects, parent) + requested_objects = self.prep_object_data(deepcopy(requested_objects), available_objects, parent) # Initialize the serializer with a list or a single object depending on what was requested serializer_class = get_serializer_for_model(self.queryset.model) From 95a8415e2d027a11c7067e0f9235bedf27349855 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 7 Dec 2023 16:21:15 -0500 Subject: [PATCH 3/8] Add deployment type to bug report template --- .github/ISSUE_TEMPLATE/bug_report.yaml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 5e936c5ec..dcc3f0a97 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -10,16 +10,25 @@ body: installation. If you're having trouble with installation or just looking for assistance with using NetBox, please visit our [discussion forum](https://github.com/netbox-community/netbox/discussions) instead. + - type: dropdown + attributes: + label: Deployment Type + description: How are you running NetBox? + options: + - Self-hosted + - NetBox Cloud + validations: + required: true - type: input attributes: - label: NetBox version + label: NetBox Version description: What version of NetBox are you currently running? placeholder: v3.6.6 validations: required: true - type: dropdown attributes: - label: Python version + label: Python Version description: What version of Python are you currently running? options: - "3.8" From 9d7192202d793b7eaf8d67b8100ecc2ad5494bd2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 5 Dec 2023 15:09:59 -0500 Subject: [PATCH 4/8] Fixes #14392: Fix admin UI bulk actions --- netbox/templates/django/forms/widgets/checkbox.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/netbox/templates/django/forms/widgets/checkbox.html b/netbox/templates/django/forms/widgets/checkbox.html index bbe201a29..359657136 100644 --- a/netbox/templates/django/forms/widgets/checkbox.html +++ b/netbox/templates/django/forms/widgets/checkbox.html @@ -1,6 +1,7 @@ {% comment %} Include a hidden field of the same name to ensure that unchecked checkboxes - are always included in the submitted form data. + are always included in the submitted form data. Omit fields names + _selected_action to avoid breaking the admin UI. {% endcomment %} - +{% if widget.name != '_selected_action' %}{% endif %} {% include "django/forms/widgets/input.html" %} From 2ef023a16045b848f9c1cb3dbb9c2ee180589dac Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 7 Dec 2023 16:34:49 -0500 Subject: [PATCH 5/8] Changelog for #14249, #14390, #14392, #14397, #14401, #14432, #14448 --- docs/release-notes/version-3.6.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 10e93be1e..a4234f8dd 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -2,6 +2,19 @@ ## v3.6.7 (FUTURE) +### Enhancements + +* [#14390](https://github.com/netbox-community/netbox/issues/14390) - Add `classes` parameter to `copy_content` template tag + +### Bug Fixes + +* [#14249](https://github.com/netbox-community/netbox/issues/14249) - Fix server error when authenticating via IP-restricted API tokens using IPv6 +* [#14392](https://github.com/netbox-community/netbox/issues/14392) - Fix bulk operations for plugin models under admin UI +* [#14397](https://github.com/netbox-community/netbox/issues/14397) - Fix exception on non-JSON request to `/available-ips/` API endpoints +* [#14401](https://github.com/netbox-community/netbox/issues/14401) - Rack `starting_unit` cannot be zero +* [#14432](https://github.com/netbox-community/netbox/issues/14432) - Populate custom field default values for components when creating a device +* [#14448](https://github.com/netbox-community/netbox/issues/14448) - Fix exception when creating a power feed with rack and panel in different sites + --- ## v3.6.6 (2023-11-29) From 35be4f05ef376e28d9af4d7245ba10cc286bb62a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 11 Dec 2023 10:10:28 -0500 Subject: [PATCH 6/8] Add note to bug reports section --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 301fac079..471846427 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,8 @@ NetBox users are welcome to participate in either role, on stage or in the crowd ## :bug: Reporting Bugs +:warning: Bug reports are used to call attention to some unintended or unexpected behavior in NetBox, such as when an error occurs or when the result of taking some action is inconsistent with the documentation. **Bug reports may not be used to suggest new functionality**; please see "feature requests" below if that is your goal. + * First, ensure that you're running the [latest stable version](https://github.com/netbox-community/netbox/releases) of NetBox. If you're running an older version, it's likely that the bug has already been fixed. * Next, search our [issues list](https://github.com/netbox-community/netbox/issues?q=is%3Aissue) to see if the bug you've found has already been reported. If you come across a bug report that seems to match, please click "add a reaction" in the top right corner of the issue and add a thumbs up (:thumbsup:). This will help draw more attention to it. Any comments you can add to provide additional information or context would also be much appreciated. From 81fa4265da51268e0f2652ac36a76a724ebdc4ab Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 11 Dec 2023 16:01:33 +0530 Subject: [PATCH 7/8] add tags field in L2VPN Termination --- netbox/ipam/forms/model_forms.py | 2 +- netbox/ipam/tables/l2vpn.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index bfd4f952d..41b31dc76 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -818,7 +818,7 @@ class L2VPNTerminationForm(NetBoxModelForm): class Meta: model = L2VPNTermination - fields = ('l2vpn', ) + fields = ('l2vpn', 'tags') def __init__(self, *args, **kwargs): instance = kwargs.get('instance') diff --git a/netbox/ipam/tables/l2vpn.py b/netbox/ipam/tables/l2vpn.py index 8635ab62a..6678d184c 100644 --- a/netbox/ipam/tables/l2vpn.py +++ b/netbox/ipam/tables/l2vpn.py @@ -73,12 +73,15 @@ class L2VPNTerminationTable(NetBoxTable): orderable=False, verbose_name=_('Object Site') ) + tags = columns.TagColumn( + url_name='ipam:l2vpntermination_list' + ) class Meta(NetBoxTable.Meta): model = L2VPNTermination fields = ( 'pk', 'l2vpn', 'assigned_object_type', 'assigned_object', 'assigned_object_parent', 'assigned_object_site', - 'actions', + 'tags', 'actions', ) default_columns = ( 'pk', 'l2vpn', 'assigned_object_type', 'assigned_object_parent', 'assigned_object', 'actions', From 6939ae4a47192d3d6e87061cc741a9b51f7ea215 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Tue, 12 Dec 2023 11:31:39 -0800 Subject: [PATCH 8/8] 14467 change ChoiceField separator from comma to colon (#14469) * 14467 change ChoiceField separator from comma to colon * 14467 fix test * 14467 fix test * 14467 use regex for colon detection * 14467 update tests --- netbox/extras/forms/model_forms.py | 7 ++++--- netbox/extras/tests/test_views.py | 9 ++++++++- netbox/utilities/forms/widgets/misc.py | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index 83a346420..4e4a6e0de 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -1,4 +1,5 @@ import json +import re from django import forms from django.conf import settings @@ -95,8 +96,8 @@ class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm): required=False, help_text=mark_safe(_( 'Enter one choice per line. An optional label may be specified for each choice by appending it with a ' - 'comma. Example:' - ) + ' choice1,First Choice') + 'colon. Example:' + ) + ' choice1:First Choice') ) class Meta: @@ -107,7 +108,7 @@ class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm): data = [] for line in self.cleaned_data['extra_choices'].splitlines(): try: - value, label = line.split(',', maxsplit=1) + value, label = re.split(r'(?