mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-25 01:48:38 -06:00
Add web UI view tests for object-level permissions
This commit is contained in:
parent
aeb32104a4
commit
64f60228ec
@ -8,6 +8,7 @@ from django.views.generic import View
|
|||||||
from django_tables2 import RequestConfig
|
from django_tables2 import RequestConfig
|
||||||
|
|
||||||
from dcim.models import Device, Interface
|
from dcim.models import Device, Interface
|
||||||
|
from netbox.authentication import ObjectPermissionRequiredMixin
|
||||||
from utilities.paginator import EnhancedPaginator
|
from utilities.paginator import EnhancedPaginator
|
||||||
from utilities.views import (
|
from utilities.views import (
|
||||||
BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||||
@ -440,7 +441,7 @@ class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
# Prefixes
|
# Prefixes
|
||||||
#
|
#
|
||||||
|
|
||||||
class PrefixListView(PermissionRequiredMixin, ObjectListView):
|
class PrefixListView(ObjectPermissionRequiredMixin, ObjectListView):
|
||||||
permission_required = 'ipam.view_prefix'
|
permission_required = 'ipam.view_prefix'
|
||||||
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
||||||
filterset = filters.PrefixFilterSet
|
filterset = filters.PrefixFilterSet
|
||||||
@ -454,14 +455,13 @@ class PrefixListView(PermissionRequiredMixin, ObjectListView):
|
|||||||
return self.queryset.annotate_depth(limit=limit)
|
return self.queryset.annotate_depth(limit=limit)
|
||||||
|
|
||||||
|
|
||||||
class PrefixView(PermissionRequiredMixin, View):
|
class PrefixView(ObjectPermissionRequiredMixin, View):
|
||||||
permission_required = 'ipam.view_prefix'
|
permission_required = 'ipam.view_prefix'
|
||||||
|
queryset = Prefix.objects.prefetch_related('vrf', 'site__region', 'tenant__group', 'vlan__group', 'role')
|
||||||
|
|
||||||
def get(self, request, pk):
|
def get(self, request, pk):
|
||||||
|
|
||||||
prefix = get_object_or_404(Prefix.objects.prefetch_related(
|
prefix = get_object_or_404(self.queryset, pk=pk)
|
||||||
'vrf', 'site__region', 'tenant__group', 'vlan__group', 'role'
|
|
||||||
), pk=pk)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
aggregate = Aggregate.objects.get(prefix__net_contains_or_equals=str(prefix.prefix))
|
aggregate = Aggregate.objects.get(prefix__net_contains_or_equals=str(prefix.prefix))
|
||||||
@ -586,7 +586,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class PrefixCreateView(PermissionRequiredMixin, ObjectEditView):
|
class PrefixCreateView(ObjectPermissionRequiredMixin, ObjectEditView):
|
||||||
permission_required = 'ipam.add_prefix'
|
permission_required = 'ipam.add_prefix'
|
||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
model_form = forms.PrefixForm
|
model_form = forms.PrefixForm
|
||||||
@ -598,7 +598,7 @@ class PrefixEditView(PrefixCreateView):
|
|||||||
permission_required = 'ipam.change_prefix'
|
permission_required = 'ipam.change_prefix'
|
||||||
|
|
||||||
|
|
||||||
class PrefixDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
class PrefixDeleteView(ObjectPermissionRequiredMixin, ObjectDeleteView):
|
||||||
permission_required = 'ipam.delete_prefix'
|
permission_required = 'ipam.delete_prefix'
|
||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
template_name = 'ipam/prefix_delete.html'
|
template_name = 'ipam/prefix_delete.html'
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Group, User
|
from django.contrib.auth.models import Group, User
|
||||||
from django.test import Client, TestCase
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.test import Client
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from netaddr import IPNetwork
|
||||||
|
|
||||||
|
from dcim.models import Site
|
||||||
|
from ipam.choices import PrefixStatusChoices
|
||||||
|
from ipam.models import Prefix
|
||||||
|
from users.models import ObjectPermission
|
||||||
|
from utilities.testing.testcases import TestCase
|
||||||
|
|
||||||
|
|
||||||
class ExternalAuthenticationTestCase(TestCase):
|
class ExternalAuthenticationTestCase(TestCase):
|
||||||
@ -157,3 +165,214 @@ class ExternalAuthenticationTestCase(TestCase):
|
|||||||
new_user = User.objects.get(username='remoteuser2')
|
new_user = User.objects.get(username='remoteuser2')
|
||||||
self.assertEqual(int(self.client.session.get('_auth_user_id')), new_user.pk, msg='Authentication failed')
|
self.assertEqual(int(self.client.session.get('_auth_user_id')), new_user.pk, msg='Authentication failed')
|
||||||
self.assertTrue(new_user.has_perms(['dcim.add_site', 'dcim.change_site']))
|
self.assertTrue(new_user.has_perms(['dcim.add_site', 'dcim.change_site']))
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectPermissionTestCase(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
|
||||||
|
cls.sites = (
|
||||||
|
Site(name='Site 1', slug='site-1'),
|
||||||
|
Site(name='Site 2', slug='site-2'),
|
||||||
|
Site(name='Site 3', slug='site-3'),
|
||||||
|
)
|
||||||
|
Site.objects.bulk_create(cls.sites)
|
||||||
|
|
||||||
|
cls.prefixes = (
|
||||||
|
Prefix(prefix=IPNetwork('10.0.0.0/24'), site=cls.sites[0]),
|
||||||
|
Prefix(prefix=IPNetwork('10.0.1.0/24'), site=cls.sites[0]),
|
||||||
|
Prefix(prefix=IPNetwork('10.0.2.0/24'), site=cls.sites[0]),
|
||||||
|
Prefix(prefix=IPNetwork('10.0.3.0/24'), site=cls.sites[1]),
|
||||||
|
Prefix(prefix=IPNetwork('10.0.4.0/24'), site=cls.sites[1]),
|
||||||
|
Prefix(prefix=IPNetwork('10.0.5.0/24'), site=cls.sites[1]),
|
||||||
|
Prefix(prefix=IPNetwork('10.0.6.0/24'), site=cls.sites[2]),
|
||||||
|
Prefix(prefix=IPNetwork('10.0.7.0/24'), site=cls.sites[2]),
|
||||||
|
Prefix(prefix=IPNetwork('10.0.8.0/24'), site=cls.sites[2]),
|
||||||
|
)
|
||||||
|
Prefix.objects.bulk_create(cls.prefixes)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
||||||
|
def test_ui_get_object(self):
|
||||||
|
|
||||||
|
# Assign object permission
|
||||||
|
obj_perm = ObjectPermission(
|
||||||
|
model=ContentType.objects.get_for_model(Prefix),
|
||||||
|
attrs={
|
||||||
|
'site__name': 'Site 1',
|
||||||
|
},
|
||||||
|
can_view=True
|
||||||
|
)
|
||||||
|
obj_perm.save()
|
||||||
|
obj_perm.users.add(self.user)
|
||||||
|
|
||||||
|
# Retrieve permitted object
|
||||||
|
response = self.client.get(self.prefixes[0].get_absolute_url())
|
||||||
|
self.assertHttpStatus(response, 200)
|
||||||
|
|
||||||
|
# Attempt to retrieve non-permitted object
|
||||||
|
response = self.client.get(self.prefixes[3].get_absolute_url())
|
||||||
|
self.assertHttpStatus(response, 404)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
||||||
|
def test_ui_list_objects(self):
|
||||||
|
|
||||||
|
# Attempt to list objects without permission
|
||||||
|
response = self.client.get(reverse('ipam:prefix_list'))
|
||||||
|
self.assertHttpStatus(response, 403)
|
||||||
|
|
||||||
|
# Assign object permission
|
||||||
|
obj_perm = ObjectPermission(
|
||||||
|
model=ContentType.objects.get_for_model(Prefix),
|
||||||
|
attrs={
|
||||||
|
'site__name': 'Site 1',
|
||||||
|
},
|
||||||
|
can_view=True
|
||||||
|
)
|
||||||
|
obj_perm.save()
|
||||||
|
obj_perm.users.add(self.user)
|
||||||
|
|
||||||
|
# Retrieve all objects. Only permitted objects should be returned.
|
||||||
|
response = self.client.get(reverse('ipam:prefix_list'))
|
||||||
|
self.assertHttpStatus(response, 200)
|
||||||
|
self.assertIn(str(self.prefixes[0].prefix), str(response.content))
|
||||||
|
self.assertNotIn(str(self.prefixes[3].prefix), str(response.content))
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
||||||
|
def test_ui_create_object(self):
|
||||||
|
initial_count = Prefix.objects.count()
|
||||||
|
form_data = {
|
||||||
|
'prefix': '10.0.9.0/24',
|
||||||
|
'site': self.sites[1].pk,
|
||||||
|
'status': PrefixStatusChoices.STATUS_ACTIVE,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Attempt to create an object without permission
|
||||||
|
request = {
|
||||||
|
'path': reverse('ipam:prefix_add'),
|
||||||
|
'data': form_data,
|
||||||
|
'follow': False, # Do not follow 302 redirects
|
||||||
|
}
|
||||||
|
response = self.client.post(**request)
|
||||||
|
self.assertHttpStatus(response, 403)
|
||||||
|
self.assertEqual(initial_count, Prefix.objects.count())
|
||||||
|
|
||||||
|
# Assign object permission
|
||||||
|
obj_perm = ObjectPermission(
|
||||||
|
model=ContentType.objects.get_for_model(Prefix),
|
||||||
|
attrs={
|
||||||
|
'site__name': 'Site 1',
|
||||||
|
},
|
||||||
|
can_view=True,
|
||||||
|
can_add=True
|
||||||
|
)
|
||||||
|
obj_perm.save()
|
||||||
|
obj_perm.users.add(self.user)
|
||||||
|
|
||||||
|
# Attempt to create a non-permitted object
|
||||||
|
request = {
|
||||||
|
'path': reverse('ipam:prefix_add'),
|
||||||
|
'data': form_data,
|
||||||
|
'follow': True, # Follow 302 redirects
|
||||||
|
}
|
||||||
|
response = self.client.post(**request)
|
||||||
|
self.assertHttpStatus(response, 200)
|
||||||
|
self.assertEqual(initial_count, Prefix.objects.count())
|
||||||
|
|
||||||
|
# Create a permitted object
|
||||||
|
form_data['site'] = self.sites[0].pk
|
||||||
|
request = {
|
||||||
|
'path': reverse('ipam:prefix_add'),
|
||||||
|
'data': form_data,
|
||||||
|
'follow': True, # Follow 302 redirects
|
||||||
|
}
|
||||||
|
response = self.client.post(**request)
|
||||||
|
self.assertHttpStatus(response, 200)
|
||||||
|
self.assertEqual(initial_count + 1, Prefix.objects.count())
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
||||||
|
def test_ui_edit_object(self):
|
||||||
|
form_data = {
|
||||||
|
'prefix': '10.0.9.0/24',
|
||||||
|
'site': self.sites[0].pk,
|
||||||
|
'status': PrefixStatusChoices.STATUS_RESERVED,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Attempt to edit an object without permission
|
||||||
|
request = {
|
||||||
|
'path': reverse('ipam:prefix_edit', kwargs={'pk': self.prefixes[0].pk}),
|
||||||
|
'data': form_data,
|
||||||
|
'follow': False, # Do not follow 302 redirects
|
||||||
|
}
|
||||||
|
response = self.client.post(**request)
|
||||||
|
self.assertHttpStatus(response, 403)
|
||||||
|
|
||||||
|
# Assign object permission
|
||||||
|
obj_perm = ObjectPermission(
|
||||||
|
model=ContentType.objects.get_for_model(Prefix),
|
||||||
|
attrs={
|
||||||
|
'site__name': 'Site 1',
|
||||||
|
},
|
||||||
|
can_view=True,
|
||||||
|
can_change=True
|
||||||
|
)
|
||||||
|
obj_perm.save()
|
||||||
|
obj_perm.users.add(self.user)
|
||||||
|
|
||||||
|
# Attempt to edit a non-permitted object
|
||||||
|
request = {
|
||||||
|
'path': reverse('ipam:prefix_edit', kwargs={'pk': self.prefixes[3].pk}),
|
||||||
|
'data': form_data,
|
||||||
|
'follow': True, # Follow 302 redirects
|
||||||
|
}
|
||||||
|
response = self.client.post(**request)
|
||||||
|
self.assertHttpStatus(response, 404)
|
||||||
|
|
||||||
|
# Edit a permitted object
|
||||||
|
request = {
|
||||||
|
'path': reverse('ipam:prefix_edit', kwargs={'pk': self.prefixes[0].pk}),
|
||||||
|
'data': form_data,
|
||||||
|
'follow': True, # Follow 302 redirects
|
||||||
|
}
|
||||||
|
response = self.client.post(**request)
|
||||||
|
self.assertHttpStatus(response, 200)
|
||||||
|
prefix = Prefix.objects.get(pk=self.prefixes[0].pk)
|
||||||
|
self.assertEqual(prefix.status, PrefixStatusChoices.STATUS_RESERVED)
|
||||||
|
|
||||||
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
||||||
|
def test_ui_delete_object(self):
|
||||||
|
form_data = {
|
||||||
|
'confirm': True
|
||||||
|
}
|
||||||
|
|
||||||
|
# Assign object permission
|
||||||
|
obj_perm = ObjectPermission(
|
||||||
|
model=ContentType.objects.get_for_model(Prefix),
|
||||||
|
attrs={
|
||||||
|
'site__name': 'Site 1',
|
||||||
|
},
|
||||||
|
can_view=True,
|
||||||
|
can_delete=True
|
||||||
|
)
|
||||||
|
obj_perm.save()
|
||||||
|
obj_perm.users.add(self.user)
|
||||||
|
|
||||||
|
# Delete permitted object
|
||||||
|
request = {
|
||||||
|
'path': reverse('ipam:prefix_delete', kwargs={'pk': self.prefixes[0].pk}),
|
||||||
|
'data': form_data,
|
||||||
|
'follow': True, # Follow 302 redirects
|
||||||
|
}
|
||||||
|
response = self.client.post(**request)
|
||||||
|
self.assertHttpStatus(response, 200)
|
||||||
|
self.assertFalse(Prefix.objects.filter(pk=self.prefixes[0].pk).exists())
|
||||||
|
|
||||||
|
# Attempt to delete non-permitted object
|
||||||
|
request = {
|
||||||
|
'path': reverse('ipam:prefix_delete', kwargs={'pk': self.prefixes[3].pk}),
|
||||||
|
'data': form_data,
|
||||||
|
'follow': True, # Follow 302 redirects
|
||||||
|
}
|
||||||
|
response = self.client.post(**request)
|
||||||
|
self.assertHttpStatus(response, 404)
|
||||||
|
self.assertTrue(Prefix.objects.filter(pk=self.prefixes[3].pk).exists())
|
||||||
|
Loading…
Reference in New Issue
Block a user