forked from ungleich-public/dynamicweb
232 lines
9.1 KiB
Python
232 lines
9.1 KiB
Python
from copy import deepcopy
|
|
|
|
from django.contrib import admin
|
|
from django.contrib.admin import site
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.auth.admin import UserAdmin
|
|
from django.contrib.sites.models import Site
|
|
from django.db import OperationalError, ProgrammingError
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from cms.admin.forms import (
|
|
GlobalPagePermissionAdminForm,
|
|
PagePermissionInlineAdminForm,
|
|
ViewRestrictionInlineAdminForm,
|
|
)
|
|
from cms.exceptions import NoPermissionsException
|
|
from cms.models import GlobalPagePermission, PagePermission
|
|
from cms.utils import page_permissions, permissions
|
|
from cms.utils.conf import get_cms_setting
|
|
from cms.utils.helpers import classproperty
|
|
|
|
PERMISSION_ADMIN_INLINES = []
|
|
|
|
user_model = get_user_model()
|
|
admin_class = UserAdmin
|
|
for model, admin_instance in site._registry.items():
|
|
if model == user_model:
|
|
admin_class = admin_instance.__class__
|
|
|
|
|
|
class TabularInline(admin.TabularInline):
|
|
pass
|
|
|
|
|
|
class PagePermissionInlineAdmin(TabularInline):
|
|
model = PagePermission
|
|
# use special form, so we can override of user and group field
|
|
form = PagePermissionInlineAdminForm
|
|
extra = 0 # edit page load time boost
|
|
show_with_view_permissions = False
|
|
|
|
def has_change_permission(self, request, obj=None):
|
|
if not obj:
|
|
return False
|
|
return page_permissions.user_can_change_page_permissions(
|
|
request.user,
|
|
page=obj,
|
|
site=obj.site,
|
|
)
|
|
|
|
def has_add_permission(self, request, obj=None):
|
|
return self.has_change_permission(request, obj)
|
|
|
|
@classproperty
|
|
def raw_id_fields(cls):
|
|
# Dynamically set raw_id_fields based on settings
|
|
threshold = get_cms_setting('RAW_ID_USERS')
|
|
|
|
# Given a fresh django-cms install and a django settings with the
|
|
# CMS_RAW_ID_USERS = CMS_PERMISSION = True
|
|
# django throws an OperationalError (sqlite) or ProgrammingError
|
|
# (postgres) when running ./manage migrate because the user table
|
|
# doesn't exist yet.
|
|
try:
|
|
threshold = threshold and get_user_model().objects.count() > threshold
|
|
except (OperationalError, ProgrammingError):
|
|
threshold = False
|
|
|
|
return ['user'] if threshold else []
|
|
|
|
def get_queryset(self, request):
|
|
"""
|
|
Queryset change, so user with global change permissions can see
|
|
all permissions. Otherwise user can see only permissions for
|
|
peoples which are under him (he can't see his permissions, because
|
|
this will lead to violation, when he can add more power to himself)
|
|
"""
|
|
site = Site.objects.get_current(request)
|
|
|
|
try:
|
|
# can see only permissions for users which are under him in tree
|
|
qs = self.model.objects.subordinate_to_user(request.user, site)
|
|
except NoPermissionsException:
|
|
return self.model.objects.none()
|
|
return qs.filter(can_view=self.show_with_view_permissions)
|
|
|
|
def get_formset(self, request, obj=None, **kwargs):
|
|
"""
|
|
Some fields may be excluded here. User can change only
|
|
permissions which are available for him. E.g. if user does not haves
|
|
can_change flag, he can't change assign can_change permissions.
|
|
"""
|
|
exclude = self.exclude or []
|
|
if obj:
|
|
user = request.user
|
|
if not obj.has_add_permission(user):
|
|
exclude.append('can_add')
|
|
if not obj.has_delete_permission(user):
|
|
exclude.append('can_delete')
|
|
if not obj.has_publish_permission(user):
|
|
exclude.append('can_publish')
|
|
if not obj.has_advanced_settings_permission(user):
|
|
exclude.append('can_change_advanced_settings')
|
|
if not obj.has_move_page_permission(user):
|
|
exclude.append('can_move_page')
|
|
|
|
kwargs['exclude'] = exclude
|
|
self.form._page = obj
|
|
formset_cls = super().get_formset(request, obj=obj, **kwargs)
|
|
qs = self.get_queryset(request)
|
|
if obj is not None:
|
|
qs = qs.filter(page=obj)
|
|
formset_cls._queryset = qs
|
|
return formset_cls
|
|
|
|
|
|
class ViewRestrictionInlineAdmin(PagePermissionInlineAdmin):
|
|
extra = 0 # edit page load time boost
|
|
form = ViewRestrictionInlineAdminForm
|
|
verbose_name = _("View restriction")
|
|
verbose_name_plural = _("View restrictions")
|
|
show_with_view_permissions = True
|
|
|
|
|
|
class GlobalPagePermissionAdmin(admin.ModelAdmin):
|
|
list_display = ['user', 'group', 'can_change', 'can_delete', 'can_publish', 'can_change_permissions']
|
|
list_filter = ['user', 'group', 'can_change', 'can_delete', 'can_publish', 'can_change_permissions']
|
|
|
|
form = GlobalPagePermissionAdminForm
|
|
search_fields = []
|
|
for field in admin_class.search_fields:
|
|
search_fields.append("user__%s" % field)
|
|
search_fields.append('group__name')
|
|
|
|
list_display.append('can_change_advanced_settings')
|
|
list_filter.append('can_change_advanced_settings')
|
|
|
|
def get_list_filter(self, request):
|
|
threshold = get_cms_setting('RAW_ID_USERS')
|
|
try:
|
|
threshold = threshold and get_user_model().objects.count() > threshold
|
|
except (OperationalError, ProgrammingError):
|
|
threshold = False
|
|
filter_copy = deepcopy(self.list_filter)
|
|
if threshold:
|
|
filter_copy.remove('user')
|
|
return filter_copy
|
|
|
|
def get_exclude(self, request, obj=None):
|
|
"""Offer only the ``can_*`` flags the acting user may hand out.
|
|
|
|
Mirrors ``PagePermissionInlineAdmin.get_formset``: a manager must not be
|
|
able to grant -- to themselves or anyone else -- a right they do not hold
|
|
(CWE-269). Excluding the field keeps it out of the rendered form *and*
|
|
out of a crafted POST. Whether a flag the manager holds on some site may
|
|
be granted for the sites actually picked is settled in the form's
|
|
``clean()``, once ``sites`` is known.
|
|
"""
|
|
exclude = list(super().get_exclude(request, obj) or [])
|
|
grantable = permissions.get_grantable_global_permissions(request.user)
|
|
exclude.extend(
|
|
field for field in GlobalPagePermission.get_all_permissions()
|
|
if field not in grantable and field not in exclude
|
|
)
|
|
return exclude
|
|
|
|
def get_form(self, request, obj=None, **kwargs):
|
|
form_class = super().get_form(request, obj, **kwargs)
|
|
# The form caps each granted flag to the sites the manager holds it on,
|
|
# so it needs to know who is acting. ``modelform_factory`` returns a
|
|
# fresh subclass per call, so this is per-request state rather than
|
|
# shared mutation of the declared form class.
|
|
form_class._current_user = request.user
|
|
return form_class
|
|
|
|
def has_add_permission(self, request):
|
|
site = Site.objects.get_current(request)
|
|
return permissions.user_can_add_global_permissions(request.user, site)
|
|
|
|
def has_change_permission(self, request, obj=None):
|
|
site = Site.objects.get_current(request)
|
|
return (
|
|
permissions.user_can_change_global_permissions(request.user, site)
|
|
and self._can_manage_grant(request, obj)
|
|
)
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
site = Site.objects.get_current(request)
|
|
return (
|
|
permissions.user_can_delete_global_permissions(request.user, site)
|
|
and self._can_manage_grant(request, obj)
|
|
)
|
|
|
|
def _can_manage_grant(self, request, obj):
|
|
"""Only let a manager edit or delete a grant they could have created.
|
|
|
|
A grant for a site the manager may not manage permissions on, or holding
|
|
flags they may not hand out on its sites, is out of reach: its ``user``,
|
|
``group`` and ``sites`` would otherwise let them move those flags to
|
|
themselves or to other sites (CWE-269).
|
|
"""
|
|
if obj is None:
|
|
return True
|
|
held = {flag for flag in GlobalPagePermission.get_all_permissions() if getattr(obj, flag)}
|
|
site_ids = list(obj.sites.values_list("pk", flat=True))
|
|
grantable = permissions.get_grantable_global_permissions(request.user, site_ids)
|
|
return "can_change_permissions" in grantable and held <= grantable
|
|
|
|
@classproperty
|
|
def raw_id_fields(cls):
|
|
# Dynamically set raw_id_fields based on settings
|
|
threshold = get_cms_setting('RAW_ID_USERS')
|
|
|
|
# Given a fresh django-cms install and a django settings with the
|
|
# CMS_RAW_ID_USERS = CMS_PERMISSION = True
|
|
# django throws an OperationalError (sqlite) or ProgrammingError
|
|
# (postgres) when running ./manage migrate because the user table
|
|
# doesn't exist yet.
|
|
try:
|
|
threshold = threshold and get_user_model().objects.count() > threshold
|
|
except (OperationalError, ProgrammingError):
|
|
threshold = False
|
|
|
|
return ['user'] if threshold else []
|
|
|
|
|
|
if get_cms_setting('PERMISSION'):
|
|
admin.site.register(GlobalPagePermission, GlobalPagePermissionAdmin)
|
|
PERMISSION_ADMIN_INLINES.extend([
|
|
ViewRestrictionInlineAdmin,
|
|
PagePermissionInlineAdmin,
|
|
])
|