From e23b2c4c4fdf9d7c77145823648dc1c1ede7274e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 29 May 2020 16:27:36 -0400 Subject: [PATCH] Implement RestrictedQuerySet as a manager --- netbox/circuits/models.py | 8 ++++- netbox/circuits/querysets.py | 6 ++-- netbox/dcim/models/__init__.py | 35 +++++++++++++++++-- .../dcim/models/device_component_templates.py | 2 ++ netbox/dcim/models/device_components.py | 3 ++ netbox/extras/models/models.py | 3 ++ netbox/extras/models/tags.py | 3 ++ netbox/extras/querysets.py | 4 ++- netbox/ipam/managers.py | 3 +- netbox/ipam/models.py | 23 ++++++++---- netbox/ipam/querysets.py | 4 +-- netbox/secrets/models.py | 6 +++- netbox/tenancy/models.py | 7 +++- netbox/utilities/mptt.py | 19 ++++++++++ netbox/utilities/querysets.py | 2 +- netbox/virtualization/models.py | 11 ++++-- 16 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 netbox/utilities/mptt.py diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 57d41a994..dcf1c5118 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -8,6 +8,7 @@ from dcim.fields import ASNField from dcim.models import CableTermination from extras.models import CustomFieldModel, ObjectChange, TaggedItem from extras.utils import extras_features +from utilities.querysets import RestrictedQuerySet from utilities.models import ChangeLoggedModel from utilities.utils import serialize_object from .choices import * @@ -66,9 +67,10 @@ class Provider(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = [ 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', ] @@ -115,6 +117,8 @@ class CircuitType(ChangeLoggedModel): blank=True, ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'description'] class Meta: @@ -300,6 +304,8 @@ class CircuitTermination(CableTermination): blank=True ) + objects = RestrictedQuerySet.as_manager() + class Meta: ordering = ['circuit', 'term_side'] unique_together = ['circuit', 'term_side'] diff --git a/netbox/circuits/querysets.py b/netbox/circuits/querysets.py index 60956f32a..8a9bd50a4 100644 --- a/netbox/circuits/querysets.py +++ b/netbox/circuits/querysets.py @@ -1,7 +1,9 @@ -from django.db.models import OuterRef, QuerySet, Subquery +from django.db.models import OuterRef, Subquery + +from utilities.querysets import RestrictedQuerySet -class CircuitQuerySet(QuerySet): +class CircuitQuerySet(RestrictedQuerySet): def annotate_sites(self): """ diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 1f6478119..3dd3b8c89 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -25,7 +25,9 @@ from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange, Ta from extras.utils import extras_features from utilities.choices import ColorChoices from utilities.fields import ColorField, NaturalOrderingField +from utilities.querysets import RestrictedQuerySet from utilities.models import ChangeLoggedModel +from utilities.mptt import TreeManager from utilities.utils import serialize_object, to_meters from utilities.validators import ExclusionValidator from .device_component_templates import ( @@ -103,6 +105,8 @@ class Region(MPTTModel, ChangeLoggedModel): blank=True ) + objects = TreeManager() + csv_headers = ['name', 'slug', 'parent', 'description'] class MPTTMeta: @@ -244,6 +248,8 @@ class Site(ChangeLoggedModel, CustomFieldModel): ) tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = [ 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', 'comments', @@ -326,6 +332,8 @@ class RackGroup(MPTTModel, ChangeLoggedModel): blank=True ) + objects = TreeManager() + csv_headers = ['site', 'parent', 'name', 'slug', 'description'] class Meta: @@ -388,6 +396,8 @@ class RackRole(ChangeLoggedModel): blank=True, ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'color', 'description'] class Meta: @@ -526,6 +536,8 @@ class Rack(ChangeLoggedModel, CustomFieldModel): ) tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = [ 'site', 'group', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'comments', @@ -821,6 +833,8 @@ class RackReservation(ChangeLoggedModel): max_length=200 ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['site', 'rack_group', 'rack', 'units', 'tenant', 'user', 'description'] class Meta: @@ -900,6 +914,8 @@ class Manufacturer(ChangeLoggedModel): blank=True ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'description'] class Meta: @@ -982,9 +998,10 @@ class DeviceType(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + clone_fields = [ 'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', ] @@ -1206,6 +1223,8 @@ class DeviceRole(ChangeLoggedModel): blank=True, ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'color', 'vm_role', 'description'] class Meta: @@ -1263,6 +1282,8 @@ class Platform(ChangeLoggedModel): blank=True ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'description'] class Meta: @@ -1429,6 +1450,8 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): ) tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = [ 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status', 'site', 'rack_group', 'rack_name', 'position', 'face', 'comments', @@ -1741,9 +1764,10 @@ class VirtualChassis(ChangeLoggedModel): max_length=30, blank=True ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['master', 'domain'] class Meta: @@ -1813,6 +1837,8 @@ class PowerPanel(ChangeLoggedModel): max_length=50 ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['site', 'rack_group', 'name'] class Meta: @@ -1916,9 +1942,10 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = [ 'site', 'power_panel', 'rack_group', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', 'comments', @@ -2084,6 +2111,8 @@ class Cable(ChangeLoggedModel): null=True ) + objects = RestrictedQuerySet.as_manager() + csv_headers = [ 'termination_a_type', 'termination_a_id', 'termination_b_type', 'termination_b_id', 'type', 'status', 'label', 'color', 'length', 'length_unit', diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 164d37d77..e412a602e 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -6,6 +6,7 @@ from dcim.choices import * from dcim.constants import * from extras.models import ObjectChange from utilities.fields import NaturalOrderingField +from utilities.querysets import RestrictedQuerySet from utilities.ordering import naturalize_interface from utilities.utils import serialize_object from .device_components import ( @@ -26,6 +27,7 @@ __all__ = ( class ComponentTemplateModel(models.Model): + objects = RestrictedQuerySet.as_manager() class Meta: abstract = True diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py index 4005d41a4..702455c7e 100644 --- a/netbox/dcim/models/device_components.py +++ b/netbox/dcim/models/device_components.py @@ -16,6 +16,7 @@ from extras.models import ObjectChange, TaggedItem from extras.utils import extras_features from utilities.fields import NaturalOrderingField from utilities.ordering import naturalize_interface +from utilities.querysets import RestrictedQuerySet from utilities.query_functions import CollateAsChar from utilities.utils import serialize_object from virtualization.choices import VMInterfaceTypeChoices @@ -41,6 +42,8 @@ class ComponentModel(models.Model): blank=True ) + objects = RestrictedQuerySet.as_manager() + class Meta: abstract = True diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index f98a7b34f..a94fc3eea 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -12,6 +12,7 @@ from django.template import Template, Context from django.urls import reverse from rest_framework.utils.encoders import JSONEncoder +from utilities.querysets import RestrictedQuerySet from utilities.utils import deepmerge, render_jinja2 from extras.choices import * from extras.constants import * @@ -670,6 +671,8 @@ class ObjectChange(models.Model): editable=False ) + objects = RestrictedQuerySet.as_manager() + csv_headers = [ 'time', 'user', 'user_name', 'request_id', 'action', 'changed_object_type', 'changed_object_id', 'related_object_type', 'related_object_id', 'object_repr', 'object_data', diff --git a/netbox/extras/models/tags.py b/netbox/extras/models/tags.py index d68ca2ce6..d5792ebda 100644 --- a/netbox/extras/models/tags.py +++ b/netbox/extras/models/tags.py @@ -6,6 +6,7 @@ from taggit.models import TagBase, GenericTaggedItemBase from utilities.choices import ColorChoices from utilities.fields import ColorField from utilities.models import ChangeLoggedModel +from utilities.querysets import RestrictedQuerySet # @@ -21,6 +22,8 @@ class Tag(TagBase, ChangeLoggedModel): blank=True, ) + objects = RestrictedQuerySet.as_manager() + def get_absolute_url(self): return reverse('extras:tag', args=[self.slug]) diff --git a/netbox/extras/querysets.py b/netbox/extras/querysets.py index 812c66714..9d9b55778 100644 --- a/netbox/extras/querysets.py +++ b/netbox/extras/querysets.py @@ -2,6 +2,8 @@ from collections import OrderedDict from django.db.models import Q, QuerySet +from utilities.querysets import RestrictedQuerySet + class CustomFieldQueryset: """ @@ -19,7 +21,7 @@ class CustomFieldQueryset: yield obj -class ConfigContextQuerySet(QuerySet): +class ConfigContextQuerySet(RestrictedQuerySet): def get_for_object(self, obj): """ diff --git a/netbox/ipam/managers.py b/netbox/ipam/managers.py index 8811e504a..245a3c891 100644 --- a/netbox/ipam/managers.py +++ b/netbox/ipam/managers.py @@ -1,6 +1,7 @@ from django.db import models from ipam.lookups import Host, Inet +from utilities.querysets import RestrictedQuerySet class IPAddressManager(models.Manager): @@ -13,5 +14,5 @@ class IPAddressManager(models.Manager): then re-cast this value to INET() so that records will be ordered properly. We are essentially re-casting each IP address as a /32 or /128. """ - qs = super().get_queryset() + qs = RestrictedQuerySet(self.model, using=self._db) return qs.order_by(Inet(Host('address'))) diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index eeb985b7c..b99a6c919 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -12,6 +12,7 @@ from dcim.models import Device, Interface from extras.models import CustomFieldModel, ObjectChange, TaggedItem from extras.utils import extras_features from utilities.models import ChangeLoggedModel +from utilities.querysets import RestrictedQuerySet from utilities.utils import serialize_object from virtualization.models import VirtualMachine from .choices import * @@ -74,9 +75,10 @@ class VRF(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'rd', 'tenant', 'enforce_unique', 'description'] clone_fields = [ 'tenant', 'enforce_unique', 'description', @@ -131,6 +133,8 @@ class RIR(ChangeLoggedModel): blank=True ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'is_private', 'description'] class Meta: @@ -179,9 +183,10 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['prefix', 'rir', 'date_added', 'description'] clone_fields = [ 'rir', 'date_added', 'description', @@ -274,6 +279,8 @@ class Role(ChangeLoggedModel): blank=True, ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'weight', 'description'] class Meta: @@ -360,9 +367,9 @@ class Prefix(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) + tags = TaggableManager(through=TaggedItem) objects = PrefixQuerySet.as_manager() - tags = TaggableManager(through=TaggedItem) csv_headers = [ 'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan', 'status', 'role', 'is_pool', 'description', @@ -631,9 +638,9 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) + tags = TaggableManager(through=TaggedItem) objects = IPAddressManager() - tags = TaggableManager(through=TaggedItem) csv_headers = [ 'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary', @@ -828,6 +835,8 @@ class VLANGroup(ChangeLoggedModel): blank=True ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'site', 'description'] class Meta: @@ -923,9 +932,10 @@ class VLAN(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description'] clone_fields = [ 'site', 'group', 'tenant', 'status', 'role', 'description', @@ -1039,9 +1049,10 @@ class Service(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['device', 'virtual_machine', 'name', 'protocol', 'port', 'description'] class Meta: diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py index 3a48be789..6d2dc6f33 100644 --- a/netbox/ipam/querysets.py +++ b/netbox/ipam/querysets.py @@ -1,7 +1,7 @@ -from django.db.models import QuerySet +from utilities.querysets import RestrictedQuerySet -class PrefixQuerySet(QuerySet): +class PrefixQuerySet(RestrictedQuerySet): def annotate_depth(self, limit=None): """ diff --git a/netbox/secrets/models.py b/netbox/secrets/models.py index 61d8adb6b..757ef88c7 100644 --- a/netbox/secrets/models.py +++ b/netbox/secrets/models.py @@ -17,6 +17,7 @@ from dcim.models import Device from extras.models import CustomFieldModel, TaggedItem from extras.utils import extras_features from utilities.models import ChangeLoggedModel +from utilities.querysets import RestrictedQuerySet from .exceptions import InvalidKey from .hashers import SecretValidationHasher from .querysets import UserKeyQuerySet @@ -268,6 +269,8 @@ class SecretRole(ChangeLoggedModel): blank=True ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'description'] class Meta: @@ -333,9 +336,10 @@ class Secret(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + plaintext = None csv_headers = ['device', 'role', 'name', 'plaintext'] diff --git a/netbox/tenancy/models.py b/netbox/tenancy/models.py index 077fb6ad1..2e415b965 100644 --- a/netbox/tenancy/models.py +++ b/netbox/tenancy/models.py @@ -7,6 +7,8 @@ from taggit.managers import TaggableManager from extras.models import CustomFieldModel, ObjectChange, TaggedItem from extras.utils import extras_features from utilities.models import ChangeLoggedModel +from utilities.mptt import TreeManager +from utilities.querysets import RestrictedQuerySet from utilities.utils import serialize_object @@ -40,6 +42,8 @@ class TenantGroup(MPTTModel, ChangeLoggedModel): blank=True ) + objects = TreeManager() + csv_headers = ['name', 'slug', 'parent', 'description'] class Meta: @@ -104,9 +108,10 @@ class Tenant(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'group', 'description', 'comments'] clone_fields = [ 'group', 'description', diff --git a/netbox/utilities/mptt.py b/netbox/utilities/mptt.py new file mode 100644 index 000000000..1bae2053d --- /dev/null +++ b/netbox/utilities/mptt.py @@ -0,0 +1,19 @@ +from mptt.managers import TreeManager as TreeManager_ +from mptt.querysets import TreeQuerySet as TreeQuerySet_ + +from django.db.models import Manager +from .querysets import RestrictedQuerySet + + +class TreeQuerySet(TreeQuerySet_, RestrictedQuerySet): + """ + Mate django-mptt's TreeQuerySet with our RestrictedQuerySet for permissions enforcement. + """ + pass + + +class TreeManager(Manager.from_queryset(TreeQuerySet), TreeManager_): + """ + Extend django-mptt's TreeManager to incorporate RestrictedQuerySet(). + """ + pass diff --git a/netbox/utilities/querysets.py b/netbox/utilities/querysets.py index 36460310e..3bc41e072 100644 --- a/netbox/utilities/querysets.py +++ b/netbox/utilities/querysets.py @@ -27,7 +27,7 @@ class RestrictedQuerySet(QuerySet): # Determine what constraints (if any) have been placed on this user for this action and model # TODO: Find a better way to ensure permissions are cached if not hasattr(user, '_object_perm_cache'): - user.get_all_permisisons() + user.get_all_permissions() obj_perm_attrs = user._object_perm_cache[permission_required] # Filter the queryset to include only objects with allowed attributes diff --git a/netbox/virtualization/models.py b/netbox/virtualization/models.py index 3daeff013..8ad40bab7 100644 --- a/netbox/virtualization/models.py +++ b/netbox/virtualization/models.py @@ -9,6 +9,7 @@ from dcim.models import Device from extras.models import ConfigContextModel, CustomFieldModel, TaggedItem from extras.utils import extras_features from utilities.models import ChangeLoggedModel +from utilities.querysets import RestrictedQuerySet from .choices import * @@ -40,6 +41,8 @@ class ClusterType(ChangeLoggedModel): blank=True ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'description'] class Meta: @@ -79,6 +82,8 @@ class ClusterGroup(ChangeLoggedModel): blank=True ) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'slug', 'description'] class Meta: @@ -145,9 +150,10 @@ class Cluster(ChangeLoggedModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = ['name', 'type', 'group', 'site', 'comments'] clone_fields = [ 'type', 'group', 'tenant', 'site', @@ -269,9 +275,10 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): content_type_field='obj_type', object_id_field='obj_id' ) - tags = TaggableManager(through=TaggedItem) + objects = RestrictedQuerySet.as_manager() + csv_headers = [ 'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments', ]