from django.contrib.auth.models import AnonymousUser, Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase

from wagtail.images.models import Image
from wagtail.images.tests.utils import get_test_image_file
from wagtail.permission_policies import (
    AuthenticationOnlyPermissionPolicy,
    BlanketPermissionPolicy,
    ModelPermissionPolicy,
    OwnershipPermissionPolicy,
)
from wagtail.test.utils import WagtailTestUtils


class PermissionPolicyTestUtils:
    def assertResultSetEqual(self, actual, expected):
        self.assertEqual(set(actual), set(expected))

    def assertUserPermissionMatrix(self, test_cases, actions=()):
        """
        Given a list of (user, can_add, can_change, can_delete, can_frobnicate) tuples
        (where 'frobnicate' is an unrecognised action not defined on the model),
        confirm that all tuples correctly represent permissions for that user as
        returned by user_has_permission
        """
        if not actions:
            actions = ["add", "change", "delete", "frobnicate"]
        for test_case in test_cases:
            user = test_case[0]
            expected_results = zip(actions, test_case[1:])

            for action, expected_result in expected_results:
                if expected_result:
                    self.assertTrue(
                        self.policy.user_has_permission(user, action),
                        msg="User {} should be able to {}, but can't".format(
                            user, action
                        ),
                    )
                else:
                    self.assertFalse(
                        self.policy.user_has_permission(user, action),
                        msg="User %s should not be able to %s, but can"
                        % (user, action),
                    )

    def assertUserInstancePermissionMatrix(self, instance, test_cases, actions=()):
        """
        Given a list of (user, can_change, can_delete, can_frobnicate) tuples
        (where 'frobnicate' is an unrecognised action not defined on the model),
        confirm that all tuples correctly represent permissions for that user on
        the given instance, as returned by user_has_permission_for_instance
        """
        if not actions:
            actions = ["change", "delete", "frobnicate"]
        for test_case in test_cases:
            user = test_case[0]
            expected_results = zip(actions, test_case[1:])

            for action, expected_result in expected_results:
                if expected_result:
                    self.assertTrue(
                        self.policy.user_has_permission_for_instance(
                            user, action, instance
                        ),
                        msg="User %s should be able to %s instance %s, but can't"
                        % (user, action, instance),
                    )
                else:
                    self.assertFalse(
                        self.policy.user_has_permission_for_instance(
                            user, action, instance
                        ),
                        msg="User %s should not be able to %s instance %s, but can"
                        % (user, action, instance),
                    )


class PermissionPolicyTestCase(PermissionPolicyTestUtils, WagtailTestUtils, TestCase):
    def setUp(self):
        # Permissions
        image_content_type = ContentType.objects.get_for_model(Image)
        add_image_permission = Permission.objects.get(
            content_type=image_content_type, codename="add_image"
        )
        change_image_permission = Permission.objects.get(
            content_type=image_content_type, codename="change_image"
        )
        delete_image_permission = Permission.objects.get(
            content_type=image_content_type, codename="delete_image"
        )

        # Groups
        image_adders_group = Group.objects.create(name="Image adders")
        image_adders_group.permissions.add(add_image_permission)

        image_changers_group = Group.objects.create(name="Image changers")
        image_changers_group.permissions.add(change_image_permission)

        # Users
        self.superuser = self.create_superuser(
            "superuser", "superuser@example.com", "password"
        )
        self.inactive_superuser = self.create_superuser(
            "inactivesuperuser", "inactivesuperuser@example.com", "password"
        )
        self.inactive_superuser.is_active = False
        self.inactive_superuser.save()

        # a user with add_image permission through the 'Image adders' group
        self.image_adder = self.create_user(
            "imageadder", "imageadder@example.com", "password"
        )
        self.image_adder.groups.add(image_adders_group)

        # a user with add_image permission through user_permissions
        self.oneoff_image_adder = self.create_user(
            "oneoffimageadder", "oneoffimageadder@example.com", "password"
        )
        self.oneoff_image_adder.user_permissions.add(add_image_permission)

        # a user that has add_image permission, but is inactive
        self.inactive_image_adder = self.create_user(
            "inactiveimageadder", "inactiveimageadder@example.com", "password"
        )
        self.inactive_image_adder.groups.add(image_adders_group)
        self.inactive_image_adder.is_active = False
        self.inactive_image_adder.save()

        # a user with change_image permission through the 'Image changers' group
        self.image_changer = self.create_user(
            "imagechanger", "imagechanger@example.com", "password"
        )
        self.image_changer.groups.add(image_changers_group)

        # a user with change_image permission through user_permissions
        self.oneoff_image_changer = self.create_user(
            "oneoffimagechanger", "oneoffimagechanger@example.com", "password"
        )
        self.oneoff_image_changer.user_permissions.add(change_image_permission)

        # a user that has change_image permission, but is inactive
        self.inactive_image_changer = self.create_user(
            "inactiveimagechanger", "inactiveimagechanger@example.com", "password"
        )
        self.inactive_image_changer.groups.add(image_changers_group)
        self.inactive_image_changer.is_active = False
        self.inactive_image_changer.save()

        # a user with delete_image permission through user_permissions
        self.oneoff_image_deleter = self.create_user(
            "oneoffimagedeleter", "oneoffimagedeleter@example.com", "password"
        )
        self.oneoff_image_deleter.user_permissions.add(delete_image_permission)

        # a user with no permissions
        self.useless_user = self.create_user(
            "uselessuser", "uselessuser@example.com", "password"
        )

        self.anonymous_user = AnonymousUser()

        # Images

        # an image owned by 'imageadder'
        self.adder_image = Image.objects.create(
            title="imageadder's image",
            file=get_test_image_file(),
            uploaded_by_user=self.image_adder,
        )

        # an image owned by 'uselessuser'
        self.useless_image = Image.objects.create(
            title="uselessuser's image",
            file=get_test_image_file(),
            uploaded_by_user=self.useless_user,
        )

        # an image with no owner
        self.anonymous_image = Image.objects.create(
            title="anonymous image",
            file=get_test_image_file(),
        )


class TestBlanketPermissionPolicy(PermissionPolicyTestCase):
    def setUp(self):
        super().setUp()
        self.policy = BlanketPermissionPolicy(Image)

        self.active_users = [
            self.superuser,
            self.image_adder,
            self.oneoff_image_adder,
            self.image_changer,
            self.oneoff_image_changer,
            self.oneoff_image_deleter,
            self.useless_user,
        ]
        self.all_users = self.active_users + [
            self.inactive_superuser,
            self.inactive_image_adder,
            self.inactive_image_changer,
            self.anonymous_user,
        ]

    def test_user_has_permission(self):
        # All users have permission to do everything
        self.assertUserPermissionMatrix(
            [(user, True, True, True, True) for user in self.all_users]
        )

    def test_user_has_any_permission(self):
        for user in self.all_users:
            self.assertTrue(
                self.policy.user_has_any_permission(user, ["add", "change"])
            )

    def test_users_with_permission(self):
        # all active users have permission
        users_with_add_permission = self.policy.users_with_permission("add")

        self.assertResultSetEqual(users_with_add_permission, self.active_users)

    def test_users_with_any_permission(self):
        # all active users have permission
        users_with_add_or_change_permission = self.policy.users_with_any_permission(
            ["add", "change"]
        )

        self.assertResultSetEqual(
            users_with_add_or_change_permission, self.active_users
        )

    def test_user_has_permission_for_instance(self):
        # All users have permission to do everything on any given instance
        self.assertUserInstancePermissionMatrix(
            self.adder_image, [(user, True, True, True) for user in self.all_users]
        )

    def test_user_has_any_permission_for_instance(self):
        for user in self.all_users:
            self.assertTrue(
                self.policy.user_has_any_permission_for_instance(
                    user, ["change", "delete"], self.adder_image
                )
            )

    def test_instances_user_has_permission_for(self):
        all_images = [self.adder_image, self.useless_image, self.anonymous_image]

        # all users can edit all instances
        for user in self.all_users:
            self.assertResultSetEqual(
                self.policy.instances_user_has_permission_for(user, "change"),
                all_images,
            )

    def test_instances_user_has_any_permission_for(self):
        all_images = [self.adder_image, self.useless_image, self.anonymous_image]

        for user in self.all_users:
            self.assertResultSetEqual(
                self.policy.instances_user_has_any_permission_for(
                    user, ["change", "delete"]
                ),
                all_images,
            )

    def test_users_with_permission_for_instance(self):
        # all active users have permission
        users_with_change_permission = self.policy.users_with_permission_for_instance(
            "change", self.useless_image
        )

        self.assertResultSetEqual(users_with_change_permission, self.active_users)

    def test_users_with_any_permission_for_instance(self):
        # all active users have permission
        users_with_change_or_del_permission = (
            self.policy.users_with_any_permission_for_instance(
                ["change", "delete"], self.useless_image
            )
        )

        self.assertResultSetEqual(
            users_with_change_or_del_permission, self.active_users
        )


class TestAuthenticationOnlyPermissionPolicy(PermissionPolicyTestCase):
    def setUp(self):
        super().setUp()
        self.policy = AuthenticationOnlyPermissionPolicy(Image)

    def test_user_has_permission(self):
        # All active authenticated users have permission to do everything;
        # inactive and anonymous users have permission to do nothing
        self.assertUserPermissionMatrix(
            [
                (self.superuser, True, True, True, True),
                (self.inactive_superuser, False, False, False, False),
                (self.image_adder, True, True, True, True),
                (self.oneoff_image_adder, True, True, True, True),
                (self.inactive_image_adder, False, False, False, False),
                (self.image_changer, True, True, True, True),
                (self.oneoff_image_changer, True, True, True, True),
                (self.inactive_image_changer, False, False, False, False),
                (self.oneoff_image_deleter, True, True, True, True),
                (self.useless_user, True, True, True, True),
                (self.anonymous_user, False, False, False, False),
            ]
        )

    def test_user_has_any_permission(self):
        self.assertTrue(
            self.policy.user_has_any_permission(self.superuser, ["add", "change"])
        )

        self.assertFalse(
            self.policy.user_has_any_permission(
                self.inactive_superuser, ["add", "change"]
            )
        )

        self.assertTrue(
            self.policy.user_has_any_permission(self.useless_user, ["add", "change"])
        )

        self.assertFalse(
            self.policy.user_has_any_permission(self.anonymous_user, ["add", "change"])
        )

    def test_users_with_permission(self):
        # all active users have permission
        users_with_add_permission = self.policy.users_with_permission("add")

        self.assertResultSetEqual(
            users_with_add_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
                self.image_changer,
                self.oneoff_image_changer,
                self.oneoff_image_deleter,
                self.useless_user,
            ],
        )

    def test_users_with_any_permission(self):
        # all active users have permission
        users_with_add_or_change_permission = self.policy.users_with_any_permission(
            ["add", "change"]
        )

        self.assertResultSetEqual(
            users_with_add_or_change_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
                self.image_changer,
                self.oneoff_image_changer,
                self.oneoff_image_deleter,
                self.useless_user,
            ],
        )

    def test_user_has_permission_for_instance(self):
        # Permissions for this policy are applied at the model level,
        # so rules for a specific instance will match rules for the
        # model as a whole
        self.assertUserInstancePermissionMatrix(
            self.adder_image,
            [
                (self.superuser, True, True, True),
                (self.inactive_superuser, False, False, False),
                (self.image_adder, True, True, True),
                (self.oneoff_image_adder, True, True, True),
                (self.inactive_image_adder, False, False, False),
                (self.image_changer, True, True, True),
                (self.oneoff_image_changer, True, True, True),
                (self.inactive_image_changer, False, False, False),
                (self.oneoff_image_deleter, True, True, True),
                (self.useless_user, True, True, True),
                (self.anonymous_user, False, False, False),
            ],
        )

    def test_user_has_any_permission_for_instance(self):
        # superuser has permission
        self.assertTrue(
            self.policy.user_has_any_permission_for_instance(
                self.superuser, ["change", "delete"], self.adder_image
            )
        )

        # inactive user has no permission
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.inactive_superuser, ["change", "delete"], self.adder_image
            )
        )

        # ordinary user has permission
        self.assertTrue(
            self.policy.user_has_any_permission_for_instance(
                self.useless_user, ["change", "delete"], self.adder_image
            )
        )

        # anonymous user has no permission
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.anonymous_user, ["change", "delete"], self.adder_image
            )
        )

    def test_instances_user_has_permission_for(self):
        all_images = [self.adder_image, self.useless_image, self.anonymous_image]
        no_images = []

        # the set of images editable by superuser includes all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.superuser, "change"),
            all_images,
        )

        # the set of images editable by inactive superuser includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(
                self.inactive_superuser, "change"
            ),
            no_images,
        )

        # the set of images editable by ordinary user includes all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.useless_user, "change"),
            all_images,
        )

        # the set of images editable by anonymous user includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(
                self.anonymous_user, "change"
            ),
            no_images,
        )

    def test_instances_user_has_any_permission_for(self):
        all_images = [self.adder_image, self.useless_image, self.anonymous_image]
        no_images = []

        # the set of images editable by superuser includes all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.superuser, ["change", "delete"]
            ),
            all_images,
        )

        # the set of images editable by inactive superuser includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.inactive_superuser, ["change", "delete"]
            ),
            no_images,
        )

        # the set of images editable by ordinary user includes all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.useless_user, ["change", "delete"]
            ),
            all_images,
        )

        # the set of images editable by anonymous user includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.anonymous_user, ["change", "delete"]
            ),
            no_images,
        )

    def test_users_with_permission_for_instance(self):
        # all active users have permission
        users_with_change_permission = self.policy.users_with_permission_for_instance(
            "change", self.useless_image
        )

        self.assertResultSetEqual(
            users_with_change_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
                self.image_changer,
                self.oneoff_image_changer,
                self.oneoff_image_deleter,
                self.useless_user,
            ],
        )

    def test_users_with_any_permission_for_instance(self):
        # all active users have permission
        users_with_change_or_del_permission = (
            self.policy.users_with_any_permission_for_instance(
                ["change", "delete"], self.useless_image
            )
        )

        self.assertResultSetEqual(
            users_with_change_or_del_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
                self.image_changer,
                self.oneoff_image_changer,
                self.oneoff_image_deleter,
                self.useless_user,
            ],
        )


class TestModelPermissionPolicy(PermissionPolicyTestCase):
    def setUp(self):
        super().setUp()
        self.policy = ModelPermissionPolicy(Image)

    def test_user_has_permission(self):
        self.assertUserPermissionMatrix(
            [
                # Superuser has permission to do everything
                (self.superuser, True, True, True, True),
                # Inactive superuser can do nothing
                (self.inactive_superuser, False, False, False, False),
                # User with 'add' permission via group can only add
                (self.image_adder, True, False, False, False),
                # User with 'add' permission via user can only add
                (self.oneoff_image_adder, True, False, False, False),
                # Inactive user with 'add' permission can do nothing
                (self.inactive_image_adder, False, False, False, False),
                # User with 'change' permission via group can only change
                (self.image_changer, False, True, False, False),
                # User with 'change' permission via user can only change
                (self.oneoff_image_changer, False, True, False, False),
                # Inactive user with 'add' permission can do nothing
                (self.inactive_image_changer, False, False, False, False),
                # User with 'delete' permission can only delete
                (self.oneoff_image_deleter, False, False, True, False),
                # User with no permissions can do nothing
                (self.useless_user, False, False, False, False),
                # Anonymous user can do nothing
                (self.anonymous_user, False, False, False, False),
            ]
        )

    def test_user_has_any_permission(self):
        # Superuser can do everything
        self.assertTrue(
            self.policy.user_has_any_permission(self.superuser, ["add", "change"])
        )

        # Inactive superuser can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission(
                self.inactive_superuser, ["add", "change"]
            )
        )

        # Only one of the permissions in the list needs to pass
        # in order for user_has_any_permission to return true
        self.assertTrue(
            self.policy.user_has_any_permission(self.image_adder, ["add", "change"])
        )
        self.assertTrue(
            self.policy.user_has_any_permission(
                self.oneoff_image_adder, ["add", "change"]
            )
        )
        self.assertTrue(
            self.policy.user_has_any_permission(self.image_changer, ["add", "change"])
        )

        # User with some permission, but not the ones in the list,
        # should return false
        self.assertFalse(
            self.policy.user_has_any_permission(self.image_changer, ["add", "delete"])
        )

        # Inactive user with the appropriate permissions can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission(
                self.inactive_image_adder, ["add", "delete"]
            )
        )

        # User with no permissions can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission(self.useless_user, ["add", "change"])
        )

        # Anonymous user can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission(self.anonymous_user, ["add", "change"])
        )

    def test_users_with_permission(self):
        users_with_add_permission = self.policy.users_with_permission("add")

        self.assertResultSetEqual(
            users_with_add_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
            ],
        )

        users_with_change_permission = self.policy.users_with_permission("change")

        self.assertResultSetEqual(
            users_with_change_permission,
            [
                self.superuser,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )

    def test_users_with_any_permission(self):
        users_with_add_or_change_permission = self.policy.users_with_any_permission(
            ["add", "change"]
        )

        self.assertResultSetEqual(
            users_with_add_or_change_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )

        users_with_change_or_delete_permission = self.policy.users_with_any_permission(
            ["change", "delete"]
        )

        self.assertResultSetEqual(
            users_with_change_or_delete_permission,
            [
                self.superuser,
                self.image_changer,
                self.oneoff_image_changer,
                self.oneoff_image_deleter,
            ],
        )

    def test_user_has_permission_for_instance(self):
        # Permissions for this policy are applied at the model level,
        # so rules for a specific instance will match rules for the
        # model as a whole
        self.assertUserInstancePermissionMatrix(
            self.adder_image,
            [
                (self.superuser, True, True, True),
                (self.inactive_superuser, False, False, False),
                (self.image_adder, False, False, False),
                (self.oneoff_image_adder, False, False, False),
                (self.inactive_image_adder, False, False, False),
                (self.image_changer, True, False, False),
                (self.oneoff_image_changer, True, False, False),
                (self.inactive_image_changer, False, False, False),
                (self.oneoff_image_deleter, False, True, False),
                (self.useless_user, False, False, False),
                (self.anonymous_user, False, False, False),
            ],
        )

    def test_user_has_any_permission_for_instance(self):
        # Superuser can do everything
        self.assertTrue(
            self.policy.user_has_any_permission_for_instance(
                self.superuser, ["change", "delete"], self.adder_image
            )
        )

        # Inactive superuser can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.inactive_superuser, ["change", "delete"], self.adder_image
            )
        )

        # Only one of the permissions in the list needs to pass
        # in order for user_has_any_permission to return true
        self.assertTrue(
            self.policy.user_has_any_permission_for_instance(
                self.image_changer, ["change", "delete"], self.adder_image
            )
        )
        self.assertTrue(
            self.policy.user_has_any_permission_for_instance(
                self.oneoff_image_changer, ["change", "delete"], self.adder_image
            )
        )

        # User with some permission, but not the ones in the list,
        # should return false
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.image_adder, ["change", "delete"], self.adder_image
            )
        )

        # Inactive user with the appropriate permissions can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.inactive_image_changer, ["change", "delete"], self.adder_image
            )
        )

        # User with no permissions can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.useless_user, ["change", "delete"], self.adder_image
            )
        )

        # Anonymous user can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.anonymous_user, ["change", "delete"], self.adder_image
            )
        )

    def test_instances_user_has_permission_for(self):
        all_images = [self.adder_image, self.useless_image, self.anonymous_image]
        no_images = []

        # the set of images editable by superuser includes all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.superuser, "change"),
            all_images,
        )

        # the set of images editable by inactive superuser includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(
                self.inactive_superuser, "change"
            ),
            no_images,
        )

        # given the relevant model permission at the group level, a user can edit all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.image_changer, "change"),
            all_images,
        )

        # given the relevant model permission at the user level, a user can edit all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(
                self.oneoff_image_changer, "change"
            ),
            all_images,
        )

        # a user with no permission can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.useless_user, "change"),
            no_images,
        )

        # an inactive user with the relevant permission can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(
                self.inactive_image_changer, "change"
            ),
            no_images,
        )

        # a user with permission, but not the matching one, can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.image_changer, "delete"),
            no_images,
        )

        # the set of images editable by anonymous user includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(
                self.anonymous_user, "change"
            ),
            no_images,
        )

    def test_instances_user_has_any_permission_for(self):
        all_images = [self.adder_image, self.useless_image, self.anonymous_image]
        no_images = []

        # the set of images editable by superuser includes all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.superuser, ["change", "delete"]
            ),
            all_images,
        )

        # the set of images editable by inactive superuser includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.inactive_superuser, ["change", "delete"]
            ),
            no_images,
        )

        # given the relevant model permission at the group level, a user can edit all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.image_changer, ["change", "delete"]
            ),
            all_images,
        )

        # given the relevant model permission at the user level, a user can edit all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.oneoff_image_changer, ["change", "delete"]
            ),
            all_images,
        )

        # a user with no permission can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.useless_user, ["change", "delete"]
            ),
            no_images,
        )

        # an inactive user with the relevant permission can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.inactive_image_changer, ["change", "delete"]
            ),
            no_images,
        )

        # a user with permission, but not the matching one, can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.image_adder, ["change", "delete"]
            ),
            no_images,
        )

        # the set of images editable by anonymous user includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.anonymous_user, ["change", "delete"]
            ),
            no_images,
        )

    def test_users_with_permission_for_instance(self):
        users_with_change_permission = self.policy.users_with_permission_for_instance(
            "change", self.useless_image
        )

        self.assertResultSetEqual(
            users_with_change_permission,
            [
                self.superuser,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )

        users_with_delete_permission = self.policy.users_with_permission_for_instance(
            "delete", self.useless_image
        )

        self.assertResultSetEqual(
            users_with_delete_permission,
            [
                self.superuser,
                self.oneoff_image_deleter,
            ],
        )

    def test_users_with_any_permission_for_instance(self):
        users_with_change_or_del_permission = (
            self.policy.users_with_any_permission_for_instance(
                ["change", "delete"], self.useless_image
            )
        )

        self.assertResultSetEqual(
            users_with_change_or_del_permission,
            [
                self.superuser,
                self.image_changer,
                self.oneoff_image_changer,
                self.oneoff_image_deleter,
            ],
        )


class TestOwnershipPermissionPolicy(PermissionPolicyTestCase):
    def setUp(self):
        super().setUp()
        self.policy = OwnershipPermissionPolicy(
            Image, owner_field_name="uploaded_by_user"
        )

    def test_user_has_permission(self):
        self.assertUserPermissionMatrix(
            [
                # Superuser has permission to do everything
                (self.superuser, True, True, True, True),
                # Inactive superuser can do nothing
                (self.inactive_superuser, False, False, False, False),
                # User with 'add' permission via group can add,
                # and by extension, change and delete their own instances
                (self.image_adder, True, True, True, False),
                # User with 'add' permission via user can add,
                # and by extension, change and delete their own instances
                (self.oneoff_image_adder, True, True, True, False),
                # Inactive user with 'add' permission can do nothing
                (self.inactive_image_adder, False, False, False, False),
                # User with 'change' permission via group can change and delete but not add
                (self.image_changer, False, True, True, False),
                # User with 'change' permission via user can change and delete but not add
                (self.oneoff_image_changer, False, True, True, False),
                # Inactive user with 'change' permission can do nothing
                (self.inactive_image_changer, False, False, False, False),
                # 'delete' permission is ignored for this policy
                (self.oneoff_image_deleter, False, False, False, False),
                # User with no permission can do nothing
                (self.useless_user, False, False, False, False),
                # Anonymous user can do nothing
                (self.anonymous_user, False, False, False, False),
            ]
        )

    def test_user_has_any_permission(self):
        # Superuser can do everything
        self.assertTrue(
            self.policy.user_has_any_permission(self.superuser, ["add", "change"])
        )

        # Inactive superuser can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission(
                self.inactive_superuser, ["add", "change"]
            )
        )

        # Only one of the permissions in the list needs to pass
        # in order for user_has_any_permission to return true
        self.assertTrue(
            self.policy.user_has_any_permission(self.image_changer, ["add", "change"])
        )
        self.assertTrue(
            self.policy.user_has_any_permission(
                self.oneoff_image_changer, ["add", "change"]
            )
        )

        # User with some permission, but not the ones in the list,
        # should return false
        self.assertFalse(
            self.policy.user_has_any_permission(
                self.oneoff_image_deleter, ["add", "change"]
            )
        )

        # Inactive user with the appropriate permissions can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission(
                self.inactive_image_changer, ["add", "delete"]
            )
        )

        # User with no permissions can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission(self.useless_user, ["add", "change"])
        )

        # Anonymous user can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission(self.anonymous_user, ["add", "change"])
        )

    def test_users_with_permission(self):
        users_with_add_permission = self.policy.users_with_permission("add")

        self.assertResultSetEqual(
            users_with_add_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
            ],
        )

        # users with add permission have change permission too (i.e. for their own images)
        users_with_change_permission = self.policy.users_with_permission("change")

        self.assertResultSetEqual(
            users_with_change_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )

        # conditions for deletion are the same as for change; 'delete' permission
        # records in django.contrib.auth are ignored
        users_with_delete_permission = self.policy.users_with_permission("delete")

        self.assertResultSetEqual(
            users_with_delete_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )

        # non-standard permissions are only available to superusers
        users_with_frobnicate_permission = self.policy.users_with_permission(
            "frobnicate"
        )

        self.assertResultSetEqual(
            users_with_frobnicate_permission,
            [
                self.superuser,
            ],
        )

    def test_users_with_any_permission(self):
        users_with_add_or_change_permission = self.policy.users_with_any_permission(
            ["add", "change"]
        )

        self.assertResultSetEqual(
            users_with_add_or_change_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )

        users_with_add_or_frobnicate_permission = self.policy.users_with_any_permission(
            ["add", "frobnicate"]
        )

        self.assertResultSetEqual(
            users_with_add_or_frobnicate_permission,
            [
                self.superuser,
                self.image_adder,
                self.oneoff_image_adder,
            ],
        )

    def test_user_has_permission_for_instance(self):
        # Test permissions for an image owned by image_adder
        self.assertUserInstancePermissionMatrix(
            self.adder_image,
            [
                # superuser can do everything
                (self.superuser, True, True, True),
                # inactive superuser can do nothing
                (self.inactive_superuser, False, False, False),
                # image_adder can change and delete their own images,
                # but not perform custom actions
                (self.image_adder, True, True, False),
                # user with add permission cannot edit images owned by others
                (self.oneoff_image_adder, False, False, False),
                # inactive user with 'add' permission can do nothing
                (self.inactive_image_adder, False, False, False),
                # user with change permission can change and delete all images
                (self.image_changer, True, True, False),
                # likewise for change permission specified at the user level
                (self.oneoff_image_changer, True, True, False),
                # inactive user with 'change' permission can do nothing
                (self.inactive_image_changer, False, False, False),
                # delete permissions are ignored
                (self.oneoff_image_deleter, False, False, False),
                # user with no permissions can do nothing
                (self.useless_user, False, False, False),
                # anonymous user can do nothing
                (self.anonymous_user, False, False, False),
            ],
        )

        # Test permissions for an image owned by useless_user
        self.assertUserInstancePermissionMatrix(
            self.useless_image,
            [
                # superuser can do everything
                (self.superuser, True, True, True),
                # image_adder cannot edit images owned by others
                (self.image_adder, False, False, False),
                (self.oneoff_image_adder, False, False, False),
                # user with change permission can change and delete all images
                (self.image_changer, True, True, False),
                (self.oneoff_image_changer, True, True, False),
                # inactive users can do nothing
                (self.inactive_superuser, False, False, False),
                (self.inactive_image_adder, False, False, False),
                (self.inactive_image_changer, False, False, False),
                # delete permissions are ignored
                (self.oneoff_image_deleter, False, False, False),
                # user with no permissions can do nothing, even on images
                # they own
                (self.useless_user, False, False, False),
                # anonymous user can do nothing
                (self.anonymous_user, False, False, False),
            ],
        )

        # Instances with a null owner should always follow the same rules
        # as 'an instance owned by someone else'
        self.assertUserInstancePermissionMatrix(
            self.anonymous_image,
            [
                (self.superuser, True, True, True),
                (self.image_adder, False, False, False),
                (self.oneoff_image_adder, False, False, False),
                (self.image_changer, True, True, False),
                (self.oneoff_image_changer, True, True, False),
                (self.inactive_superuser, False, False, False),
                (self.inactive_image_adder, False, False, False),
                (self.inactive_image_changer, False, False, False),
                (self.oneoff_image_deleter, False, False, False),
                (self.useless_user, False, False, False),
                (self.anonymous_user, False, False, False),
            ],
        )

    def test_user_has_any_permission_for_instance(self):
        # Superuser can do everything
        self.assertTrue(
            self.policy.user_has_any_permission_for_instance(
                self.superuser, ["change", "delete"], self.adder_image
            )
        )

        # Inactive superuser can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.inactive_superuser, ["change", "delete"], self.adder_image
            )
        )

        # Only one of the permissions in the list needs to pass
        # in order for user_has_any_permission to return true
        self.assertTrue(
            self.policy.user_has_any_permission_for_instance(
                self.image_changer, ["change", "frobnicate"], self.adder_image
            )
        )
        self.assertTrue(
            self.policy.user_has_any_permission_for_instance(
                self.oneoff_image_changer, ["change", "frobnicate"], self.adder_image
            )
        )

        # User with some permission, but not the ones in the list,
        # should return false
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.oneoff_image_deleter, ["change", "delete"], self.adder_image
            )
        )

        # Inactive user with the appropriate permissions can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.inactive_image_changer, ["change", "delete"], self.adder_image
            )
        )

        # User with no permissions can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.useless_user, ["change", "delete"], self.adder_image
            )
        )

        # Anonymous user can do nothing
        self.assertFalse(
            self.policy.user_has_any_permission_for_instance(
                self.anonymous_user, ["change", "delete"], self.adder_image
            )
        )

    def test_instances_user_has_permission_for(self):
        all_images = [self.adder_image, self.useless_image, self.anonymous_image]
        no_images = []

        # the set of images editable by superuser includes all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.superuser, "change"),
            all_images,
        )

        # the set of images editable by inactive superuser includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(
                self.inactive_superuser, "change"
            ),
            no_images,
        )

        # a user with 'add' permission can change their own images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.image_adder, "change"),
            [self.adder_image],
        )
        # a user with 'add' permission can also delete their own images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.image_adder, "delete"),
            [self.adder_image],
        )

        # a user with 'change' permission can change all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.image_changer, "change"),
            all_images,
        )

        # ditto for 'change' permission assigned at the user level
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(
                self.oneoff_image_changer, "change"
            ),
            all_images,
        )

        # an inactive user with the relevant permission can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(
                self.inactive_image_changer, "change"
            ),
            no_images,
        )

        # a user with no permission can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(self.useless_user, "change"),
            no_images,
        )

        # the set of images editable by anonymous user includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_permission_for(
                self.anonymous_user, "change"
            ),
            no_images,
        )

    def test_instances_user_has_any_permission_for(self):
        all_images = [self.adder_image, self.useless_image, self.anonymous_image]
        no_images = []

        # the set of images editable by superuser includes all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.superuser, ["change", "delete"]
            ),
            all_images,
        )

        # the set of images editable by inactive superuser includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.inactive_superuser, ["change", "delete"]
            ),
            no_images,
        )

        # a user with 'add' permission can change/delete their own images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.image_adder, ["delete", "frobnicate"]
            ),
            [self.adder_image],
        )

        # a user with 'edit' permission can change/delete all images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.oneoff_image_changer, ["delete", "frobnicate"]
            ),
            all_images,
        )

        # a user with no permission can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.useless_user, ["change", "delete"]
            ),
            no_images,
        )

        # an inactive user with the relevant permission can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.inactive_image_changer, ["change", "delete"]
            ),
            no_images,
        )

        # a user with permission, but not the matching one, can edit no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.oneoff_image_deleter, ["change", "delete"]
            ),
            no_images,
        )

        # the set of images editable by anonymous user includes no images
        self.assertResultSetEqual(
            self.policy.instances_user_has_any_permission_for(
                self.anonymous_user, ["change", "delete"]
            ),
            no_images,
        )

    def test_users_with_permission_for_instance(self):
        # adder_image can be edited by its owner (who has add permission) and
        # all users with 'change' permission
        users_with_change_permission = self.policy.users_with_permission_for_instance(
            "change", self.adder_image
        )

        self.assertResultSetEqual(
            users_with_change_permission,
            [
                self.superuser,
                self.image_adder,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )

        # the same set of users can also delete
        users_with_delete_permission = self.policy.users_with_permission_for_instance(
            "delete", self.adder_image
        )

        self.assertResultSetEqual(
            users_with_delete_permission,
            [
                self.superuser,
                self.image_adder,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )

        # custom actions are available to superusers only
        users_with_delete_permission = self.policy.users_with_permission_for_instance(
            "frobnicate", self.adder_image
        )

        self.assertResultSetEqual(
            users_with_delete_permission,
            [
                self.superuser,
            ],
        )

        # useless_user can NOT edit their own image, because they do not have
        # 'add' permission
        users_with_change_permission = self.policy.users_with_permission_for_instance(
            "change", self.useless_image
        )

        self.assertResultSetEqual(
            users_with_change_permission,
            [
                self.superuser,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )

        # an image with no owner is treated as if it's owned by 'somebody else' -
        # i.e. users with 'change' permission can edit it
        users_with_change_permission = self.policy.users_with_permission_for_instance(
            "change", self.anonymous_image
        )

        self.assertResultSetEqual(
            users_with_change_permission,
            [
                self.superuser,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )

    def test_users_with_any_permission_for_instance(self):
        users_with_change_or_frob_permission = (
            self.policy.users_with_any_permission_for_instance(
                ["change", "frobnicate"], self.adder_image
            )
        )

        self.assertResultSetEqual(
            users_with_change_or_frob_permission,
            [
                self.superuser,
                self.image_adder,
                self.image_changer,
                self.oneoff_image_changer,
            ],
        )
