def test_get_object(self): """ GET a single object as an authenticated user with permission to view the object. """ self.assertGreaterEqual( self._get_queryset().count(), 2, f"Test requires the creation of at least two {self.model} instances", ) instance1, instance2 = self._get_queryset()[:2] # Add object-level permission obj_perm = ObjectPermission( name="Test permission", constraints={"pk": instance1.pk}, actions=["view"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) # Try GET to permitted object url = self._get_detail_url(instance1) self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_200_OK) # Try GET to non-permitted object url = self._get_detail_url(instance2) self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_404_NOT_FOUND)
def test_bulk_create_objects(self): """ POST a set of objects in a single request. """ # Add object-level permission obj_perm = ObjectPermission(name="Test permission", actions=["add"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) initial_count = self._get_queryset().count() response = self.client.post(self._get_list_url(), self.create_data, format="json", **self.header) self.assertHttpStatus(response, status.HTTP_201_CREATED) self.assertEqual(len(response.data), len(self.create_data)) self.assertEqual(self._get_queryset().count(), initial_count + len(self.create_data)) for i, obj in enumerate(response.data): for field in self.create_data[i]: if field not in self.validation_excluded_fields: self.assertIn( field, obj, f"Bulk create field '{field}' missing from object {i} in response", ) for i, obj in enumerate(response.data): self.assertInstanceEqual( self._get_queryset().get(pk=obj["id"]), self.create_data[i], exclude=self.validation_excluded_fields, api=True, )
def test_delete_object(self): """ DELETE a single object identified by its primary key. """ instance = self._get_queryset().first() url = self._get_detail_url(instance) # Add object-level permission obj_perm = ObjectPermission(name="Test permission", actions=["delete"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) response = self.client.delete(url, **self.header) self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertFalse( self._get_queryset().filter(pk=instance.pk).exists()) # Verify ObjectChange creation if hasattr(self.model, "to_objectchange"): objectchanges = ObjectChange.objects.filter( changed_object_type=ContentType.objects.get_for_model( instance), changed_object_id=instance.pk) self.assertEqual(len(objectchanges), 1) self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_DELETE)
def test_update_object(self): """ PATCH a single object identified by its ID. """ instance = self._get_queryset().first() url = self._get_detail_url(instance) update_data = self.update_data or getattr(self, "create_data")[0] # Add object-level permission obj_perm = ObjectPermission(name="Test permission", actions=["change"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) response = self.client.patch(url, update_data, format="json", **self.header) self.assertHttpStatus(response, status.HTTP_200_OK) instance.refresh_from_db() self.assertInstanceEqual(instance, update_data, exclude=self.validation_excluded_fields, api=True)
def test_delete_object_with_constrained_permission(self): instance1, instance2 = self._get_queryset().all()[:2] # Assign object-level permission obj_perm = ObjectPermission( name="Test permission", constraints={"pk": instance1.pk}, actions=["delete"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # Try GET with a permitted object self.assertHttpStatus(self.client.get(self._get_url("delete", instance1)), 200) # Try GET with a non-permitted object self.assertHttpStatus(self.client.get(self._get_url("delete", instance2)), 404) # Try to delete a permitted object request = { "path": self._get_url("delete", instance1), "data": post_data({"confirm": True}), } self.assertHttpStatus(self.client.post(**request), 302) with self.assertRaises(ObjectDoesNotExist): self._get_queryset().get(pk=instance1.pk) # Try to delete a non-permitted object request = { "path": self._get_url("delete", instance2), "data": post_data({"confirm": True}), } self.assertHttpStatus(self.client.post(**request), 404) self.assertTrue(self._get_queryset().filter(pk=instance2.pk).exists())
def test_create_multiple_objects_with_permission(self): initial_count = self._get_queryset().count() request = { "path": self._get_url("add"), "data": post_data(self.bulk_create_data), } # Assign non-constrained permission obj_perm = ObjectPermission( name="Test permission", actions=["add"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) # Bulk create objects response = self.client.post(**request) self.assertHttpStatus(response, 302) self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count()) matching_count = 0 for instance in self._get_queryset().all(): try: self.assertInstanceEqual(instance, self.bulk_create_data) matching_count += 1 except AssertionError: pass self.assertEqual(matching_count, self.bulk_create_count)
def test_list_objects_with_constrained_permission(self): instance1, instance2 = self._get_queryset().all()[:2] # Add object-level permission obj_perm = ObjectPermission( name="Test permission", constraints={"pk": instance1.pk}, actions=["view"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) # Try GET with object-level permission response = self.client.get(self._get_url("list")) self.assertHttpStatus(response, 200) content = extract_page_body( response.content.decode(response.charset)) # TODO: it'd make test failures more readable if we strip the page headers/footers from the content if hasattr(self.model, "name"): self.assertIn(instance1.name, content, msg=content) self.assertNotIn(instance2.name, content, msg=content) elif hasattr(self.model, "get_absolute_url"): self.assertIn(instance1.get_absolute_url(), content, msg=content) self.assertNotIn(instance2.get_absolute_url(), content, msg=content)
def test_bulk_edit_objects_with_permission(self): pk_list = list(self._get_queryset().values_list("pk", flat=True)[:3]) data = { "pk": pk_list, "_apply": True, # Form button } # Append the form data to the request data.update(post_data(self.bulk_edit_data)) # Assign model-level permission obj_perm = ObjectPermission(name="Test permission", actions=["change"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) # Try POST with model-level permission self.assertHttpStatus( self.client.post(self._get_url("bulk_edit"), data), 302) for i, instance in enumerate( self._get_queryset().filter(pk__in=pk_list)): self.assertInstanceEqual(instance, self.bulk_edit_data)
def test_bulk_import_objects_with_constrained_permission(self): initial_count = self._get_queryset().count() data = { "csv": self._get_csv_data(), } # Assign constrained permission obj_perm = ObjectPermission( name="Test permission", constraints={"pk": str(uuid.uuid4()) }, # Dummy permission to deny all actions=["add"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) # Attempt to import non-permitted objects self.assertHttpStatus( self.client.post(self._get_url("import"), data), 200) self.assertEqual(self._get_queryset().count(), initial_count) # Update permission constraints obj_perm.constraints = { "pk__isnull": False } # Dummy permission to allow all obj_perm.save() # Import permitted objects self.assertHttpStatus( self.client.post(self._get_url("import"), data), 200) self.assertEqual(self._get_queryset().count(), initial_count + len(self.csv_data) - 1)
def test_bulk_rename_objects_with_constrained_permission(self): objects = list(self._get_queryset().all()[:3]) pk_list = [obj.pk for obj in objects] data = { "pk": pk_list, "_apply": True, # Form button } data.update(self.rename_data) # Assign constrained permission obj_perm = ObjectPermission( name="Test permission", constraints={"name__regex": "[^X]$"}, actions=["change"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) # Attempt to bulk edit permitted objects into a non-permitted state response = self.client.post(self._get_url("bulk_rename"), data) self.assertHttpStatus(response, 200) # Update permission constraints obj_perm.constraints = {"pk__gt": 0} obj_perm.save() # Bulk rename permitted objects self.assertHttpStatus( self.client.post(self._get_url("bulk_rename"), data), 302) for i, instance in enumerate( self._get_queryset().filter(pk__in=pk_list)): self.assertEqual(instance.name, f"{objects[i].name}X")
def test_list_objects(self): """ GET a list of objects as an authenticated user with permission to view the objects. """ self.assertGreaterEqual( self._get_queryset().count(), 3, f"Test requires the creation of at least three {self.model} instances", ) instance1, instance2 = self._get_queryset()[:2] # Add object-level permission obj_perm = ObjectPermission( name="Test permission", constraints={"pk__in": [instance1.pk, instance2.pk]}, actions=["view"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) # Try GET to permitted objects response = self.client.get(self._get_list_url(), **self.header) self.assertHttpStatus(response, status.HTTP_200_OK) self.assertEqual(len(response.data["results"]), 2)
def test_edit_object_with_constrained_permission(self): instance1, instance2 = self._get_queryset().all()[:2] # Assign constrained permission obj_perm = ObjectPermission( name="Test permission", constraints={"pk": instance1.pk}, actions=["change"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # Try GET with a permitted object self.assertHttpStatus(self.client.get(self._get_url("edit", instance1)), 200) # Try GET with a non-permitted object self.assertHttpStatus(self.client.get(self._get_url("edit", instance2)), 404) # Try to edit a permitted object request = { "path": self._get_url("edit", instance1), "data": post_data(self.form_data), } self.assertHttpStatus(self.client.post(**request), 302) self.assertInstanceEqual(self._get_queryset().get(pk=instance1.pk), self.form_data) # Try to edit a non-permitted object request = { "path": self._get_url("edit", instance2), "data": post_data(self.form_data), } self.assertHttpStatus(self.client.post(**request), 404)
def test_create_object(self): """ POST a single object with permission. """ # Add object-level permission obj_perm = ObjectPermission(name="Test permission", actions=["add"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) initial_count = self._get_queryset().count() response = self.client.post(self._get_list_url(), self.create_data[0], format="json", **self.header) self.assertHttpStatus(response, status.HTTP_201_CREATED) self.assertEqual(self._get_queryset().count(), initial_count + 1) self.assertInstanceEqual( self._get_queryset().get(pk=response.data["id"]), self.create_data[0], exclude=self.validation_excluded_fields, api=True, )
def test_create_object_with_permission(self): initial_count = self._get_queryset().count() # Assign unconstrained permission obj_perm = ObjectPermission(name="Test permission", actions=["add"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) # Try GET with model-level permission self.assertHttpStatus(self.client.get(self._get_url("add")), 200) # Try POST with model-level permission request = { "path": self._get_url("add"), "data": post_data(self.form_data), } self.assertHttpStatus(self.client.post(**request), 302) self.assertEqual(initial_count + 1, self._get_queryset().count()) if hasattr(self.model, "last_updated"): self.assertInstanceEqual( self._get_queryset().order_by("last_updated").last(), self.form_data) else: self.assertInstanceEqual(self._get_queryset().last(), self.form_data)
def assign_permissions_to_user(user, permissions=None): """ Assign a specified user a given set of permissions. :param user: The user to assign the permissions :param permissions: A dictionary of permissions, with the permission name <app_label>.<action>_<model> as the key and constraints as values """ if permissions is None: permissions = {} permissions_list = [] for permission_name, constraints in permissions.items(): try: object_type, action = resolve_permission_ct(permission_name) # TODO: Merge multiple actions into a single ObjectPermission per content type obj_perm = ObjectPermission(name=permission_name, actions=[action], constraints=constraints) obj_perm.save() obj_perm.users.add(user) obj_perm.object_types.add(object_type) permissions_list.append(permission_name) except ValueError: logging.error( f"Invalid permission name: '{permission_name}'. Permissions must be in the form " "<app>.<action>_<model>. (Example: dcim.add_site)") if permissions_list: logger.debug( f"Assigned permissions to remotely-authenticated user {user}: {permissions_list}" )
def test_bulk_delete_objects_with_constrained_permission(self): initial_count = self._get_queryset().count() pk_list = self._get_queryset().values_list("pk", flat=True) data = { "pk": pk_list, "confirm": True, "_confirm": True, # Form button } # Assign constrained permission obj_perm = ObjectPermission( name="Test permission", constraints={"pk": str(uuid.uuid4())}, # Dummy permission to deny all actions=["delete"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # Attempt to bulk delete non-permitted objects self.assertHttpStatus(self.client.post(self._get_url("bulk_delete"), data), 302) self.assertEqual(self._get_queryset().count(), initial_count) # Update permission constraints obj_perm.constraints = {"pk__isnull": False} # Dummy permission to allow all obj_perm.save() # Bulk delete permitted objects self.assertHttpStatus(self.client.post(self._get_url("bulk_delete"), data), 302) self.assertEqual(self._get_queryset().count(), 0)
def test_bulk_update_objects(self): """ PATCH a set of objects in a single request. """ if self.bulk_update_data is None: self.skipTest("Bulk update data not set") # Add object-level permission obj_perm = ObjectPermission(name="Test permission", actions=["change"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) id_list = self._get_queryset().values_list("id", flat=True)[:3] self.assertEqual(len(id_list), 3, "Insufficient number of objects to test bulk update") data = [{"id": id, **self.bulk_update_data} for id in id_list] response = self.client.patch(self._get_list_url(), data, format="json", **self.header) self.assertHttpStatus(response, status.HTTP_200_OK) for i, obj in enumerate(response.data): for field in self.bulk_update_data: self.assertIn( field, obj, f"Bulk update field '{field}' missing from object {i} in response", ) for instance in self._get_queryset().filter(pk__in=id_list): self.assertInstanceEqual( instance, self.bulk_update_data, exclude=self.validation_excluded_fields, api=True, )
def test_get_object_with_permission(self): instance = self._get_queryset().first() # Add model-level permission obj_perm = ObjectPermission(name="Test permission", actions=["view"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) # Try GET with model-level permission response = self.client.get(instance.get_absolute_url()) self.assertHttpStatus(response, 200) response_body = extract_page_body( response.content.decode(response.charset)) # The object's display name or string representation should appear in the response self.assertIn(getattr(instance, "display", str(instance)), response_body, msg=response_body) # If any Relationships are defined, they should appear in the response if self.relationships: for relationship in self.relationships: content_type = ContentType.objects.get_for_model(instance) if content_type == relationship.source_type: self.assertIn( relationship.get_label( RelationshipSideChoices.SIDE_SOURCE), response_body, msg=response_body, ) if content_type == relationship.destination_type: self.assertIn( relationship.get_label( RelationshipSideChoices.SIDE_DESTINATION), response_body, msg=response_body, ) # If any Custom Fields are defined, they should appear in the response if self.custom_fields: for custom_field in self.custom_fields: self.assertIn(str(custom_field), response_body, msg=response_body) if custom_field.type == CustomFieldTypeChoices.TYPE_MULTISELECT: for value in instance.cf.get(custom_field.name): self.assertIn(str(value), response_body, msg=response_body) else: self.assertIn(str( instance.cf.get(custom_field.name) or ""), response_body, msg=response_body)
def add_permissions(self, *names): """ Assign a set of permissions to the test user. Accepts permission names in the form <app>.<action>_<model>. """ for name in names: ct, action = resolve_permission_ct(name) obj_perm = ObjectPermission(name=name, actions=[action]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ct)
def test_get_object_with_permission(self): instance = self._get_queryset().first() # Add model-level permission obj_perm = ObjectPermission(name="Test permission", actions=["view"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # Try GET with model-level permission self.assertHttpStatus(self.client.get(instance.get_absolute_url()), 200)
def test_delete_object(self): """ DELETE a single object identified by its primary key. """ instance = self._get_queryset().first() url = self._get_detail_url(instance) # Add object-level permission obj_perm = ObjectPermission(name="Test permission", actions=["delete"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) response = self.client.delete(url, **self.header) self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertFalse(self._get_queryset().filter(pk=instance.pk).exists())
def test_list_objects_with_permission(self): # Add model-level permission obj_perm = ObjectPermission(name="Test permission", actions=["view"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # Try GET with model-level permission self.assertHttpStatus(self.client.get(self._get_url("list")), 200) # Built-in CSV export if hasattr(self.model, "csv_headers"): response = self.client.get("{}?export".format(self._get_url("list"))) self.assertHttpStatus(response, 200) self.assertEqual(response.get("Content-Type"), "text/csv")
def test_bulk_delete_objects(self): """ DELETE a set of objects in a single request. """ # Add object-level permission obj_perm = ObjectPermission(name="Test permission", actions=["delete"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) id_list = self._get_queryset().values_list("id", flat=True) data = [{"id": id} for id in id_list] response = self.client.delete(self._get_list_url(), data, format="json", **self.header) self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertEqual(self._get_queryset().count(), 0)
def test_create_object_with_constrained_permission(self): initial_count = self._get_queryset().count() # Assign constrained permission obj_perm = ObjectPermission( name="Test permission", constraints={"pk": str(uuid.uuid4()) }, # Dummy permission to deny all actions=["add"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) # Try GET with object-level permission self.assertHttpStatus(self.client.get(self._get_url("add")), 200) # Try to create an object (not permitted) request = { "path": self._get_url("add"), "data": post_data(self.form_data), } self.assertHttpStatus(self.client.post(**request), 200) self.assertEqual(initial_count, self._get_queryset().count() ) # Check that no object was created # Update the ObjectPermission to allow creation obj_perm.constraints = {"pk__isnull": False} obj_perm.save() # Try to create an object (permitted) request = { "path": self._get_url("add"), "data": post_data(self.form_data), } self.assertHttpStatus(self.client.post(**request), 302) self.assertEqual(initial_count + 1, self._get_queryset().count()) if hasattr(self.model, "last_updated"): self.assertInstanceEqual( self._get_queryset().order_by("last_updated").last(), self.form_data) else: self.assertInstanceEqual(self._get_queryset().last(), self.form_data)
def test_bulk_delete_objects_with_permission(self): pk_list = self._get_queryset().values_list("pk", flat=True) data = { "pk": pk_list, "confirm": True, "_confirm": True, # Form button } # Assign unconstrained permission obj_perm = ObjectPermission(name="Test permission", actions=["delete"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # Try POST with model-level permission self.assertHttpStatus(self.client.post(self._get_url("bulk_delete"), data), 302) self.assertEqual(self._get_queryset().count(), 0)
def test_bulk_import_objects_with_permission(self): initial_count = self._get_queryset().count() data = { "csv": self._get_csv_data(), } # Assign model-level permission obj_perm = ObjectPermission(name="Test permission", actions=["add"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # Try GET with model-level permission self.assertHttpStatus(self.client.get(self._get_url("import")), 200) # Test POST with permission self.assertHttpStatus(self.client.post(self._get_url("import"), data), 200) self.assertEqual(self._get_queryset().count(), initial_count + len(self.csv_data) - 1)
def test_get_object_with_constrained_permission(self): instance1, instance2 = self._get_queryset().all()[:2] # Add object-level permission obj_perm = ObjectPermission( name="Test permission", constraints={"pk": instance1.pk}, actions=["view"], ) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # Try GET to permitted object self.assertHttpStatus(self.client.get(instance1.get_absolute_url()), 200) # Try GET to non-permitted object self.assertHttpStatus(self.client.get(instance2.get_absolute_url()), 404)
def test_create_object(self): """ POST a single object with permission. """ # Add object-level permission obj_perm = ObjectPermission(name="Test permission", actions=["add"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add( ContentType.objects.get_for_model(self.model)) initial_count = self._get_queryset().count() for i, create_data in enumerate(self.create_data): response = self.client.post(self._get_list_url(), create_data, format="json", **self.header) self.assertHttpStatus(response, status.HTTP_201_CREATED) self.assertEqual(self._get_queryset().count(), initial_count + i + 1) instance = self._get_queryset().get(pk=response.data["id"]) self.assertInstanceEqual( instance, create_data, exclude=self.validation_excluded_fields, api=True, ) # Check if Slug field is automatically created if self.slug_source is not None and "slug" not in create_data: object = self._get_queryset().get(pk=response.data["id"]) expected_slug = slugify(getattr(object, self.slug_source)) self.assertEqual(object.slug, expected_slug) # Verify ObjectChange creation if hasattr(self.model, "to_objectchange"): objectchanges = ObjectChange.objects.filter( changed_object_type=ContentType.objects.get_for_model( instance), changed_object_id=instance.pk) self.assertEqual(len(objectchanges), 1) self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_CREATE)
def test_edit_object_with_permission(self): instance = self._get_queryset().first() # Assign model-level permission obj_perm = ObjectPermission(name="Test permission", actions=["change"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # Try GET with model-level permission self.assertHttpStatus(self.client.get(self._get_url("edit", instance)), 200) # Try POST with model-level permission request = { "path": self._get_url("edit", instance), "data": post_data(self.form_data), } self.assertHttpStatus(self.client.post(**request), 302) self.assertInstanceEqual(self._get_queryset().get(pk=instance.pk), self.form_data)
def test_bulk_rename_objects_with_permission(self): objects = list(self._get_queryset().all()[:3]) pk_list = [obj.pk for obj in objects] data = { "pk": pk_list, "_apply": True, # Form button } data.update(self.rename_data) # Assign model-level permission obj_perm = ObjectPermission(name="Test permission", actions=["change"]) obj_perm.save() obj_perm.users.add(self.user) obj_perm.object_types.add(ContentType.objects.get_for_model(self.model)) # Try POST with model-level permission self.assertHttpStatus(self.client.post(self._get_url("bulk_rename"), data), 302) for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)): self.assertEqual(instance.name, f"{objects[i].name}X")