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

Add Remote Group Support to the RemoteUserAuth Backend and Middleware

fix incorrect assumption about when to run the group sync

Add documentation for new Settings

format to autopep8 compliance

add first set of basic testcases

format test to comply with pep8

rename SEPERATOR to SEPARATOR

remove accidentally carried over parameter
This commit is contained in:
Max Rink
2021-02-08 14:55:56 +01:00
committed by Maximilian Rink
parent f63dcb1f08
commit d5e5cdda23
5 changed files with 383 additions and 46 deletions

View File

@ -490,6 +490,14 @@ NetBox can be configured to support remote user authentication by inferring user
---
## REMOTE_AUTH_GROUP_SYNC_ENABLED
Default: `False`
NetBox can be configured to sync remote user groups by inferring user authentication from an HTTP header set by the HTTP reverse proxy (e.g. nginx or Apache). Set this to `True` to enable this functionality. (Local authentication will still take effect as a fallback.) (Requires `REMOTE_AUTH_ENABLED`.)
---
## REMOTE_AUTH_HEADER
Default: `'HTTP_REMOTE_USER'`
@ -498,6 +506,54 @@ When remote user authentication is in use, this is the name of the HTTP header w
---
## REMOTE_AUTH_GROUP_HEADER
Default: `'HTTP_REMOTE_USER_GROUP'`
When remote user authentication is in use, this is the name of the HTTP header which informs NetBox of the currently authenticated user. For example, to use the request header `X-Remote-User-Groups` it needs to be set to `HTTP_X_REMOTE_USER_GROUPS`. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )
---
## REMOTE_AUTH_SUPERUSER_GROUPS
Default: `[]` (Empty list)
The list of groups that promote an remote User to Superuser on Login. If group isn't present on next Login, the Role gets revoked. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )
---
## REMOTE_AUTH_SUPERUSERS
Default: `[]` (Empty list)
The list of users that get promoted to Superuser on Login. If user isn't present in list on next Login, the Role gets revoked. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )
---
## REMOTE_AUTH_STAFF_GROUPS
Default: `[]` (Empty list)
The list of groups that promote an remote User to Staff on Login. If group isn't present on next Login, the Role gets revoked. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )
---
## REMOTE_AUTH_STAFF_USERS
Default: `[]` (Empty list)
The list of users that get promoted to Staff on Login. If user isn't present in list on next Login, the Role gets revoked. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )
---
## REMOTE_AUTH_GROUP_SEPARATOR
Default: `|` (Pipe)
The Seperator upon which `REMOTE_AUTH_GROUP_HEADER` gets split into individual Groups. This needs to be coordinated with your authentication Proxy. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )
---
## RELEASE_CHECK_URL
Default: None (disabled)

View File

@ -2,14 +2,17 @@ import logging
from collections import defaultdict
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend, RemoteUserBackend as _RemoteUserBackend
from django.contrib.auth.models import Group
from django.contrib.auth.models import Group, AnonymousUser
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q
from users.models import ObjectPermission
from utilities.permissions import permission_is_exempt, resolve_permission, resolve_permission_ct
UserModel = get_user_model()
class ObjectPermissionMixin():
@ -101,27 +104,130 @@ class RemoteUserBackend(_RemoteUserBackend):
def create_unknown_user(self):
return settings.REMOTE_AUTH_AUTO_CREATE_USER
def configure_user(self, request, user):
def configure_groups(self, user, remote_groups):
logger = logging.getLogger('netbox.authentication.RemoteUserBackend')
# Assign default groups to the user
group_list = []
for name in remote_groups:
try:
group_list.append(Group.objects.get(name=name))
except Group.DoesNotExist:
logging.error(
f"Could not assign group {name} to remotely-authenticated user {user}: Group not found")
if group_list:
user.groups.set(group_list)
logger.debug(
f"Assigned groups to remotely-authenticated user {user}: {group_list}")
else:
user.groups.clear()
logger.debug(f"Stripping user {user} from Groups")
user.is_superuser = self._is_superuser(user)
logger.debug(f"User {user} is Superuser: {user.is_superuser}")
logger.debug(
f"User {user} should be Superuser: {self._is_superuser(user)}")
user.is_staff = self._is_staff(user)
logger.debug(f"User {user} is Staff: {user.is_staff}")
logger.debug(f"User {user} should be Staff: {self._is_staff(user)}")
user.save()
return user
def authenticate(self, request, remote_user, remote_groups=None):
"""
The username passed as ``remote_user`` is considered trusted. Return
the ``User`` object with the given username. Create a new ``User``
object if ``create_unknown_user`` is ``True``.
Return None if ``create_unknown_user`` is ``False`` and a ``User``
object with the given username is not found in the database.
"""
logger = logging.getLogger('netbox.authentication.RemoteUserBackend')
logger.debug(
f"trying to authenticate {remote_user} with groups {remote_groups}")
if not remote_user:
return
user = None
username = self.clean_username(remote_user)
# Note that this could be accomplished in one try-except clause, but
# instead we use get_or_create when creating unknown users since it has
# built-in safeguards for multiple threads.
if self.create_unknown_user:
user, created = UserModel._default_manager.get_or_create(**{
UserModel.USERNAME_FIELD: username
})
if created:
user = self.configure_user(request, user)
else:
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
pass
if self.user_can_authenticate(user):
if settings.REMOTE_AUTH_GROUP_SYNC_ENABLED:
if user is not None and not isinstance(user, AnonymousUser):
return self.configure_groups(user, remote_groups)
else:
return user
else:
return None
def _is_superuser(self, user):
logger = logging.getLogger('netbox.authentication.RemoteUserBackend')
superuser_groups = settings.REMOTE_AUTH_SUPERUSER_GROUPS
logger.debug(f"Superuser Groups: {superuser_groups}")
superusers = settings.REMOTE_AUTH_SUPERUSERS
logger.debug(f"Superuser Users: {superusers}")
user_groups = set()
for g in user.groups.all():
user_groups.add(g.name)
logger.debug(f"User {user.username} is in Groups:{user_groups}")
result = user.username in superusers or (
set(user_groups) & set(superuser_groups))
logger.debug(f"User {user.username} in Superuser Users :{result}")
return bool(result)
def _is_staff(self, user):
logger = logging.getLogger('netbox.authentication.RemoteUserBackend')
staff_groups = settings.REMOTE_AUTH_STAFF_GROUPS
logger.debug(f"Superuser Groups: {staff_groups}")
staff_users = settings.REMOTE_AUTH_STAFF_USERS
logger.debug(f"Staff Users :{staff_users}")
user_groups = set()
for g in user.groups.all():
user_groups.add(g.name)
logger.debug(f"User {user.username} is in Groups:{user_groups}")
result = user.username in staff_users or (
set(user_groups) & set(staff_groups))
logger.debug(f"User {user.username} in Staff Users :{result}")
return bool(result)
def configure_user(self, request, user):
logger = logging.getLogger('netbox.authentication.RemoteUserBackend')
if not settings.REMOTE_AUTH_GROUP_SYNC_ENABLED:
# Assign default groups to the user
group_list = []
for name in settings.REMOTE_AUTH_DEFAULT_GROUPS:
try:
group_list.append(Group.objects.get(name=name))
except Group.DoesNotExist:
logging.error(f"Could not assign group {name} to remotely-authenticated user {user}: Group not found")
logging.error(
f"Could not assign group {name} to remotely-authenticated user {user}: Group not found")
if group_list:
user.groups.add(*group_list)
logger.debug(f"Assigned groups to remotely-authenticated user {user}: {group_list}")
logger.debug(
f"Assigned groups to remotely-authenticated user {user}: {group_list}")
# Assign default object permissions to the user
permissions_list = []
for permission_name, constraints in settings.REMOTE_AUTH_DEFAULT_PERMISSIONS.items():
try:
object_type, action = resolve_permission_ct(permission_name)
object_type, action = resolve_permission_ct(
permission_name)
# TODO: Merge multiple actions into a single ObjectPermission per content type
obj_perm = ObjectPermission(actions=[action], constraints=constraints)
obj_perm = ObjectPermission(
actions=[action], constraints=constraints)
obj_perm.save()
obj_perm.users.add(user)
obj_perm.object_types.add(object_type)
@ -132,7 +238,11 @@ class RemoteUserBackend(_RemoteUserBackend):
"<app>.<action>_<model>. (Example: dcim.add_site)"
)
if permissions_list:
logger.debug(f"Assigned permissions to remotely-authenticated user {user}: {permissions_list}")
logger.debug(
f"Assigned permissions to remotely-authenticated user {user}: {permissions_list}")
else:
logger.debug(
f"Skipped initial assignment of permissions and groups to remotely-authenticated user {user} as Group sync is enabled")
return user

View File

@ -1,8 +1,11 @@
import uuid
from urllib import parse
import logging
from django.conf import settings
from django.contrib.auth.middleware import RemoteUserMiddleware as RemoteUserMiddleware_
from django.contrib import auth
from django.core.exceptions import ImproperlyConfigured
from django.db import ProgrammingError
from django.http import Http404, HttpResponseRedirect
from django.urls import reverse
@ -16,6 +19,7 @@ class LoginRequiredMiddleware(object):
"""
If LOGIN_REQUIRED is True, redirect all non-authenticated users to the login page.
"""
def __init__(self, get_response):
self.get_response = get_response
@ -49,12 +53,65 @@ class RemoteUserMiddleware(RemoteUserMiddleware_):
return settings.REMOTE_AUTH_HEADER
def process_request(self, request):
logger = logging.getLogger(
'netbox.authentication.RemoteUserMiddleware')
# Bypass middleware if remote authentication is not enabled
if not settings.REMOTE_AUTH_ENABLED:
return
# AuthenticationMiddleware is required so that request.user exists.
if not hasattr(request, 'user'):
raise ImproperlyConfigured(
"The Django remote user auth middleware requires the"
" authentication middleware to be installed. Edit your"
" MIDDLEWARE setting to insert"
" 'django.contrib.auth.middleware.AuthenticationMiddleware'"
" before the RemoteUserMiddleware class.")
try:
username = request.META[self.header]
except KeyError:
# If specified header doesn't exist then remove any existing
# authenticated remote-user, or return (leaving request.user set to
# AnonymousUser by the AuthenticationMiddleware).
if self.force_logout_if_no_header and request.user.is_authenticated:
self._remove_invalid_user(request)
return
# If the user is already authenticated and that user is the user we are
# getting passed in the headers, then the correct user is already
# persisted in the session and we don't need to continue.
if request.user.is_authenticated:
if request.user.get_username() == self.clean_username(username, request):
return
else:
# An authenticated user is associated with the request, but
# it does not match the authorized user in the header.
self._remove_invalid_user(request)
return super().process_request(request)
# We are seeing this user for the first time in this session, attempt
# to authenticate the user.
if settings.REMOTE_AUTH_GROUP_SYNC_ENABLED:
logger.debug("Trying to sync Groups")
user = auth.authenticate(
request, remote_user=username, remote_groups=self._get_groups(request))
else:
user = auth.authenticate(request, remote_user=username)
if user:
# User is valid. Set request.user and persist user in the session
# by logging the user in.
request.user = user
auth.login(request, user)
def _get_groups(self, request):
logger = logging.getLogger(
'netbox.authentication.RemoteUserMiddleware')
groups_string = request.META.get(
settings.REMOTE_AUTH_GROUP_HEADER, None)
if groups_string:
groups = groups_string.split(settings.REMOTE_AUTH_GROUP_SEPARATOR)
else:
groups = []
logger.debug(f"Groups are {groups}")
return groups
class ObjectChangeMiddleware(object):
@ -71,6 +128,7 @@ class ObjectChangeMiddleware(object):
have been created. Conversely, deletions are acted upon immediately, so that the serialized representation of the
object is recorded before it (and any related objects) are actually deleted from the database.
"""
def __init__(self, get_response):
self.get_response = get_response
@ -90,6 +148,7 @@ class APIVersionMiddleware(object):
"""
If the request is for an API endpoint, include the API version as a response header.
"""
def __init__(self, get_response):
self.get_response = get_response
@ -105,6 +164,7 @@ class ExceptionHandlingMiddleware(object):
Intercept certain exceptions which are likely indicative of installation issues and provide helpful instructions
to the user.
"""
def __init__(self, get_response):
self.get_response = get_response

View File

@ -120,6 +120,13 @@ REMOTE_AUTH_DEFAULT_GROUPS = getattr(configuration, 'REMOTE_AUTH_DEFAULT_GROUPS'
REMOTE_AUTH_DEFAULT_PERMISSIONS = getattr(configuration, 'REMOTE_AUTH_DEFAULT_PERMISSIONS', {})
REMOTE_AUTH_ENABLED = getattr(configuration, 'REMOTE_AUTH_ENABLED', False)
REMOTE_AUTH_HEADER = getattr(configuration, 'REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER')
REMOTE_AUTH_GROUP_HEADER = getattr(configuration, 'REMOTE_AUTH_GROUP_HEADER', 'HTTP_REMOTE_USER_GROUP')
REMOTE_AUTH_GROUP_SYNC_ENABLED = getattr(configuration, 'REMOTE_AUTH_GROUP_SYNC_ENABLED', False)
REMOTE_AUTH_SUPERUSER_GROUPS = getattr(configuration, 'REMOTE_AUTH_SUPERUSER_GROUPS', [])
REMOTE_AUTH_SUPERUSERS = getattr(configuration, 'REMOTE_AUTH_SUPERUSERS', [])
REMOTE_AUTH_STAFF_GROUPS = getattr(configuration, 'REMOTE_AUTH_STAFF_GROUPS', [])
REMOTE_AUTH_STAFF_USERS = getattr(configuration, 'REMOTE_AUTH_STAFF_USERS', [])
REMOTE_AUTH_GROUP_SEPARATOR = getattr(configuration, 'REMOTE_AUTH_GROUP_SEPARATOR', '|')
RELEASE_CHECK_URL = getattr(configuration, 'RELEASE_CHECK_URL', None)
REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 'reports')).rstrip('/')
RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300)

View File

@ -58,7 +58,8 @@ class ExternalAuthenticationTestCase(TestCase):
response = self.client.get(reverse('home'), follow=True, **headers)
self.assertEqual(response.status_code, 200)
self.assertEqual(int(self.client.session.get('_auth_user_id')), self.user.pk, msg='Authentication failed')
self.assertEqual(int(self.client.session.get(
'_auth_user_id')), self.user.pk, msg='Authentication failed')
@override_settings(
REMOTE_AUTH_ENABLED=True,
@ -78,7 +79,8 @@ class ExternalAuthenticationTestCase(TestCase):
response = self.client.get(reverse('home'), follow=True, **headers)
self.assertEqual(response.status_code, 200)
self.assertEqual(int(self.client.session.get('_auth_user_id')), self.user.pk, msg='Authentication failed')
self.assertEqual(int(self.client.session.get(
'_auth_user_id')), self.user.pk, msg='Authentication failed')
@override_settings(
REMOTE_AUTH_ENABLED=True,
@ -102,7 +104,8 @@ class ExternalAuthenticationTestCase(TestCase):
# Local user should have been automatically created
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')
@override_settings(
REMOTE_AUTH_ENABLED=True,
@ -121,7 +124,8 @@ class ExternalAuthenticationTestCase(TestCase):
self.assertTrue(settings.REMOTE_AUTH_ENABLED)
self.assertTrue(settings.REMOTE_AUTH_AUTO_CREATE_USER)
self.assertEqual(settings.REMOTE_AUTH_HEADER, 'HTTP_REMOTE_USER')
self.assertEqual(settings.REMOTE_AUTH_DEFAULT_GROUPS, ['Group 1', 'Group 2'])
self.assertEqual(settings.REMOTE_AUTH_DEFAULT_GROUPS,
['Group 1', 'Group 2'])
# Create required groups
groups = (
@ -135,7 +139,8 @@ class ExternalAuthenticationTestCase(TestCase):
self.assertEqual(response.status_code, 200)
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.assertListEqual(
[groups[0], groups[1]],
list(new_user.groups.all())
@ -144,7 +149,8 @@ class ExternalAuthenticationTestCase(TestCase):
@override_settings(
REMOTE_AUTH_ENABLED=True,
REMOTE_AUTH_AUTO_CREATE_USER=True,
REMOTE_AUTH_DEFAULT_PERMISSIONS={'dcim.add_site': None, 'dcim.change_site': None},
REMOTE_AUTH_DEFAULT_PERMISSIONS={
'dcim.add_site': None, 'dcim.change_site': None},
LOGIN_REQUIRED=True
)
def test_remote_auth_default_permissions(self):
@ -158,14 +164,102 @@ class ExternalAuthenticationTestCase(TestCase):
self.assertTrue(settings.REMOTE_AUTH_ENABLED)
self.assertTrue(settings.REMOTE_AUTH_AUTO_CREATE_USER)
self.assertEqual(settings.REMOTE_AUTH_HEADER, 'HTTP_REMOTE_USER')
self.assertEqual(settings.REMOTE_AUTH_DEFAULT_PERMISSIONS, {'dcim.add_site': None, 'dcim.change_site': None})
self.assertEqual(settings.REMOTE_AUTH_DEFAULT_PERMISSIONS, {
'dcim.add_site': None, 'dcim.change_site': None})
response = self.client.get(reverse('home'), follow=True, **headers)
self.assertEqual(response.status_code, 200)
new_user = User.objects.get(username='remoteuser2')
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.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']))
@override_settings(
REMOTE_AUTH_ENABLED=True,
REMOTE_AUTH_AUTO_CREATE_USER=True,
REMOTE_AUTH_GROUP_SYNC_ENABLED=True,
LOGIN_REQUIRED=True
)
def test_remote_auth_remote_groups_default(self):
"""
Test enabling remote authentication with group sync enabled with the default configuration.
"""
headers = {
'HTTP_REMOTE_USER': 'remoteuser2',
'HTTP_REMOTE_USER_GROUP': 'Group 1|Group 2',
}
self.assertTrue(settings.REMOTE_AUTH_ENABLED)
self.assertTrue(settings.REMOTE_AUTH_AUTO_CREATE_USER)
self.assertTrue(settings.REMOTE_AUTH_GROUP_SYNC_ENABLED)
self.assertEqual(settings.REMOTE_AUTH_HEADER, 'HTTP_REMOTE_USER')
self.assertEqual(settings.REMOTE_AUTH_GROUP_HEADER,
'HTTP_REMOTE_USER_GROUP')
self.assertEqual(settings.REMOTE_AUTH_GROUP_SEPARATOR, '|')
# Create required groups
groups = (
Group(name='Group 1'),
Group(name='Group 2'),
Group(name='Group 3'),
)
Group.objects.bulk_create(groups)
response = self.client.get(reverse('home'), follow=True, **headers)
self.assertEqual(response.status_code, 200)
new_user = User.objects.get(username='remoteuser2')
self.assertEqual(int(self.client.session.get(
'_auth_user_id')), new_user.pk, msg='Authentication failed')
self.assertListEqual(
[groups[0], groups[1]],
list(new_user.groups.all())
)
@override_settings(
REMOTE_AUTH_ENABLED=True,
REMOTE_AUTH_AUTO_CREATE_USER=True,
REMOTE_AUTH_GROUP_SYNC_ENABLED=True,
REMOTE_AUTH_HEADER='HTTP_FOO',
REMOTE_AUTH_GROUP_HEADER='HTTP_BAR',
LOGIN_REQUIRED=True
)
def test_remote_auth_remote_groups_custom_header(self):
"""
Test enabling remote authentication with group sync enabled with the default configuration.
"""
headers = {
'HTTP_FOO': 'remoteuser2',
'HTTP_BAR': 'Group 1|Group 2',
}
self.assertTrue(settings.REMOTE_AUTH_ENABLED)
self.assertTrue(settings.REMOTE_AUTH_AUTO_CREATE_USER)
self.assertTrue(settings.REMOTE_AUTH_GROUP_SYNC_ENABLED)
self.assertEqual(settings.REMOTE_AUTH_HEADER, 'HTTP_FOO')
self.assertEqual(settings.REMOTE_AUTH_GROUP_HEADER, 'HTTP_BAR')
self.assertEqual(settings.REMOTE_AUTH_GROUP_SEPARATOR, '|')
# Create required groups
groups = (
Group(name='Group 1'),
Group(name='Group 2'),
Group(name='Group 3'),
)
Group.objects.bulk_create(groups)
response = self.client.get(reverse('home'), follow=True, **headers)
self.assertEqual(response.status_code, 200)
new_user = User.objects.get(username='remoteuser2')
self.assertEqual(int(self.client.session.get(
'_auth_user_id')), new_user.pk, msg='Authentication failed')
self.assertListEqual(
[groups[0], groups[1]],
list(new_user.groups.all())
)
class ObjectPermissionAPIViewTestCase(TestCase):
@ -206,7 +300,8 @@ class ObjectPermissionAPIViewTestCase(TestCase):
def test_get_object(self):
# Attempt to retrieve object without permission
url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefixes[0].pk})
url = reverse('ipam-api:prefix-detail',
kwargs={'pk': self.prefixes[0].pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.status_code, 403)
@ -221,12 +316,14 @@ class ObjectPermissionAPIViewTestCase(TestCase):
obj_perm.object_types.add(ContentType.objects.get_for_model(Prefix))
# Retrieve permitted object
url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefixes[0].pk})
url = reverse('ipam-api:prefix-detail',
kwargs={'pk': self.prefixes[0].pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.status_code, 200)
# Attempt to retrieve non-permitted object
url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefixes[3].pk})
url = reverse('ipam-api:prefix-detail',
kwargs={'pk': self.prefixes[3].pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.status_code, 404)
@ -292,7 +389,8 @@ class ObjectPermissionAPIViewTestCase(TestCase):
# Attempt to edit an object without permission
data = {'site': self.sites[0].pk}
url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefixes[0].pk})
url = reverse('ipam-api:prefix-detail',
kwargs={'pk': self.prefixes[0].pk})
response = self.client.patch(url, data, format='json', **self.header)
self.assertEqual(response.status_code, 403)
@ -308,19 +406,22 @@ class ObjectPermissionAPIViewTestCase(TestCase):
# Attempt to edit a non-permitted object
data = {'site': self.sites[0].pk}
url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefixes[3].pk})
url = reverse('ipam-api:prefix-detail',
kwargs={'pk': self.prefixes[3].pk})
response = self.client.patch(url, data, format='json', **self.header)
self.assertEqual(response.status_code, 404)
# Edit a permitted object
data['status'] = 'reserved'
url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefixes[0].pk})
url = reverse('ipam-api:prefix-detail',
kwargs={'pk': self.prefixes[0].pk})
response = self.client.patch(url, data, format='json', **self.header)
self.assertEqual(response.status_code, 200)
# Attempt to modify a permitted object to a non-permitted object
data['site'] = self.sites[1].pk
url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefixes[0].pk})
url = reverse('ipam-api:prefix-detail',
kwargs={'pk': self.prefixes[0].pk})
response = self.client.patch(url, data, format='json', **self.header)
self.assertEqual(response.status_code, 403)
@ -328,7 +429,8 @@ class ObjectPermissionAPIViewTestCase(TestCase):
def test_delete_object(self):
# Attempt to delete an object without permission
url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefixes[0].pk})
url = reverse('ipam-api:prefix-detail',
kwargs={'pk': self.prefixes[0].pk})
response = self.client.delete(url, format='json', **self.header)
self.assertEqual(response.status_code, 403)
@ -343,11 +445,13 @@ class ObjectPermissionAPIViewTestCase(TestCase):
obj_perm.object_types.add(ContentType.objects.get_for_model(Prefix))
# Attempt to delete a non-permitted object
url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefixes[3].pk})
url = reverse('ipam-api:prefix-detail',
kwargs={'pk': self.prefixes[3].pk})
response = self.client.delete(url, format='json', **self.header)
self.assertEqual(response.status_code, 404)
# Delete a permitted object
url = reverse('ipam-api:prefix-detail', kwargs={'pk': self.prefixes[0].pk})
url = reverse('ipam-api:prefix-detail',
kwargs={'pk': self.prefixes[0].pk})
response = self.client.delete(url, format='json', **self.header)
self.assertEqual(response.status_code, 204)