Initial commit
This commit is contained in:
1
env/lib/python3.10/site-packages/wagtail/permission_policies/__init__.py
vendored
Normal file
1
env/lib/python3.10/site-packages/wagtail/permission_policies/__init__.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
from .base import * # NOQA: F403
|
||||
BIN
env/lib/python3.10/site-packages/wagtail/permission_policies/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/permission_policies/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/permission_policies/__pycache__/base.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/permission_policies/__pycache__/base.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/permission_policies/__pycache__/collections.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/permission_policies/__pycache__/collections.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/permission_policies/__pycache__/pages.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/permission_policies/__pycache__/pages.cpython-310.pyc
vendored
Normal file
Binary file not shown.
427
env/lib/python3.10/site-packages/wagtail/permission_policies/base.py
vendored
Normal file
427
env/lib/python3.10/site-packages/wagtail/permission_policies/base.py
vendored
Normal file
@@ -0,0 +1,427 @@
|
||||
from django.contrib.auth import get_permission_codename, get_user_model
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
|
||||
from django.db.models import Q
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from wagtail.coreutils import resolve_model_string
|
||||
|
||||
|
||||
class BasePermissionPolicy:
|
||||
"""
|
||||
A 'permission policy' is an object that handles all decisions about the actions
|
||||
users are allowed to perform on a given model. The mechanism by which it does this
|
||||
is arbitrary, and may or may not involve the django.contrib.auth Permission model;
|
||||
it could be as simple as "allow all users to do everything".
|
||||
|
||||
In this way, admin apps can change their permission-handling logic just by swapping
|
||||
to a different policy object, rather than having that logic spread across numerous
|
||||
view functions.
|
||||
|
||||
BasePermissionPolicy is an abstract class that all permission policies inherit from.
|
||||
The only method that subclasses need to implement is users_with_any_permission;
|
||||
all other methods can be derived from that (but in practice, subclasses will probably
|
||||
want to override additional methods, either for efficiency or to implement more
|
||||
fine-grained permission logic).
|
||||
"""
|
||||
|
||||
permission_cache_name = ""
|
||||
|
||||
def __init__(self, model):
|
||||
self._model_or_name = model
|
||||
|
||||
@cached_property
|
||||
def model(self):
|
||||
model = resolve_model_string(self._model_or_name)
|
||||
self.check_model(model)
|
||||
return model
|
||||
|
||||
def check_model(self, model):
|
||||
# a hook that is called at the point that the model argument (which may be a string
|
||||
# rather than a model class) is resolved to a model class, for subclasses to perform
|
||||
# any necessary validation checks on that model class
|
||||
pass
|
||||
|
||||
def get_all_permissions_for_user(self, user):
|
||||
"""
|
||||
Return a set of all permissions that the given user has on this model.
|
||||
|
||||
They may be instances of django.contrib.auth.Permission, or custom
|
||||
permission objects defined by the policy, which are not necessarily
|
||||
model instances.
|
||||
"""
|
||||
return set()
|
||||
|
||||
def get_cached_permissions_for_user(self, user):
|
||||
"""
|
||||
Return a list of all permissions that the given user has on this model,
|
||||
using the cache if available and populating the cache if not.
|
||||
|
||||
This can be useful for the other methods to perform efficient queries
|
||||
against the set of permissions that the user has.
|
||||
"""
|
||||
if hasattr(user, self.permission_cache_name):
|
||||
perms = getattr(user, self.permission_cache_name)
|
||||
else:
|
||||
perms = self.get_all_permissions_for_user(user)
|
||||
if self.permission_cache_name:
|
||||
setattr(user, self.permission_cache_name, perms)
|
||||
return perms
|
||||
|
||||
# Basic user permission tests. Most policies are expected to override these,
|
||||
# since the default implementation is to query the set of permitted users
|
||||
# (which is pretty inefficient).
|
||||
|
||||
def user_has_permission(self, user, action):
|
||||
"""
|
||||
Return whether the given user has permission to perform the given action
|
||||
on some or all instances of this model
|
||||
"""
|
||||
return user in self.users_with_permission(action)
|
||||
|
||||
def user_has_any_permission(self, user, actions):
|
||||
"""
|
||||
Return whether the given user has permission to perform any of the given actions
|
||||
on some or all instances of this model
|
||||
"""
|
||||
return any(self.user_has_permission(user, action) for action in actions)
|
||||
|
||||
# Operations for retrieving a list of users matching the permission criteria.
|
||||
# All policies must implement, at minimum, users_with_any_permission.
|
||||
|
||||
def users_with_any_permission(self, actions):
|
||||
"""
|
||||
Return a queryset of users who have permission to perform any of the given actions
|
||||
on some or all instances of this model
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def users_with_permission(self, action):
|
||||
"""
|
||||
Return a queryset of users who have permission to perform the given action on
|
||||
some or all instances of this model
|
||||
"""
|
||||
return self.users_with_any_permission([action])
|
||||
|
||||
# Per-instance permission tests. In the simplest cases - corresponding to the
|
||||
# basic Django permission model - permissions are enforced on a per-model basis
|
||||
# and so these methods can simply defer to the per-model tests. Policies that
|
||||
# require per-instance permission logic must override, at minimum:
|
||||
# user_has_permission_for_instance
|
||||
# instances_user_has_any_permission_for
|
||||
# users_with_any_permission_for_instance
|
||||
|
||||
def user_has_permission_for_instance(self, user, action, instance):
|
||||
"""
|
||||
Return whether the given user has permission to perform the given action on the
|
||||
given model instance
|
||||
"""
|
||||
return self.user_has_permission(user, action)
|
||||
|
||||
def user_has_any_permission_for_instance(self, user, actions, instance):
|
||||
"""
|
||||
Return whether the given user has permission to perform any of the given actions
|
||||
on the given model instance
|
||||
"""
|
||||
return any(
|
||||
self.user_has_permission_for_instance(user, action, instance)
|
||||
for action in actions
|
||||
)
|
||||
|
||||
def instances_user_has_any_permission_for(self, user, actions):
|
||||
"""
|
||||
Return a queryset of all instances of this model for which the given user has
|
||||
permission to perform any of the given actions
|
||||
"""
|
||||
if self.user_has_any_permission(user, actions):
|
||||
return self.model.objects.all()
|
||||
else:
|
||||
return self.model.objects.none()
|
||||
|
||||
def instances_user_has_permission_for(self, user, action):
|
||||
"""
|
||||
Return a queryset of all instances of this model for which the given user has
|
||||
permission to perform the given action
|
||||
"""
|
||||
return self.instances_user_has_any_permission_for(user, [action])
|
||||
|
||||
def users_with_any_permission_for_instance(self, actions, instance):
|
||||
"""
|
||||
Return a queryset of all users who have permission to perform any of the given
|
||||
actions on the given model instance
|
||||
"""
|
||||
return self.users_with_any_permission(actions)
|
||||
|
||||
def users_with_permission_for_instance(self, action, instance):
|
||||
return self.users_with_any_permission_for_instance([action], instance)
|
||||
|
||||
|
||||
class BlanketPermissionPolicy(BasePermissionPolicy):
|
||||
"""
|
||||
A permission policy that gives everyone (including anonymous users)
|
||||
full permission over the given model
|
||||
"""
|
||||
|
||||
def user_has_permission(self, user, action):
|
||||
return True
|
||||
|
||||
def user_has_any_permission(self, user, actions):
|
||||
return True
|
||||
|
||||
def users_with_any_permission(self, actions):
|
||||
# Here we filter out inactive users from the results, even though inactive users
|
||||
# - and for that matter anonymous users - still have permission according to the
|
||||
# user_has_permission method. This is appropriate because, for most applications,
|
||||
# setting is_active=False is equivalent to deleting the user account; you would
|
||||
# not want these accounts to appear in, for example, a dropdown of users to
|
||||
# assign a task to. The result here could never be completely logically correct
|
||||
# (because it will not include anonymous users), so as the next best thing we
|
||||
# return the "least surprise" result.
|
||||
return get_user_model().objects.filter(is_active=True)
|
||||
|
||||
def users_with_permission(self, action):
|
||||
return get_user_model().objects.filter(is_active=True)
|
||||
|
||||
|
||||
class AuthenticationOnlyPermissionPolicy(BasePermissionPolicy):
|
||||
"""
|
||||
A permission policy that gives all active authenticated users
|
||||
full permission over the given model
|
||||
"""
|
||||
|
||||
def user_has_permission(self, user, action):
|
||||
return user.is_authenticated and user.is_active
|
||||
|
||||
def user_has_any_permission(self, user, actions):
|
||||
return user.is_authenticated and user.is_active
|
||||
|
||||
def users_with_any_permission(self, actions):
|
||||
return get_user_model().objects.filter(is_active=True)
|
||||
|
||||
def users_with_permission(self, action):
|
||||
return get_user_model().objects.filter(is_active=True)
|
||||
|
||||
|
||||
class BaseDjangoAuthPermissionPolicy(BasePermissionPolicy):
|
||||
"""
|
||||
Extends BasePermissionPolicy with helper methods useful for policies that need to
|
||||
perform lookups against the django.contrib.auth permission model
|
||||
"""
|
||||
|
||||
def __init__(self, model, auth_model=None):
|
||||
# `auth_model` specifies the model to be used for permission record lookups;
|
||||
# usually this will match `model` (which specifies the type of instances that
|
||||
# `instances_user_has_permission_for` will return), but this may differ when
|
||||
# swappable models are in use - for example, an interface for editing user
|
||||
# records might use a custom User model but will typically still refer to the
|
||||
# permission records for auth.user.
|
||||
super().__init__(model)
|
||||
self._auth_model_or_name = auth_model or model
|
||||
|
||||
@cached_property
|
||||
def auth_model(self):
|
||||
return resolve_model_string(self._auth_model_or_name)
|
||||
|
||||
@cached_property
|
||||
def app_label(self):
|
||||
return self.auth_model._meta.app_label
|
||||
|
||||
@cached_property
|
||||
def model_name(self):
|
||||
return self.auth_model._meta.model_name
|
||||
|
||||
@cached_property
|
||||
def _content_type(self):
|
||||
return ContentType.objects.get_for_model(self.auth_model)
|
||||
|
||||
def _get_permission_codenames(self, actions):
|
||||
return {get_permission_codename(action, self.model._meta) for action in actions}
|
||||
|
||||
def _get_permission_name(self, action):
|
||||
"""
|
||||
Get the full app-label-qualified permission name (as required by
|
||||
user.has_perm(...) ) for the given action on this model
|
||||
"""
|
||||
return "{}.{}".format(
|
||||
self.app_label,
|
||||
get_permission_codename(action, self.model._meta),
|
||||
)
|
||||
|
||||
def _get_permission_objects_for_actions(self, actions):
|
||||
"""
|
||||
Get a queryset of the Permission objects for the given actions
|
||||
"""
|
||||
return Permission.objects.filter(
|
||||
content_type=self._content_type,
|
||||
codename__in=self._get_permission_codenames(actions),
|
||||
)
|
||||
|
||||
def _get_users_with_any_permission_codenames_filter(self, permission_codenames):
|
||||
"""
|
||||
Given a list of permission codenames, return a filter expression which
|
||||
will find all users which have any of those permissions - either
|
||||
through group permissions, user permissions, or implicitly through
|
||||
being a superuser.
|
||||
"""
|
||||
permissions = Permission.objects.filter(
|
||||
content_type=self._content_type, codename__in=permission_codenames
|
||||
)
|
||||
return (
|
||||
Q(is_superuser=True)
|
||||
| Q(user_permissions__in=permissions)
|
||||
| Q(groups__permissions__in=permissions)
|
||||
) & Q(is_active=True)
|
||||
|
||||
def _get_users_with_any_permission_codenames(self, permission_codenames):
|
||||
"""
|
||||
Given a list of permission codenames, return a queryset of users which
|
||||
have any of those permissions - either through group permissions, user
|
||||
permissions, or implicitly through being a superuser.
|
||||
"""
|
||||
filter_expr = self._get_users_with_any_permission_codenames_filter(
|
||||
permission_codenames
|
||||
)
|
||||
return get_user_model().objects.filter(filter_expr).distinct()
|
||||
|
||||
|
||||
class ModelPermissionPolicy(BaseDjangoAuthPermissionPolicy):
|
||||
"""
|
||||
A permission policy that enforces permissions at the model level, by consulting
|
||||
the standard django.contrib.auth permission model directly
|
||||
"""
|
||||
|
||||
def user_has_permission(self, user, action):
|
||||
return user.has_perm(self._get_permission_name(action))
|
||||
|
||||
def users_with_any_permission(self, actions):
|
||||
return self._get_users_with_any_permission_codenames(
|
||||
self._get_permission_codenames(actions)
|
||||
)
|
||||
|
||||
|
||||
class OwnershipPermissionPolicy(BaseDjangoAuthPermissionPolicy):
|
||||
"""
|
||||
A permission policy for objects that support a concept of 'ownership', where
|
||||
the owner is typically the user who created the object.
|
||||
|
||||
This policy piggybacks off 'add' and 'change' permissions defined through the
|
||||
django.contrib.auth Permission model, as follows:
|
||||
|
||||
* any user with 'add' permission can create instances, and ALSO edit instances
|
||||
that they own
|
||||
* any user with 'change' permission can edit instances regardless of ownership
|
||||
* ability to edit also implies ability to delete
|
||||
|
||||
Besides 'add', 'change' and 'delete', no other actions are recognised or permitted
|
||||
(unless the user is an active superuser, in which case they can do everything).
|
||||
"""
|
||||
|
||||
def __init__(self, model, auth_model=None, owner_field_name="owner"):
|
||||
super().__init__(model, auth_model=auth_model)
|
||||
self.owner_field_name = owner_field_name
|
||||
|
||||
def check_model(self, model):
|
||||
super().check_model(model)
|
||||
|
||||
# make sure owner_field_name is a field that exists on the model
|
||||
try:
|
||||
model._meta.get_field(self.owner_field_name)
|
||||
except FieldDoesNotExist:
|
||||
raise ImproperlyConfigured(
|
||||
"%s has no field named '%s'. To use this model with OwnershipPermissionPolicy, "
|
||||
"you must specify a valid field name as owner_field_name."
|
||||
% (model, self.owner_field_name)
|
||||
)
|
||||
|
||||
def user_has_permission(self, user, action):
|
||||
if action == "add":
|
||||
return user.has_perm(self._get_permission_name("add"))
|
||||
elif action == "change" or action == "delete":
|
||||
return (
|
||||
# having 'add' permission means that there are *potentially*
|
||||
# some instances they can edit (namely: ones they own),
|
||||
# which is sufficient for returning True here
|
||||
user.has_perm(self._get_permission_name("add"))
|
||||
or user.has_perm(self._get_permission_name("change"))
|
||||
)
|
||||
else:
|
||||
# unrecognised actions are only allowed for active superusers
|
||||
return user.is_active and user.is_superuser
|
||||
|
||||
def users_with_any_permission(self, actions):
|
||||
if "change" in actions or "delete" in actions:
|
||||
# either 'add' or 'change' permission means that there are *potentially*
|
||||
# some instances they can edit
|
||||
permission_codenames = self._get_permission_codenames({"add", "change"})
|
||||
elif "add" in actions:
|
||||
permission_codenames = self._get_permission_codenames({"add"})
|
||||
else:
|
||||
# none of the actions passed in here are ones that we recognise, so only
|
||||
# allow them for active superusers
|
||||
return get_user_model().objects.filter(is_active=True, is_superuser=True)
|
||||
|
||||
return self._get_users_with_any_permission_codenames(permission_codenames)
|
||||
|
||||
def user_has_permission_for_instance(self, user, action, instance):
|
||||
return self.user_has_any_permission_for_instance(user, [action], instance)
|
||||
|
||||
def user_has_any_permission_for_instance(self, user, actions, instance):
|
||||
if "change" in actions or "delete" in actions:
|
||||
if user.has_perm(self._get_permission_name("change")):
|
||||
return True
|
||||
elif (
|
||||
user.has_perm(self._get_permission_name("add"))
|
||||
and getattr(instance, self.owner_field_name) == user
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
# 'change' and 'delete' are the only actions that are well-defined
|
||||
# for specific instances. Other actions are only available to
|
||||
# active superusers.
|
||||
return user.is_active and user.is_superuser
|
||||
|
||||
def instances_user_has_any_permission_for(self, user, actions):
|
||||
if user.is_active and user.is_superuser:
|
||||
# active superusers can perform any action (including unrecognised ones)
|
||||
# on any instance
|
||||
return self.model.objects.all()
|
||||
elif "change" in actions or "delete" in actions:
|
||||
if user.has_perm(self._get_permission_name("change")):
|
||||
# user can edit all instances
|
||||
return self.model.objects.all()
|
||||
elif user.has_perm(self._get_permission_name("add")):
|
||||
# user can edit their own instances
|
||||
return self.model.objects.filter(**{self.owner_field_name: user})
|
||||
else:
|
||||
# user has no permissions at all on this model
|
||||
return self.model.objects.none()
|
||||
else:
|
||||
# action is either not recognised, or is the 'add' action which is
|
||||
# not meaningful for existing instances. As such, non-superusers
|
||||
# cannot perform it on any existing instances.
|
||||
return self.model.objects.none()
|
||||
|
||||
def users_with_any_permission_for_instance(self, actions, instance):
|
||||
if "change" in actions or "delete" in actions:
|
||||
# get filter expression for users with 'change' permission
|
||||
filter_expr = self._get_users_with_any_permission_codenames_filter(
|
||||
self._get_permission_codenames({"change"})
|
||||
)
|
||||
|
||||
# add on the item's owner, if they still have 'add' permission
|
||||
# (and the owner field isn't blank)
|
||||
owner = getattr(instance, self.owner_field_name)
|
||||
if owner is not None and owner.has_perm(self._get_permission_name("add")):
|
||||
filter_expr = filter_expr | Q(pk=owner.pk)
|
||||
|
||||
# return the filtered queryset
|
||||
return get_user_model().objects.filter(filter_expr).distinct()
|
||||
|
||||
else:
|
||||
# action is either not recognised, or is the 'add' action which is
|
||||
# not meaningful for existing instances. As such, the action is only
|
||||
# available to superusers
|
||||
return get_user_model().objects.filter(is_active=True, is_superuser=True)
|
||||
489
env/lib/python3.10/site-packages/wagtail/permission_policies/collections.py
vendored
Normal file
489
env/lib/python3.10/site-packages/wagtail/permission_policies/collections.py
vendored
Normal file
@@ -0,0 +1,489 @@
|
||||
from django.contrib.auth import get_permission_codename, get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
|
||||
from django.db.models import Q
|
||||
|
||||
from wagtail.models import Collection, GroupCollectionPermission
|
||||
|
||||
from .base import BaseDjangoAuthPermissionPolicy
|
||||
|
||||
|
||||
class CollectionPermissionLookupMixin:
|
||||
permission_cache_name = "_collection_permission_cache"
|
||||
|
||||
def _get_user_permission_objects_for_actions(self, user, actions):
|
||||
"""
|
||||
Get a set of the user's GroupCollectionPermission objects for the given actions
|
||||
"""
|
||||
permission_codenames = {
|
||||
get_permission_codename(action, self.auth_model._meta) for action in actions
|
||||
}
|
||||
return {
|
||||
group_permission
|
||||
for group_permission in self.get_cached_permissions_for_user(user)
|
||||
if group_permission.permission.codename in permission_codenames
|
||||
}
|
||||
|
||||
def get_all_permissions_for_user(self, user):
|
||||
# For these users, we can determine the permissions without querying
|
||||
# GroupCollectionPermission by checking it directly in _check_perm()
|
||||
if not user.is_active or user.is_anonymous or user.is_superuser:
|
||||
return GroupCollectionPermission.objects.none()
|
||||
|
||||
return GroupCollectionPermission.objects.filter(
|
||||
group__user=user
|
||||
).select_related("permission", "collection")
|
||||
|
||||
def _check_perm(self, user, actions, collection=None):
|
||||
"""
|
||||
Equivalent to user.has_perm(self._get_permission_name(action)) on all listed actions,
|
||||
but using GroupCollectionPermission rather than group.permissions.
|
||||
If collection is specified, only consider GroupCollectionPermission records
|
||||
that apply to that collection.
|
||||
"""
|
||||
if not (user.is_active and user.is_authenticated):
|
||||
return False
|
||||
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
||||
collection_permissions = self._get_user_permission_objects_for_actions(
|
||||
user, actions
|
||||
)
|
||||
|
||||
if collection:
|
||||
collection_permissions = {
|
||||
permission
|
||||
for permission in collection_permissions
|
||||
if collection.is_descendant_of(permission.collection)
|
||||
or collection.pk == permission.collection_id
|
||||
}
|
||||
|
||||
return bool(collection_permissions)
|
||||
|
||||
def _collections_with_perm(self, user, actions):
|
||||
"""
|
||||
Return a queryset of collections on which this user has a GroupCollectionPermission
|
||||
record for any of the given actions, either on the collection itself or an ancestor
|
||||
"""
|
||||
permissions = self._get_user_permission_objects_for_actions(user, actions)
|
||||
|
||||
collections = Collection.objects.none()
|
||||
for perm in permissions:
|
||||
collections |= Collection.objects.descendant_of(
|
||||
perm.collection, inclusive=True
|
||||
)
|
||||
|
||||
return collections
|
||||
|
||||
def _users_with_perm_filter(self, actions, collection=None):
|
||||
"""
|
||||
Return a filter expression that will filter a user queryset to those with any
|
||||
permissions corresponding to 'actions', via either GroupCollectionPermission
|
||||
or superuser privileges.
|
||||
If collection is specified, only consider GroupCollectionPermission records
|
||||
that apply to that collection.
|
||||
"""
|
||||
permissions = self._get_permission_objects_for_actions(actions)
|
||||
# Find all groups with GroupCollectionPermission records for
|
||||
# any of these permissions
|
||||
groups = Group.objects.filter(
|
||||
collection_permissions__permission__in=permissions,
|
||||
)
|
||||
|
||||
if collection is not None:
|
||||
collections = collection.get_ancestors(inclusive=True)
|
||||
groups = groups.filter(collection_permissions__collection__in=collections)
|
||||
|
||||
# Find all users who are active, and are superusers or in any of these groups
|
||||
return Q(is_active=True) & (Q(is_superuser=True) | Q(groups__in=groups))
|
||||
|
||||
def _users_with_perm(self, actions, collection=None):
|
||||
"""
|
||||
Return a queryset of users with any permissions corresponding to 'actions',
|
||||
via either GroupCollectionPermission or superuser privileges.
|
||||
If collection is specified, only consider GroupCollectionPermission records
|
||||
that apply to that collection.
|
||||
"""
|
||||
return (
|
||||
get_user_model()
|
||||
.objects.filter(
|
||||
self._users_with_perm_filter(actions, collection=collection)
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
def collections_user_has_any_permission_for(self, user, actions):
|
||||
"""
|
||||
Return a queryset of all collections in which the given user has
|
||||
permission to perform any of the given actions
|
||||
"""
|
||||
if user.is_active and user.is_superuser:
|
||||
# active superusers can perform any action (including unrecognised ones)
|
||||
# in any collection
|
||||
return Collection.objects.all()
|
||||
|
||||
if not user.is_authenticated:
|
||||
return Collection.objects.none()
|
||||
|
||||
return self._collections_with_perm(user, actions)
|
||||
|
||||
def collections_user_has_permission_for(self, user, action):
|
||||
"""
|
||||
Return a queryset of all collections in which the given user has
|
||||
permission to perform the given action
|
||||
"""
|
||||
return self.collections_user_has_any_permission_for(user, [action])
|
||||
|
||||
|
||||
class CollectionPermissionPolicy(
|
||||
CollectionPermissionLookupMixin, BaseDjangoAuthPermissionPolicy
|
||||
):
|
||||
"""
|
||||
A permission policy for objects that are assigned locations in the Collection tree.
|
||||
Permissions may be defined at any node of the hierarchy, through the
|
||||
GroupCollectionPermission model, and propagate downwards. These permissions are
|
||||
applied to objects according to the standard django.contrib.auth permission model.
|
||||
"""
|
||||
|
||||
def user_has_permission(self, user, action):
|
||||
"""
|
||||
Return whether the given user has permission to perform the given action
|
||||
on some or all instances of this model
|
||||
"""
|
||||
return self._check_perm(user, [action])
|
||||
|
||||
def user_has_any_permission(self, user, actions):
|
||||
"""
|
||||
Return whether the given user has permission to perform any of the given actions
|
||||
on some or all instances of this model
|
||||
"""
|
||||
return self._check_perm(user, actions)
|
||||
|
||||
def users_with_any_permission(self, actions):
|
||||
"""
|
||||
Return a queryset of users who have permission to perform any of the given actions
|
||||
on some or all instances of this model
|
||||
"""
|
||||
return self._users_with_perm(actions)
|
||||
|
||||
def user_has_permission_for_instance(self, user, action, instance):
|
||||
"""
|
||||
Return whether the given user has permission to perform the given action on the
|
||||
given model instance
|
||||
"""
|
||||
return self._check_perm(user, [action], collection=instance.collection)
|
||||
|
||||
def user_has_any_permission_for_instance(self, user, actions, instance):
|
||||
"""
|
||||
Return whether the given user has permission to perform any of the given actions
|
||||
on the given model instance
|
||||
"""
|
||||
return self._check_perm(user, actions, collection=instance.collection)
|
||||
|
||||
def instances_user_has_any_permission_for(self, user, actions):
|
||||
"""
|
||||
Return a queryset of all instances of this model for which the given user has
|
||||
permission to perform any of the given actions
|
||||
"""
|
||||
if not (user.is_active and user.is_authenticated):
|
||||
return self.model.objects.none()
|
||||
elif user.is_superuser:
|
||||
return self.model.objects.all()
|
||||
else:
|
||||
# filter to just the collections with this permission
|
||||
return self.model.objects.filter(
|
||||
collection__in=list(self._collections_with_perm(user, actions))
|
||||
)
|
||||
|
||||
def users_with_any_permission_for_instance(self, actions, instance):
|
||||
"""
|
||||
Return a queryset of all users who have permission to perform any of the given
|
||||
actions on the given model instance
|
||||
"""
|
||||
return self._users_with_perm(actions, collection=instance.collection)
|
||||
|
||||
|
||||
class CollectionOwnershipPermissionPolicy(
|
||||
CollectionPermissionLookupMixin, BaseDjangoAuthPermissionPolicy
|
||||
):
|
||||
"""
|
||||
A permission policy for objects that are assigned locations in the Collection tree.
|
||||
Permissions may be defined at any node of the hierarchy, through the
|
||||
GroupCollectionPermission model, and propagate downwards. These permissions are
|
||||
applied to objects according to the 'ownership' permission model
|
||||
(see permission_policies.base.OwnershipPermissionPolicy)
|
||||
"""
|
||||
|
||||
def __init__(self, model, auth_model=None, owner_field_name="owner"):
|
||||
super().__init__(model, auth_model=auth_model)
|
||||
self.owner_field_name = owner_field_name
|
||||
|
||||
def check_model(self, model):
|
||||
super().check_model(model)
|
||||
|
||||
# make sure owner_field_name is a field that exists on the model
|
||||
try:
|
||||
model._meta.get_field(self.owner_field_name)
|
||||
except FieldDoesNotExist:
|
||||
raise ImproperlyConfigured(
|
||||
"%s has no field named '%s'. To use this model with "
|
||||
"CollectionOwnershipPermissionPolicy, you must specify a valid field name as "
|
||||
"owner_field_name." % (model, self.owner_field_name)
|
||||
)
|
||||
|
||||
def user_has_permission(self, user, action):
|
||||
if action == "add":
|
||||
return self._check_perm(user, ["add"])
|
||||
elif action == "choose":
|
||||
return self._check_perm(user, ["choose"])
|
||||
elif action == "change" or action == "delete":
|
||||
# having 'add' permission means that there are *potentially*
|
||||
# some instances they can edit (namely: ones they own),
|
||||
# which is sufficient for returning True here
|
||||
return self._check_perm(user, ["add", "change"])
|
||||
else:
|
||||
# unrecognised actions are only allowed for active superusers
|
||||
return user.is_active and user.is_superuser
|
||||
|
||||
def users_with_any_permission(self, actions):
|
||||
known_actions = set(actions) & {"add", "choose", "change"}
|
||||
|
||||
# "delete" is considered equivalent to "change"
|
||||
if "delete" in actions:
|
||||
known_actions.add("change")
|
||||
|
||||
# users with only "add" permission can still change instances they own
|
||||
if "change" in known_actions:
|
||||
known_actions.add("add")
|
||||
|
||||
if not known_actions:
|
||||
# none of the actions passed in here are ones that we recognise, so only
|
||||
# allow them for active superusers
|
||||
return get_user_model().objects.filter(is_active=True, is_superuser=True)
|
||||
|
||||
return self._users_with_perm(known_actions)
|
||||
|
||||
def user_has_permission_for_instance(self, user, action, instance):
|
||||
return self.user_has_any_permission_for_instance(user, [action], instance)
|
||||
|
||||
def user_has_any_permission_for_instance(self, user, actions, instance):
|
||||
known_actions = set(actions) & {"add", "choose", "change"}
|
||||
|
||||
# "delete" is considered equivalent to "change"
|
||||
if "delete" in actions:
|
||||
known_actions.add("change")
|
||||
|
||||
# users with only "add" permission can still change instances they own
|
||||
if (
|
||||
"change" in known_actions
|
||||
and getattr(instance, self.owner_field_name) == user
|
||||
):
|
||||
known_actions.add("add")
|
||||
|
||||
if known_actions:
|
||||
return self._check_perm(user, known_actions, collection=instance.collection)
|
||||
else:
|
||||
# 'change', 'delete', and 'choose' are the only actions that are well-defined
|
||||
# for specific instances. Other actions are only available to
|
||||
# active superusers.
|
||||
return user.is_active and user.is_superuser
|
||||
|
||||
def instances_user_has_any_permission_for(self, user, actions):
|
||||
known_actions = set(actions) & {"change", "choose"}
|
||||
|
||||
# "delete" is considered equivalent to "change"
|
||||
if "delete" in actions:
|
||||
known_actions.add("change")
|
||||
|
||||
if user.is_active and user.is_superuser:
|
||||
# active superusers can perform any action (including unrecognised ones)
|
||||
# on any instance
|
||||
return self.model.objects.all()
|
||||
elif not user.is_authenticated:
|
||||
return self.model.objects.none()
|
||||
elif known_actions:
|
||||
# if "change" or "delete" in actions, return instances which are:
|
||||
# - in (a descendant of) a collection for which they have "change" permission
|
||||
# - OR in (a descendant of) a collection for which they have "add" permission,
|
||||
# and are owned by them
|
||||
# if "choose" in actions, return instances which are:
|
||||
# - in (a descendant of) a collection for which they have "choose" permission.
|
||||
# Note that if the actions contain both cases, the results will be combined
|
||||
# because the user has "any" of the permissions in the actions.
|
||||
|
||||
collections = self._collections_with_perm(user, known_actions)
|
||||
perm_filter = Q(collection__in=collections)
|
||||
|
||||
# "add" permission implies "change" permission,
|
||||
# but only if the instance is owned by the user
|
||||
if "change" in known_actions:
|
||||
perm_filter |= Q(
|
||||
collection__in=self._collections_with_perm(user, ["add"])
|
||||
) & Q(**{self.owner_field_name: user})
|
||||
|
||||
return self.model.objects.filter(perm_filter)
|
||||
else:
|
||||
# action is either not recognised, or is the 'add' action which is
|
||||
# not meaningful for existing instances. As such, non-superusers
|
||||
# cannot perform it on any existing instances.
|
||||
return self.model.objects.none()
|
||||
|
||||
def users_with_any_permission_for_instance(self, actions, instance):
|
||||
known_actions = set(actions) & {"choose", "change"}
|
||||
|
||||
# "delete" is considered equivalent to "change"
|
||||
if "delete" in actions:
|
||||
known_actions.add("change")
|
||||
|
||||
filter_expr = self._users_with_perm_filter(
|
||||
known_actions, collection=instance.collection
|
||||
)
|
||||
|
||||
# users with only "add" permission can still change instances they own
|
||||
if "change" in known_actions:
|
||||
owner = getattr(instance, self.owner_field_name)
|
||||
if owner is not None and self._check_perm(
|
||||
owner, {"add"}, collection=instance.collection
|
||||
):
|
||||
filter_expr |= Q(pk=owner.pk)
|
||||
|
||||
if known_actions:
|
||||
return get_user_model().objects.filter(filter_expr).distinct()
|
||||
else:
|
||||
# action is either not recognised, or is the 'add' action which is
|
||||
# not meaningful for existing instances. As such, the action is only
|
||||
# available to superusers
|
||||
return get_user_model().objects.filter(is_active=True, is_superuser=True)
|
||||
|
||||
def collections_user_has_any_permission_for(self, user, actions):
|
||||
"""
|
||||
Return a queryset of all collections in which the given user has
|
||||
permission to perform any of the given actions
|
||||
"""
|
||||
known_actions = set(actions) & {"add", "choose", "change"}
|
||||
|
||||
# "delete" is considered equivalent to "change"
|
||||
if "delete" in actions:
|
||||
known_actions.add("change")
|
||||
|
||||
# users with only "add" permission can still change instances they own
|
||||
if "change" in known_actions:
|
||||
known_actions.add("add")
|
||||
|
||||
if user.is_active and user.is_superuser:
|
||||
# active superusers can perform any action (including unrecognised ones)
|
||||
# in any collection
|
||||
return Collection.objects.all()
|
||||
|
||||
elif not user.is_authenticated:
|
||||
return Collection.objects.none()
|
||||
|
||||
elif known_actions:
|
||||
return self._collections_with_perm(user, known_actions)
|
||||
|
||||
else:
|
||||
# action is not recognised, and so non-superusers
|
||||
# cannot perform it on any existing collections
|
||||
return Collection.objects.none()
|
||||
|
||||
|
||||
class CollectionManagementPermissionPolicy(
|
||||
CollectionPermissionLookupMixin, BaseDjangoAuthPermissionPolicy
|
||||
):
|
||||
def _descendants_with_perm(self, user, action):
|
||||
"""
|
||||
Return a queryset of collections descended from a collection on which this user has
|
||||
a GroupCollectionPermission record for this action. Used for actions, like edit and
|
||||
delete where the user cannot modify the collection where they are granted permission.
|
||||
"""
|
||||
# Get the permission object corresponding to this action
|
||||
permission = self._get_permission_objects_for_actions([action]).first()
|
||||
|
||||
# Get the collections that have a GroupCollectionPermission record
|
||||
# for this permission and any of the user's groups;
|
||||
# create a list of their paths
|
||||
collection_roots = Collection.objects.filter(
|
||||
group_permissions__group__in=user.groups.all(),
|
||||
group_permissions__permission=permission,
|
||||
).values("path", "depth")
|
||||
|
||||
if collection_roots:
|
||||
# build a filter expression that will filter our model to just those
|
||||
# instances in collections with a path that starts with one of the above
|
||||
# but excluding the collection on which permission was granted
|
||||
collection_path_filter = Q(
|
||||
path__startswith=collection_roots[0]["path"]
|
||||
) & Q(depth__gt=collection_roots[0]["depth"])
|
||||
for collection in collection_roots[1:]:
|
||||
collection_path_filter = collection_path_filter | (
|
||||
Q(path__startswith=collection["path"])
|
||||
& Q(depth__gt=collection["depth"])
|
||||
)
|
||||
return Collection.objects.all().filter(collection_path_filter)
|
||||
else:
|
||||
# no matching collections
|
||||
return Collection.objects.none()
|
||||
|
||||
def user_has_permission(self, user, action):
|
||||
"""
|
||||
Return whether the given user has permission to perform the given action
|
||||
on some or all instances of this model
|
||||
"""
|
||||
return self.user_has_any_permission(user, [action])
|
||||
|
||||
def user_has_any_permission(self, user, actions):
|
||||
"""
|
||||
Return whether the given user has permission to perform any of the given actions
|
||||
on some or all instances of this model.
|
||||
"""
|
||||
return self._check_perm(user, actions)
|
||||
|
||||
def users_with_any_permission(self, actions):
|
||||
"""
|
||||
Return a queryset of users who have permission to perform any of the given actions
|
||||
on some or all instances of this model
|
||||
"""
|
||||
return self._users_with_perm(actions)
|
||||
|
||||
def user_has_permission_for_instance(self, user, action, instance):
|
||||
"""
|
||||
Return whether the given user has permission to perform the given action on the
|
||||
given model instance
|
||||
"""
|
||||
return self._check_perm(user, [action], collection=instance)
|
||||
|
||||
def user_has_any_permission_for_instance(self, user, actions, instance):
|
||||
"""
|
||||
Return whether the given user has permission to perform any of the given actions
|
||||
on the given model instance
|
||||
"""
|
||||
return self._check_perm(user, actions, collection=instance)
|
||||
|
||||
def users_with_any_permission_for_instance(self, actions, instance):
|
||||
"""
|
||||
Return a queryset of all users who have permission to perform any of the given
|
||||
actions on the given model instance
|
||||
"""
|
||||
return self._users_with_perm(actions, collection=instance)
|
||||
|
||||
def instances_user_has_permission_for(self, user, action):
|
||||
if user.is_active and user.is_superuser:
|
||||
# active superusers can perform any action (including unrecognised ones)
|
||||
# in any collection - except for deleting the root collection
|
||||
if action == "delete":
|
||||
return Collection.objects.exclude(depth=1).all()
|
||||
else:
|
||||
return Collection.objects.all()
|
||||
|
||||
elif not user.is_authenticated:
|
||||
return Collection.objects.none()
|
||||
|
||||
else:
|
||||
if action == "delete":
|
||||
return self._descendants_with_perm(user, action)
|
||||
else:
|
||||
return self._collections_with_perm(user, [action])
|
||||
|
||||
def instances_user_has_any_permission_for(self, user, actions):
|
||||
return self.collections_user_has_any_permission_for(user, actions)
|
||||
215
env/lib/python3.10/site-packages/wagtail/permission_policies/pages.py
vendored
Normal file
215
env/lib/python3.10/site-packages/wagtail/permission_policies/pages.py
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
from django.contrib.auth import get_permission_codename, get_user_model
|
||||
from django.db.models import Q
|
||||
|
||||
from wagtail.models import GroupPagePermission, Page
|
||||
from wagtail.permission_policies.base import OwnershipPermissionPolicy
|
||||
|
||||
|
||||
class PagePermissionPolicy(OwnershipPermissionPolicy):
|
||||
permission_cache_name = "_page_permission_cache"
|
||||
_explorable_root_instance_cache_name = "_explorable_root_page_cache"
|
||||
|
||||
def __init__(self, model=Page):
|
||||
super().__init__(model=model)
|
||||
|
||||
def get_all_permissions_for_user(self, user):
|
||||
if not user.is_active or user.is_anonymous or user.is_superuser:
|
||||
return GroupPagePermission.objects.none()
|
||||
return GroupPagePermission.objects.filter(group__user=user).select_related(
|
||||
"page", "permission"
|
||||
)
|
||||
|
||||
def _base_user_has_permission(self, user):
|
||||
if not user.is_active:
|
||||
return False
|
||||
if user.is_superuser:
|
||||
return True
|
||||
return None
|
||||
|
||||
def _base_queryset_for_user(self, user):
|
||||
if not user.is_active:
|
||||
return self.model._default_manager.none()
|
||||
if user.is_superuser:
|
||||
return self.model._default_manager.all()
|
||||
return None
|
||||
|
||||
def user_has_permission(self, user, action):
|
||||
return self.user_has_any_permission(user, {action})
|
||||
|
||||
def user_has_any_permission(self, user, actions):
|
||||
base_permission = self._base_user_has_permission(user)
|
||||
if base_permission is not None:
|
||||
return base_permission
|
||||
|
||||
# User with only "add" permission can still edit their own pages
|
||||
actions = set(actions)
|
||||
if "change" in actions:
|
||||
actions.add("add")
|
||||
|
||||
permissions = {
|
||||
perm.permission.codename
|
||||
for perm in self.get_cached_permissions_for_user(user)
|
||||
}
|
||||
return bool(self._get_permission_codenames(actions) & permissions)
|
||||
|
||||
def users_with_any_permission(self, actions, include_superusers=True):
|
||||
# User with only "add" permission can still edit their own pages
|
||||
actions = set(actions)
|
||||
if "change" in actions:
|
||||
actions.add("add")
|
||||
|
||||
groups = GroupPagePermission.objects.filter(
|
||||
permission__codename__in=self._get_permission_codenames(actions)
|
||||
).values_list("group", flat=True)
|
||||
|
||||
q = Q(groups__in=groups)
|
||||
if include_superusers:
|
||||
q |= Q(is_superuser=True)
|
||||
|
||||
return (
|
||||
get_user_model()
|
||||
._default_manager.filter(is_active=True)
|
||||
.filter(q)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
def users_with_permission(self, action, include_superusers=True):
|
||||
return self.users_with_any_permission({action}, include_superusers)
|
||||
|
||||
def user_has_permission_for_instance(self, user, action, instance):
|
||||
return self.user_has_any_permission_for_instance(user, {action}, instance)
|
||||
|
||||
def user_has_any_permission_for_instance(self, user, actions, instance):
|
||||
base_permission = self._base_user_has_permission(user)
|
||||
if base_permission is not None:
|
||||
return base_permission
|
||||
|
||||
permissions = set()
|
||||
for perm in self.get_cached_permissions_for_user(user):
|
||||
if instance.pk == perm.page_id or instance.is_descendant_of(perm.page):
|
||||
permissions.add(perm.permission.codename)
|
||||
if (
|
||||
perm.permission.codename
|
||||
== get_permission_codename("add", self.model._meta)
|
||||
and instance.owner_id == user.pk
|
||||
):
|
||||
permissions.add(get_permission_codename("change", self.model._meta))
|
||||
|
||||
return bool(self._get_permission_codenames(actions) & permissions)
|
||||
|
||||
def instances_user_has_any_permission_for(self, user, actions):
|
||||
base_queryset = self._base_queryset_for_user(user)
|
||||
if base_queryset is not None:
|
||||
return base_queryset
|
||||
|
||||
pages = self.model._default_manager.none()
|
||||
for perm in self.get_cached_permissions_for_user(user):
|
||||
if (
|
||||
perm.permission.codename
|
||||
== get_permission_codename("add", self.model._meta)
|
||||
and "add" not in actions
|
||||
and "change" in actions
|
||||
):
|
||||
pages |= self.model._default_manager.descendant_of(
|
||||
perm.page, inclusive=True
|
||||
).filter(owner=user)
|
||||
elif perm.permission.codename in self._get_permission_codenames(actions):
|
||||
pages |= self.model._default_manager.descendant_of(
|
||||
perm.page, inclusive=True
|
||||
)
|
||||
return pages
|
||||
|
||||
def users_with_any_permission_for_instance(
|
||||
self, actions, instance, include_superusers=True
|
||||
):
|
||||
# Find permissions for all ancestors that match any of the actions
|
||||
ancestors = instance.get_ancestors(inclusive=True)
|
||||
groups = GroupPagePermission.objects.filter(
|
||||
permission__codename__in=self._get_permission_codenames(actions),
|
||||
page__in=ancestors,
|
||||
).values_list("group", flat=True)
|
||||
|
||||
q = Q(groups__in=groups)
|
||||
|
||||
if include_superusers:
|
||||
q |= Q(is_superuser=True)
|
||||
|
||||
# If "change" is in actions but "add" is not, then we need to check for
|
||||
# cases where the user has "add" permission on an ancestor, and is the
|
||||
# owner of the instance
|
||||
if "change" in actions and "add" not in actions:
|
||||
add_groups = GroupPagePermission.objects.filter(
|
||||
permission__codename=get_permission_codename("add", self.model._meta),
|
||||
page__in=ancestors,
|
||||
).values_list("group", flat=True)
|
||||
|
||||
q |= Q(groups__in=add_groups) & Q(pk=instance.owner_id)
|
||||
|
||||
return (
|
||||
get_user_model()
|
||||
._default_manager.filter(is_active=True)
|
||||
.filter(q)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
def users_with_permission_for_instance(
|
||||
self, action, instance, include_superusers=True
|
||||
):
|
||||
return self.users_with_any_permission_for_instance(
|
||||
{action}, instance, include_superusers
|
||||
)
|
||||
|
||||
def instances_with_direct_explore_permission(self, user):
|
||||
# Get all pages that the user has direct add/change/publish/lock permission on
|
||||
if user.is_superuser:
|
||||
# superuser has implicit permission on the root node
|
||||
return Page.objects.filter(depth=1)
|
||||
else:
|
||||
codenames = self._get_permission_codenames(
|
||||
{"add", "change", "publish", "lock"}
|
||||
)
|
||||
return [
|
||||
perm.page
|
||||
for perm in self.get_cached_permissions_for_user(user)
|
||||
if perm.permission.codename in codenames
|
||||
]
|
||||
|
||||
def explorable_root_instance(self, user):
|
||||
# This method is used all around the admin,
|
||||
# so cache the result on the user for the duration of the request
|
||||
if hasattr(user, self._explorable_root_instance_cache_name):
|
||||
return getattr(user, self._explorable_root_instance_cache_name)
|
||||
pages = self.instances_with_direct_explore_permission(user)
|
||||
try:
|
||||
root_page = Page.objects.first_common_ancestor_of(
|
||||
pages, include_self=True, strict=True
|
||||
)
|
||||
except Page.DoesNotExist:
|
||||
root_page = None
|
||||
setattr(user, self._explorable_root_instance_cache_name, root_page)
|
||||
return root_page
|
||||
|
||||
def explorable_instances(self, user):
|
||||
base_queryset = self._base_queryset_for_user(user)
|
||||
if base_queryset is not None:
|
||||
return base_queryset
|
||||
|
||||
explorable_pages = self.instances_user_has_any_permission_for(
|
||||
user, {"add", "change", "publish", "lock"}
|
||||
)
|
||||
|
||||
# For all pages with specific permissions, add their ancestors as
|
||||
# explorable. This will allow deeply nested pages to be accessed in the
|
||||
# explorer. For example, in the hierarchy A>B>C>D where the user has
|
||||
# 'change' access on D, they will be able to navigate to D without having
|
||||
# explicit access to A, B or C.
|
||||
page_permissions = [
|
||||
perm.page for perm in self.get_cached_permissions_for_user(user)
|
||||
]
|
||||
for page in page_permissions:
|
||||
explorable_pages |= page.get_ancestors()
|
||||
|
||||
# Remove unnecessary top-level ancestors that the user has no access to
|
||||
fca_page = Page.objects.first_common_ancestor_of(page_permissions)
|
||||
explorable_pages = explorable_pages.filter(path__startswith=fca_page.path)
|
||||
return explorable_pages
|
||||
Reference in New Issue
Block a user