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

Merge branch 'develop' into feature

This commit is contained in:
jeremystretch
2023-04-12 17:38:16 -04:00
25 changed files with 215 additions and 55 deletions

View File

@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v3.4.7 placeholder: v3.4.8
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -14,7 +14,7 @@ body:
attributes: attributes:
label: NetBox version label: NetBox version
description: What version of NetBox are you currently running? description: What version of NetBox are you currently running?
placeholder: v3.4.7 placeholder: v3.4.8
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -1,5 +1,5 @@
# HTML sanitizer # HTML sanitizer
# https://github.com/mozilla/bleach # https://github.com/mozilla/bleach/blob/main/CHANGES
bleach<6.0 bleach<6.0
# Python client for Amazon AWS API # Python client for Amazon AWS API
@ -7,55 +7,55 @@ bleach<6.0
boto3 boto3
# The Python web framework on which NetBox is built # The Python web framework on which NetBox is built
# https://github.com/django/django # https://docs.djangoproject.com/en/stable/releases/
Django<4.2 Django<4.2
# Django middleware which permits cross-domain API requests # Django middleware which permits cross-domain API requests
# https://github.com/OttoYiu/django-cors-headers # https://github.com/adamchainz/django-cors-headers/blob/main/CHANGELOG.rst
django-cors-headers django-cors-headers
# Runtime UI tool for debugging Django # Runtime UI tool for debugging Django
# https://github.com/jazzband/django-debug-toolbar # https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst
django-debug-toolbar django-debug-toolbar
# Library for writing reusable URL query filters # Library for writing reusable URL query filters
# https://github.com/carltongibson/django-filter # https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst
django-filter django-filter
# Django debug toolbar extension with support for GraphiQL # Django debug toolbar extension with support for GraphiQL
# https://github.com/flavors/django-graphiql-debug-toolbar/ # https://github.com/flavors/django-graphiql-debug-toolbar/blob/main/CHANGES.rst
django-graphiql-debug-toolbar django-graphiql-debug-toolbar
# Modified Preorder Tree Traversal (recursive nesting of objects) # Modified Preorder Tree Traversal (recursive nesting of objects)
# https://github.com/django-mptt/django-mptt # https://github.com/django-mptt/django-mptt/blob/main/CHANGELOG.rst
django-mptt django-mptt
# Context managers for PostgreSQL advisory locks # Context managers for PostgreSQL advisory locks
# https://github.com/Xof/django-pglocks # https://github.com/Xof/django-pglocks/blob/master/CHANGES.txt
django-pglocks django-pglocks
# Prometheus metrics library for Django # Prometheus metrics library for Django
# https://github.com/korfuri/django-prometheus # https://github.com/korfuri/django-prometheus/blob/master/CHANGELOG.md
django-prometheus django-prometheus
# Django caching backend using Redis # Django caching backend using Redis
# https://github.com/jazzband/django-redis # https://github.com/jazzband/django-redis/blob/master/CHANGELOG.rst
django-redis django-redis
# Django extensions for Rich (terminal text rendering) # Django extensions for Rich (terminal text rendering)
# https://github.com/adamchainz/django-rich # https://github.com/adamchainz/django-rich/blob/main/CHANGELOG.rst
django-rich django-rich
# Django integration for RQ (Reqis queuing) # Django integration for RQ (Reqis queuing)
# https://github.com/rq/django-rq # https://github.com/rq/django-rq/blob/master/CHANGELOG.md
django-rq django-rq
# Abstraction models for rendering and paginating HTML tables # Abstraction models for rendering and paginating HTML tables
# https://github.com/jieter/django-tables2 # https://github.com/jieter/django-tables2/blob/master/CHANGELOG.md
django-tables2 django-tables2
# User-defined tags for objects # User-defined tags for objects
# https://github.com/alex/django-taggit # https://github.com/jazzband/django-taggit/blob/master/CHANGELOG.rst
django-taggit django-taggit
# A Django field for representing time zones # A Django field for representing time zones
@ -63,11 +63,11 @@ django-taggit
django-timezone-field django-timezone-field
# A REST API framework for Django projects # A REST API framework for Django projects
# https://github.com/encode/django-rest-framework # https://www.django-rest-framework.org/community/release-notes/
djangorestframework djangorestframework
# Sane and flexible OpenAPI 3 schema generation for Django REST framework. # Sane and flexible OpenAPI 3 schema generation for Django REST framework.
# https://github.com/tfranzel/drf-spectacular # https://github.com/tfranzel/drf-spectacular/blob/master/CHANGELOG.rst
drf-spectacular drf-spectacular
# Serve self-contained distribution builds of Swagger UI and Redoc with Django. # Serve self-contained distribution builds of Swagger UI and Redoc with Django.
@ -75,23 +75,23 @@ drf-spectacular
drf-spectacular-sidecar drf-spectacular-sidecar
# RSS feed parser # RSS feed parser
# https://github.com/kurtmckee/feedparser # https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
feedparser feedparser
# Django wrapper for Graphene (GraphQL support) # Django wrapper for Graphene (GraphQL support)
# https://github.com/graphql-python/graphene-django # https://github.com/graphql-python/graphene-django/releases
graphene_django graphene_django
# WSGI HTTP server # WSGI HTTP server
# https://gunicorn.org/ # https://docs.gunicorn.org/en/latest/news.html
gunicorn gunicorn
# Platform-agnostic template rendering engine # Platform-agnostic template rendering engine
# https://github.com/pallets/jinja # https://jinja.palletsprojects.com/changes/
Jinja2 Jinja2
# Simple markup language for rendering HTML # Simple markup language for rendering HTML
# https://github.com/Python-Markdown/markdown # https://python-markdown.github.io/change_log/
# mkdocs currently requires Markdown v3.3 # mkdocs currently requires Markdown v3.3
Markdown<3.4 Markdown<3.4
@ -100,50 +100,50 @@ Markdown<3.4
markdown-include markdown-include
# MkDocs Material theme (for documentation build) # MkDocs Material theme (for documentation build)
# https://github.com/squidfunk/mkdocs-material # https://squidfunk.github.io/mkdocs-material/changelog/
mkdocs-material mkdocs-material
# Introspection for embedded code # Introspection for embedded code
# https://github.com/mkdocstrings/mkdocstrings # https://github.com/mkdocstrings/mkdocstrings/blob/master/CHANGELOG.md
mkdocstrings[python-legacy] mkdocstrings[python-legacy]
# Library for manipulating IP prefixes and addresses # Library for manipulating IP prefixes and addresses
# https://github.com/netaddr/netaddr # https://github.com/netaddr/netaddr/blob/master/CHANGELOG
netaddr netaddr
# Fork of PIL (Python Imaging Library) for image processing # Fork of PIL (Python Imaging Library) for image processing
# https://github.com/python-pillow/Pillow # https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst
Pillow Pillow
# PostgreSQL database adapter for Python # PostgreSQL database adapter for Python
# https://github.com/psycopg/psycopg2 # https://www.psycopg.org/docs/news.html
psycopg2-binary psycopg2-binary
# YAML rendering library # YAML rendering library
# https://github.com/yaml/pyyaml # https://github.com/yaml/pyyaml/blob/master/CHANGES
PyYAML PyYAML
# Sentry SDK # Sentry SDK
# https://github.com/getsentry/sentry-python # https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md
sentry-sdk sentry-sdk
# Social authentication framework # Social authentication framework
# https://github.com/python-social-auth/social-core # https://github.com/python-social-auth/social-core/blob/master/CHANGELOG.md
social-auth-core social-auth-core
# Django app for social-auth-core # Django app for social-auth-core
# https://github.com/python-social-auth/social-app-django # https://github.com/python-social-auth/social-app-django/blob/master/CHANGELOG.md
# See https://github.com/python-social-auth/social-app-django/issues/429 # See https://github.com/python-social-auth/social-app-django/issues/429
social-auth-app-django==5.0.0 social-auth-app-django==5.0.0
# SVG image rendering (used for rack elevations) # SVG image rendering (used for rack elevations)
# https://github.com/mozman/svgwrite # hhttps://github.com/mozman/svgwrite/blob/master/NEWS.rst
svgwrite svgwrite
# Tabular dataset library (for table-based exports) # Tabular dataset library (for table-based exports)
# https://github.com/jazzband/tablib # https://github.com/jazzband/tablib/blob/master/HISTORY.md
tablib tablib
# Timezone data (required by django-timezone-field on Python 3.9+) # Timezone data (required by django-timezone-field on Python 3.9+)
# https://github.com/python/tzdata # https://github.com/python/tzdata/blob/master/NEWS.md
tzdata tzdata

View File

@ -18,4 +18,4 @@ interface.
Default: False Default: False
This parameter serves as a safeguard to prevent some potentially dangerous behavior, such as generating new database schema migrations. Set this to `True` **only** if you are actively developing the NetBox code base. This parameter serves as a safeguard to prevent some potentially dangerous behavior, such as generating new database schema migrations. Additionally, enabling this setting disables the debug warning banner in the UI. Set this to `True` **only** if you are actively developing the NetBox code base.

View File

@ -130,7 +130,7 @@ Once you have created a report, it will appear in the reports list. Initially, r
!!! note !!! note
To run a report, a user must be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below. To run a report, a user must be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below.
![Adding the run action to a permission](/media/admin_ui_run_permission.png) ![Adding the run action to a permission](../media/admin_ui_run_permission.png)
### Via the Web UI ### Via the Web UI

View File

@ -586,6 +586,15 @@ Additionally, a token can be set to expire at a specific time. This can be usefu
Each API token can optionally be restricted by client IP address. If one or more allowed IP prefixes/addresses is defined for a token, authentication will fail for any client connecting from an IP address outside the defined range(s). This enables restricting the use a token to a specific client. (By default, any client IP address is permitted.) Each API token can optionally be restricted by client IP address. If one or more allowed IP prefixes/addresses is defined for a token, authentication will fail for any client connecting from an IP address outside the defined range(s). This enables restricting the use a token to a specific client. (By default, any client IP address is permitted.)
#### Creating Tokens for Other Users
It is possible to provision authentication tokens for other users via the REST API. To do, so the requesting user must have the `users.grant_token` permission assigned. While all users have inherent permission to create their own tokens, this permission is required to enable the creation of tokens for other users.
![Adding the grant action to a permission](../media/admin_ui_grant_permission.png)
!!! warning "Exercise Caution"
The ability to create tokens on behalf of other users enables the requestor to access the created token. This ability is intended e.g. for the provisioning of tokens by automated services, and should be used with extreme caution to avoid a security compromise.
### Authenticating to the API ### Authenticating to the API
An authentication token is attached to a request by setting the `Authorization` header to the string `Token` followed by a space and the user's token: An authentication token is attached to a request by setting the `Authorization` header to the string `Token` followed by a space and the user's token:

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -1,16 +1,30 @@
# NetBox v3.4 # NetBox v3.4
## v3.4.8 (FUTURE) ## v3.4.9 (FUTURE)
---
## v3.4.8 (2023-04-12)
### Enhancements ### Enhancements
* [#10414](https://github.com/netbox-community/netbox/issues/10414) - Enable general purpose image attachments for device types
* [#10600](https://github.com/netbox-community/netbox/issues/10600) - Allow custom object fields to reference a user or group
* [#11015](https://github.com/netbox-community/netbox/issues/11015) - Remove unit from commit rate column header in circuits table
* [#11431](https://github.com/netbox-community/netbox/issues/11431) - Disallow changing custom field type after creation
* [#11453](https://github.com/netbox-community/netbox/issues/11453) - Display a warning banner when `DEBUG` is enabled
* [#12007](https://github.com/netbox-community/netbox/issues/12007) - Enable filtering of VM Interfaces by assigned VLAN * [#12007](https://github.com/netbox-community/netbox/issues/12007) - Enable filtering of VM Interfaces by assigned VLAN
* [#12095](https://github.com/netbox-community/netbox/issues/12095) - Specify UTF-8 encoding for default export template MIME type * [#12095](https://github.com/netbox-community/netbox/issues/12095) - Specify UTF-8 encoding for default export template MIME type
* [#12207](https://github.com/netbox-community/netbox/issues/12207) - Introduce the `grant_token` permission for controlling the creation of API tokens on behalf of other users
### Bug Fixes ### Bug Fixes
* [#10221](https://github.com/netbox-community/netbox/issues/10221) - Validate generic foreign key relations assigned via REST API requests
* [#11432](https://github.com/netbox-community/netbox/issues/11432) - Prevent existing components & component templates from being reassigned to different devices/device types via the REST API
* [#11454](https://github.com/netbox-community/netbox/issues/11454) - Raise validation error if generic foreign key assignment does not specify both object type and ID
* [#11746](https://github.com/netbox-community/netbox/issues/11746) - Fix cleanup of object data when deleting a custom field * [#11746](https://github.com/netbox-community/netbox/issues/11746) - Fix cleanup of object data when deleting a custom field
* [#12011](https://github.com/netbox-community/netbox/issues/12011) - Fix KeyError exception when attempting to add module bays in bulk * [#12011](https://github.com/netbox-community/netbox/issues/12011) - Fix KeyError exception when attempting to add module bays in bulk
* [#12040](https://github.com/netbox-community/netbox/issues/12040) - Display relevant UI tab upon bulk import validation failure
* [#12074](https://github.com/netbox-community/netbox/issues/12074) - Fix the automatic assignment of racks to devices via the REST API * [#12074](https://github.com/netbox-community/netbox/issues/12074) - Fix the automatic assignment of racks to devices via the REST API
* [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters * [#12084](https://github.com/netbox-community/netbox/issues/12084) - Fix exception when attempting to create a saved filter for applied filters
* [#12087](https://github.com/netbox-community/netbox/issues/12087) - Fix bulk editing of many-to-many relationships * [#12087](https://github.com/netbox-community/netbox/issues/12087) - Fix bulk editing of many-to-many relationships
@ -18,6 +32,7 @@
* [#12118](https://github.com/netbox-community/netbox/issues/12118) - Fix instantiation of nested inventory item templates when creating a device * [#12118](https://github.com/netbox-community/netbox/issues/12118) - Fix instantiation of nested inventory item templates when creating a device
* [#12184](https://github.com/netbox-community/netbox/issues/12184) - Fix filtered bulk deletion for various models * [#12184](https://github.com/netbox-community/netbox/issues/12184) - Fix filtered bulk deletion for various models
* [#12190](https://github.com/netbox-community/netbox/issues/12190) - Fix form layout for plugin textarea fields * [#12190](https://github.com/netbox-community/netbox/issues/12190) - Fix form layout for plugin textarea fields
* [#12227](https://github.com/netbox-community/netbox/issues/12227) - Fix tenant assignment on bulk import of L2VPNs
--- ---

View File

@ -64,7 +64,9 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
template_code=CIRCUITTERMINATION_LINK, template_code=CIRCUITTERMINATION_LINK,
verbose_name='Side Z' verbose_name='Side Z'
) )
commit_rate = CommitRateColumn() commit_rate = CommitRateColumn(
verbose_name='Commit Rate'
)
comments = columns.MarkdownColumn() comments = columns.MarkdownColumn()
tags = columns.TagColumn( tags = columns.TagColumn(
url_name='circuits:circuit_list' url_name='circuits:circuit_list'

View File

@ -1694,12 +1694,14 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
field_name='terminations__termination_type' field_name='terminations__termination_type'
) )
termination_a_id = MultiValueNumberFilter( termination_a_id = MultiValueNumberFilter(
method='filter_by_cable_end_a',
field_name='terminations__termination_id' field_name='terminations__termination_id'
) )
termination_b_type = ContentTypeFilter( termination_b_type = ContentTypeFilter(
field_name='terminations__termination_type' field_name='terminations__termination_type'
) )
termination_b_id = MultiValueNumberFilter( termination_b_id = MultiValueNumberFilter(
method='filter_by_cable_end_b',
field_name='terminations__termination_id' field_name='terminations__termination_id'
) )
type = django_filters.MultipleChoiceFilter( type = django_filters.MultipleChoiceFilter(
@ -1757,6 +1759,18 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
# Supported objects: device, rack, location, site # Supported objects: device, rack, location, site
return queryset.filter(**{f'terminations___{name}__in': value}).distinct() return queryset.filter(**{f'terminations___{name}__in': value}).distinct()
def filter_by_cable_end(self, queryset, name, value, side):
# Filter by termination id and cable_end type
return queryset.filter(**{f'{name}__in': value, 'terminations__cable_end': side}).distinct()
def filter_by_cable_end_a(self, queryset, name, value):
# Filter by termination id and cable_end type
return self.filter_by_cable_end(queryset, name, value, CableEndChoices.SIDE_A)
def filter_by_cable_end_b(self, queryset, name, value):
# Filter by termination id and cable_end type
return self.filter_by_cable_end(queryset, name, value, CableEndChoices.SIDE_B)
class CableTerminationFilterSet(BaseFilterSet): class CableTerminationFilterSet(BaseFilterSet):
termination_type = ContentTypeFilter() termination_type = ContentTypeFilter()

View File

@ -119,6 +119,12 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
), ),
) )
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Cache the original DeviceType ID for reference under clean()
self._original_device_type = self.device_type_id
def to_objectchange(self, action): def to_objectchange(self, action):
objectchange = super().to_objectchange(action) objectchange = super().to_objectchange(action)
if self.device_type is not None: if self.device_type is not None:
@ -130,6 +136,11 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
def clean(self): def clean(self):
super().clean() super().clean()
if self.pk is not None and self._original_device_type != self.device_type_id:
raise ValidationError({
"device_type": "Component templates cannot be moved to a different device type."
})
# A component template must belong to a DeviceType *or* to a ModuleType # A component template must belong to a DeviceType *or* to a ModuleType
if self.device_type and self.module_type: if self.device_type and self.module_type:
raise ValidationError( raise ValidationError(

View File

@ -78,6 +78,12 @@ class ComponentModel(NetBoxModel):
), ),
) )
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Cache the original Device ID for reference under clean()
self._original_device = self.device_id
def __str__(self): def __str__(self):
if self.label: if self.label:
return f"{self.name} ({self.label})" return f"{self.name} ({self.label})"
@ -88,6 +94,14 @@ class ComponentModel(NetBoxModel):
objectchange.related_object = self.device objectchange.related_object = self.device
return objectchange return objectchange
def clean(self):
super().clean()
if self.pk is not None and self._original_device != self.device_id:
raise ValidationError({
"device": "Components cannot be moved to a different device."
})
@property @property
def parent_object(self): def parent_object(self):
return self.device return self.device

View File

@ -128,6 +128,10 @@ class DeviceType(PrimaryModel, WeightMixin):
blank=True blank=True
) )
images = GenericRelation(
to='extras.ImageAttachment'
)
clone_fields = ( clone_fields = (
'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit' 'manufacturer', 'default_platform', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
) )

View File

@ -104,6 +104,12 @@ class CustomFieldSerializer(ValidatedModelSerializer):
'last_updated', 'last_updated',
] ]
def validate_type(self, value):
if self.instance and self.instance.type != value:
raise serializers.ValidationError('Changing the type of custom fields is not supported.')
return value
@extend_schema_field(OpenApiTypes.STR) @extend_schema_field(OpenApiTypes.STR)
def get_data_type(self, obj): def get_data_type(self, obj):
types = CustomFieldTypeChoices types = CustomFieldTypeChoices

View File

@ -1,6 +1,7 @@
import json import json
from django import forms from django import forms
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -39,7 +40,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
object_type = ContentTypeChoiceField( object_type = ContentTypeChoiceField(
queryset=ContentType.objects.all(), queryset=ContentType.objects.all(),
# TODO: Come up with a canonical way to register suitable models # TODO: Come up with a canonical way to register suitable models
limit_choices_to=FeatureQuery('webhooks'), limit_choices_to=FeatureQuery('webhooks').get_query() | Q(app_label='auth', model__in=['user', 'group']),
required=False, required=False,
help_text=_("Type of the related object (for object/multi-object fields only)") help_text=_("Type of the related object (for object/multi-object fields only)")
) )
@ -63,6 +64,13 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
) )
} }
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Disable changing the type of a CustomField as it almost universally causes errors if custom field data is already present.
if self.instance.pk:
self.fields['type'].disabled = True
class CustomLinkForm(BootstrapMixin, forms.ModelForm): class CustomLinkForm(BootstrapMixin, forms.ModelForm):
content_types = ContentTypeMultipleChoiceField( content_types = ContentTypeMultipleChoiceField(

View File

@ -103,6 +103,11 @@ class CustomFieldTest(APIViewTestCases.APIViewTestCase):
bulk_update_data = { bulk_update_data = {
'description': 'New description', 'description': 'New description',
} }
update_data = {
'content_types': ['dcim.device'],
'name': 'New_Name',
'description': 'New description',
}
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):

View File

@ -456,7 +456,8 @@ class L2VPNImportForm(NetBoxModelImportForm):
class Meta: class Meta:
model = L2VPN model = L2VPN
fields = ('identifier', 'name', 'slug', 'type', 'description', 'comments', 'tags') fields = ('identifier', 'name', 'slug', 'tenant', 'type', 'description',
'comments', 'tags')
class L2VPNTerminationImportForm(NetBoxModelImportForm): class L2VPNTerminationImportForm(NetBoxModelImportForm):

View File

@ -1,4 +1,5 @@
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.validators import ValidationError from django.core.validators import ValidationError
from django.db import models from django.db import models
from mptt.models import MPTTModel, TreeForeignKey from mptt.models import MPTTModel, TreeForeignKey
@ -58,6 +59,33 @@ class NetBoxModel(CloningMixin, NetBoxFeatureSet, models.Model):
class Meta: class Meta:
abstract = True abstract = True
def clean(self):
"""
Validate the model for GenericForeignKey fields to ensure that the content type and object ID exist.
"""
super().clean()
for field in self._meta.get_fields():
if isinstance(field, GenericForeignKey):
ct_value = getattr(self, field.ct_field)
fk_value = getattr(self, field.fk_field)
if ct_value is None and fk_value is not None:
raise ValidationError({
field.ct_field: "This field cannot be null.",
})
if fk_value is None and ct_value is not None:
raise ValidationError({
field.fk_field: "This field cannot be null.",
})
if ct_value and fk_value:
klass = getattr(self, field.ct_field).model_class()
if not klass.objects.filter(pk=fk_value).exists():
raise ValidationError({
field.fk_field: f"Related object not found using the provided value: {fk_value}."
})
class PrimaryModel(NetBoxModel): class PrimaryModel(NetBoxModel):
""" """

View File

@ -70,10 +70,17 @@ Blocks:
</div> </div>
{% endif %} {% endif %}
{% if settings.DEBUG and not settings.DEVELOPER %}
<div class="alert alert-warning text-center mx-3" role="alert">
<strong><i class="mdi mdi-alert"></i> Debug mode is enabled.</strong>
Performance may be limited. Debugging should never be enabled on a production system.
</div>
{% endif %}
{% if config.MAINTENANCE_MODE %} {% if config.MAINTENANCE_MODE %}
<div class="alert alert-warning text-center mx-3" role="alert"> <div class="alert alert-warning text-center mx-3" role="alert">
<h4><i class="mdi mdi-alert"></i> Maintenance Mode</h4> <h5><i class="mdi mdi-alert"></i> Maintenance Mode</h5>
<span>NetBox is currently in maintenance mode. Functionality may be limited.</span> NetBox is currently in maintenance mode. Functionality may be limited.
</div> </div>
{% endif %} {% endif %}

View File

@ -99,6 +99,7 @@
{% include 'inc/panels/related_objects.html' %} {% include 'inc/panels/related_objects.html' %}
{% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/custom_fields.html' %}
{% include 'inc/panels/comments.html' %} {% include 'inc/panels/comments.html' %}
{% include 'inc/panels/image_attachments.html' %}
{% plugin_right_page object %} {% plugin_right_page object %}
</div> </div>
</div> </div>

View File

@ -15,17 +15,17 @@ Context:
{% block tabs %} {% block tabs %}
<ul class="nav nav-tabs px-3"> <ul class="nav nav-tabs px-3">
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link active" id="import-form-tab" data-bs-toggle="tab" data-bs-target="#import-form" type="button" role="tab" aria-controls="import-form" aria-selected="true"> <button class="nav-link active" id="import-form-tab" data-bs-toggle="tab" data-bs-target="#import-form" data-href="#tab_import-form" type="button" role="tab" aria-controls="import-form" aria-selected="true">
Direct Import Direct Import
</button> </button>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link" id="upload-form-tab" data-bs-toggle="tab" data-bs-target="#upload-form" type="button" role="tab" aria-controls="upload-form" aria-selected="false"> <button class="nav-link" id="upload-form-tab" data-bs-toggle="tab" data-bs-target="#upload-form" data-href="#tab_upload-form" type="button" role="tab" aria-controls="upload-form" aria-selected="false">
Upload File Upload File
</button> </button>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link" id="datafile-form-tab" data-bs-toggle="tab" data-bs-target="#datafile-form" type="button" role="tab" aria-controls="datafile-form" aria-selected="false"> <button class="nav-link" id="datafile-form-tab" data-bs-toggle="tab" data-bs-target="#datafile-form" data-href="#tab_datafile-form" type="button" role="tab" aria-controls="datafile-form" aria-selected="false">
Data File Data File
</button> </button>
</li> </li>

View File

@ -4,6 +4,7 @@ from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied
from netbox.api.fields import ContentTypeField, IPNetworkSerializer, SerializedPKRelatedField from netbox.api.fields import ContentTypeField, IPNetworkSerializer, SerializedPKRelatedField
from netbox.api.serializers import ValidatedModelSerializer from netbox.api.serializers import ValidatedModelSerializer
@ -94,6 +95,16 @@ class TokenSerializer(ValidatedModelSerializer):
data['key'] = Token.generate_key() data['key'] = Token.generate_key()
return super().to_internal_value(data) return super().to_internal_value(data)
def validate(self, data):
# If the Token is being created on behalf of another user, enforce the grant_token permission.
request = self.context.get('request')
token_user = data.get('user')
if token_user and token_user != request.user and not request.user.has_perm('users.grant_token'):
raise PermissionDenied("This user does not have permission to create tokens for other users.")
return super().validate(data)
class TokenProvisionSerializer(serializers.Serializer): class TokenProvisionSerializer(serializers.Serializer):
username = serializers.CharField() username = serializers.CharField()

View File

@ -153,6 +153,26 @@ class TokenTest(
response = self.client.post(url, data, format='json', **self.header) response = self.client.post(url, data, format='json', **self.header)
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
def test_provision_token_other_user(self):
"""
Test provisioning a Token for a different User with & without the grant_token permission.
"""
self.add_permissions('users.add_token')
user2 = User.objects.create_user(username='testuser2')
data = {
'user': user2.id,
}
url = reverse('users-api:token-list')
# Attempt to create a new Token for User2 *without* the grant_token permission
response = self.client.post(url, data, format='json', **self.header)
self.assertEqual(response.status_code, 403)
# Assign grant_token permission and successfully create a new Token for User2
self.add_permissions('users.grant_token')
response = self.client.post(url, data, format='json', **self.header)
self.assertEqual(response.status_code, 201)
class ObjectPermissionTest( class ObjectPermissionTest(
# No GraphQL support for ObjectPermission # No GraphQL support for ObjectPermission

View File

@ -48,6 +48,10 @@ def get_viewname(model, action=None, rest_api=False):
if is_plugin: if is_plugin:
viewname = f'plugins-api:{app_label}-api:{model_name}' viewname = f'plugins-api:{app_label}-api:{model_name}'
else: else:
# Alter the app_label for group and user model_name to point to users app
if app_label == 'auth' and model_name in ['group', 'user']:
app_label = 'users'
viewname = f'{app_label}-api:{model_name}' viewname = f'{app_label}-api:{model_name}'
# Append the action, if any # Append the action, if any
if action: if action:

View File

@ -1,8 +1,8 @@
bleach==5.0.1 bleach==5.0.1
boto3==1.26.91 boto3==1.26.91
Django==4.1.7 Django==4.1.8
django-cors-headers==3.14.0 django-cors-headers==3.14.0
django-debug-toolbar==3.8.1 django-debug-toolbar==4.0.0
django-filter==23.1 django-filter==23.1
django-graphiql-debug-toolbar==0.2.0 django-graphiql-debug-toolbar==0.2.0
django-mptt==0.14 django-mptt==0.14
@ -22,18 +22,18 @@ graphene-django==3.0.0
gunicorn==20.1.0 gunicorn==20.1.0
Jinja2==3.1.2 Jinja2==3.1.2
Markdown==3.3.7 Markdown==3.3.7
mkdocs-material==9.1.4 mkdocs-material==9.1.6
mkdocstrings[python-legacy]==0.20.0 mkdocstrings[python-legacy]==0.21.2
netaddr==0.8.0 netaddr==0.8.0
Pillow==9.4.0 Pillow==9.5.0
psycopg2-binary==2.9.5 psycopg2-binary==2.9.6
PyYAML==6.0 PyYAML==6.0
sentry-sdk==1.18.0 sentry-sdk==1.19.1
social-auth-app-django==5.0.0 social-auth-app-django==5.0.0
social-auth-core[openidconnect]==4.4.0 social-auth-core[openidconnect]==4.4.1
svgwrite==1.4.3 svgwrite==1.4.3
tablib==3.4.0 tablib==3.4.0
tzdata==2023.2 tzdata==2023.3
# Workaround for #7401 # Workaround for #7401
jsonschema==3.2.0 jsonschema==3.2.0