forked from ungleich-public/dynamicweb
569 lines
17 KiB
Python
569 lines
17 KiB
Python
from functools import wraps
|
|
|
|
from cms.cache.permissions import get_permission_cache, set_permission_cache
|
|
from cms.constants import GRANT_ALL_PERMISSIONS
|
|
from cms.models import Page, PermissionTuple
|
|
from cms.utils.compat.dj import available_attrs
|
|
from cms.utils.conf import get_cms_setting
|
|
from cms.utils.permissions import (
|
|
cached_func,
|
|
get_model_permission_codename,
|
|
get_page_actions_for_user,
|
|
has_global_permission,
|
|
)
|
|
|
|
PAGE_ADD_CODENAME = get_model_permission_codename(Page, 'add')
|
|
PAGE_CHANGE_CODENAME = get_model_permission_codename(Page, 'change')
|
|
PAGE_DELETE_CODENAME = get_model_permission_codename(Page, 'delete')
|
|
PAGE_PUBLISH_CODENAME = get_model_permission_codename(Page, 'publish')
|
|
PAGE_VIEW_CODENAME = get_model_permission_codename(Page, 'view')
|
|
|
|
|
|
# Maps an action to the required Django auth permission codes
|
|
_django_permissions_by_action = {
|
|
'add_page': [PAGE_ADD_CODENAME, PAGE_CHANGE_CODENAME],
|
|
'change_page': [PAGE_CHANGE_CODENAME],
|
|
'change_page_advanced_settings': [PAGE_CHANGE_CODENAME],
|
|
'change_page_permissions': [PAGE_CHANGE_CODENAME],
|
|
'delete_page': [PAGE_CHANGE_CODENAME, PAGE_DELETE_CODENAME],
|
|
'delete_page_translation': [PAGE_CHANGE_CODENAME, PAGE_DELETE_CODENAME],
|
|
'move_page': [PAGE_CHANGE_CODENAME],
|
|
'publish_page': [PAGE_CHANGE_CODENAME, PAGE_PUBLISH_CODENAME],
|
|
}
|
|
|
|
|
|
def _get_all_placeholders(page, language=None):
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
|
from cms.models import PageContent, Placeholder
|
|
|
|
page_contents = PageContent.admin_manager.filter(page=page)
|
|
if language:
|
|
page_contents = page_contents.filter(language=language)
|
|
content_type = ContentType.objects.get_for_model(PageContent)
|
|
return Placeholder.objects.filter(
|
|
content_type=content_type,
|
|
object_id__in=page_contents.values_list('pk', flat=True)
|
|
)
|
|
|
|
|
|
def _check_delete_translation(user, page, language, site=None):
|
|
return user_can_change_page(user, page, site=site)
|
|
|
|
|
|
def _get_page_permission_tuples_for_action(user, site, action, check_global=True, use_cache=True):
|
|
if user.is_superuser or not get_cms_setting('PERMISSION'):
|
|
# got superuser, or permissions aren't enabled?
|
|
# just return grant all mark
|
|
return GRANT_ALL_PERMISSIONS
|
|
|
|
if check_global and has_global_permission(user, site, action=action, use_cache=use_cache):
|
|
return GRANT_ALL_PERMISSIONS
|
|
|
|
if use_cache:
|
|
# read from cache if possible
|
|
cached = get_permission_cache(user, site, action)
|
|
get_page_actions = get_page_actions_for_user
|
|
else:
|
|
cached = None
|
|
get_page_actions = get_page_actions_for_user.without_cache
|
|
|
|
if cached is not None:
|
|
return cached
|
|
|
|
page_actions = get_page_actions(user, site)
|
|
# Set cache for all actions calculated
|
|
for act, page_paths in page_actions.items():
|
|
set_permission_cache(user, site, act, list(page_paths))
|
|
return page_actions[action]
|
|
|
|
|
|
def auth_permission_required(action):
|
|
def decorator(func):
|
|
@wraps(func, assigned=available_attrs(func))
|
|
def wrapper(user, *args, **kwargs):
|
|
if not user.is_authenticated:
|
|
return False
|
|
|
|
permissions = _django_permissions_by_action[action]
|
|
|
|
if not user.has_perms(permissions):
|
|
# Fail fast if the user does not have permissions
|
|
# in Django to perform the action.
|
|
return False
|
|
|
|
permissions_enabled = get_cms_setting('PERMISSION')
|
|
|
|
if not user.is_superuser and permissions_enabled:
|
|
return func(user, *args, **kwargs)
|
|
return True
|
|
return wrapper
|
|
return decorator
|
|
|
|
|
|
def change_permission_required(func):
|
|
@wraps(func, assigned=available_attrs(func))
|
|
def wrapper(user, page, site=None):
|
|
if not user_can_change_page(user, page, site=site):
|
|
return False
|
|
return func(user, page, site=site)
|
|
return wrapper
|
|
|
|
|
|
def skip_if_permissions_disabled(func):
|
|
@wraps(func, assigned=available_attrs(func))
|
|
def wrapper(user, page, site=None):
|
|
if not get_cms_setting('PERMISSION'):
|
|
return True
|
|
return func(user, page, site=site)
|
|
return wrapper
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('add_page')
|
|
def user_can_add_page(user, site=None):
|
|
if site is None:
|
|
import warnings
|
|
|
|
from django.contrib.sites.models import Site
|
|
|
|
from cms.utils.compat.warnings import RemovedInDjangoCMS60Warning
|
|
|
|
warnings.warn(
|
|
"user_can_add_page() called without site. This may lead to unexpected behavior. "
|
|
"Use user_can_add_page(user, site) instead.",
|
|
RemovedInDjangoCMS60Warning,
|
|
stacklevel=2,
|
|
)
|
|
site = Site.objects.get_current()
|
|
return has_global_permission(user, site, action='add_page')
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('add_page')
|
|
def user_can_add_subpage(user, target, site=None):
|
|
"""
|
|
Return true if the current user has permission to add a new page
|
|
under target.
|
|
:param user:
|
|
:param target: a Page object
|
|
:param site: optional Site object (not just PK)
|
|
:return: Boolean
|
|
"""
|
|
has_perm = has_generic_permission(
|
|
page=target,
|
|
user=user,
|
|
action='add_page',
|
|
site=site,
|
|
)
|
|
return has_perm
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('change_page')
|
|
def user_can_change_page(user, page, site=None):
|
|
can_change = has_generic_permission(
|
|
page=page,
|
|
user=user,
|
|
action='change_page',
|
|
site=site,
|
|
)
|
|
return can_change
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('delete_page')
|
|
def user_can_delete_page(user, page, site=None):
|
|
has_perm = has_generic_permission(
|
|
page=page,
|
|
user=user,
|
|
action='delete_page',
|
|
site=site,
|
|
)
|
|
|
|
if not has_perm:
|
|
return False
|
|
|
|
placeholders = _get_all_placeholders(page)
|
|
for placeholder in placeholders:
|
|
if not placeholder.has_delete_plugins_permission(user, [placeholder.source.language]):
|
|
return False
|
|
return True
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('delete_page_translation')
|
|
def user_can_delete_page_translation(user, page, language, site=None):
|
|
has_perm = has_generic_permission(
|
|
page=page,
|
|
user=user,
|
|
action='delete_page_translation',
|
|
site=site,
|
|
)
|
|
|
|
if not has_perm:
|
|
return False
|
|
|
|
placeholders = (
|
|
_get_all_placeholders(page, language)
|
|
.filter(cmsplugin__language=language)
|
|
.distinct()
|
|
)
|
|
|
|
for placeholder in placeholders.iterator():
|
|
if not placeholder.has_delete_plugins_permission(user, [language]):
|
|
return False
|
|
return True
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('publish_page')
|
|
def user_can_publish_page(user, page, site=None):
|
|
has_perm = has_generic_permission(
|
|
page=page,
|
|
user=user,
|
|
action='publish_page',
|
|
site=site,
|
|
)
|
|
return has_perm
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('change_page_advanced_settings')
|
|
def user_can_change_page_advanced_settings(user, page, site=None):
|
|
has_perm = has_generic_permission(
|
|
page=page,
|
|
user=user,
|
|
action='change_page_advanced_settings',
|
|
site=site,
|
|
)
|
|
return has_perm
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('change_page_permissions')
|
|
def user_can_change_page_permissions(user, page, site=None):
|
|
has_perm = has_generic_permission(
|
|
page=page,
|
|
user=user,
|
|
action='change_page_permissions',
|
|
site=site,
|
|
)
|
|
return has_perm
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('move_page')
|
|
def user_can_move_page(user, page, site=None):
|
|
has_perm = has_generic_permission(
|
|
page=page,
|
|
user=user,
|
|
action='move_page',
|
|
site=site,
|
|
)
|
|
return has_perm
|
|
|
|
|
|
@cached_func
|
|
def user_can_view_page(user, page, site=None):
|
|
if site is None:
|
|
from django.contrib.sites.models import Site
|
|
|
|
site = Site.objects._get_site_by_id(page.site_id)
|
|
|
|
if user.is_superuser:
|
|
return True
|
|
|
|
public_for = get_cms_setting('PUBLIC_FOR')
|
|
can_see_unrestricted = public_for == 'all' or (public_for == 'staff' and user.is_staff)
|
|
|
|
# inherited and direct view permissions
|
|
is_restricted = page.has_view_restrictions(site)
|
|
|
|
if not is_restricted and can_see_unrestricted:
|
|
# Page has no restrictions and project is configured
|
|
# to allow everyone to see unrestricted pages.
|
|
return True
|
|
elif not user.is_authenticated:
|
|
# Page has restrictions or project is configured
|
|
# to require staff user status to see pages.
|
|
return False
|
|
|
|
if user_can_view_all_pages(user, site=site):
|
|
return True
|
|
|
|
if not is_restricted:
|
|
# Page has no restrictions but user can't see unrestricted pages
|
|
return False
|
|
|
|
if user_can_change_page(user, page):
|
|
# If user has change permissions on a page
|
|
# then he can automatically view it.
|
|
return True
|
|
|
|
has_perm = has_generic_permission(
|
|
page=page,
|
|
user=user,
|
|
action='view_page',
|
|
check_global=False,
|
|
)
|
|
return has_perm
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('change_page')
|
|
def user_can_view_page_draft(user, page, site=None):
|
|
has_perm = has_generic_permission(
|
|
page=page,
|
|
user=user,
|
|
action='change_page',
|
|
site=site,
|
|
)
|
|
return has_perm
|
|
|
|
|
|
@cached_func
|
|
@auth_permission_required('change_page')
|
|
def user_can_change_all_pages(user, site):
|
|
return has_global_permission(user, site, action='change_page')
|
|
|
|
|
|
@auth_permission_required('change_page')
|
|
def user_can_change_at_least_one_page(user, site, use_cache=True):
|
|
perm_tuples = get_change_perm_tuples(
|
|
user=user,
|
|
site=site,
|
|
check_global=True,
|
|
use_cache=use_cache,
|
|
)
|
|
return perm_tuples == GRANT_ALL_PERMISSIONS or bool(perm_tuples)
|
|
|
|
|
|
@cached_func
|
|
def user_can_view_all_pages(user, site):
|
|
if user.is_superuser:
|
|
return True
|
|
|
|
if not get_cms_setting('PERMISSION'):
|
|
public_for = get_cms_setting('PUBLIC_FOR')
|
|
can_see_unrestricted = public_for == 'all' or (public_for == 'staff' and user.is_staff)
|
|
return can_see_unrestricted
|
|
|
|
if not user.is_authenticated:
|
|
return False
|
|
|
|
if user.has_perm(PAGE_VIEW_CODENAME):
|
|
# This is for backwards compatibility.
|
|
# The previous system allowed any user with the explicit view_page
|
|
# permission to see all pages.
|
|
return True
|
|
|
|
if user_can_change_all_pages(user, site):
|
|
# If a user can change all pages then he can see all pages.
|
|
return True
|
|
return has_global_permission(user, site, action='view_page')
|
|
|
|
|
|
def user_can_relocate_descendants(user, page, site, parent_page, keep_restrictions=True):
|
|
"""Allow unreadable descendants only when they stay unreadable at ``parent_page``.
|
|
|
|
The caller checks root view access and destination add access separately.
|
|
``copy_with_descendants`` and ``Page.move_page`` preserve inherited source
|
|
view restrictions. Destination grants can nevertheless widen access: change
|
|
permission also confers view access, even on a restricted page.
|
|
"""
|
|
unreadable = [
|
|
descendant for descendant in page.get_descendant_pages()
|
|
if not user_can_view_page(user, descendant, page.site)
|
|
]
|
|
if not unreadable:
|
|
return True
|
|
if not keep_restrictions:
|
|
return False
|
|
|
|
# Fetch destination permissions without reusing source-site permission caches.
|
|
view_permissions = get_view_perm_tuples(user, site, use_cache=False)
|
|
change_permissions = (
|
|
get_change_perm_tuples(user, site, use_cache=False)
|
|
if user.has_perm(PAGE_CHANGE_CODENAME) else []
|
|
)
|
|
if GRANT_ALL_PERMISSIONS in (view_permissions, change_permissions):
|
|
return False
|
|
|
|
# An all-zero segment cannot identify an existing page. Only permissions
|
|
# inherited from the destination ancestors can match these prospective paths.
|
|
root_path = (parent_page.path if parent_page else '') + '0' * Page.steplen
|
|
destination_permissions = [PermissionTuple(perm) for perm in (*view_permissions, *change_permissions)]
|
|
return not any(
|
|
permission.contains(root_path + descendant.path[len(page.path):])
|
|
for descendant in unreadable
|
|
for permission in destination_permissions
|
|
)
|
|
|
|
|
|
def user_can_copy_descendants(user, page, site, parent_page, copy_permissions):
|
|
"""Whether ``page`` may be copied below ``parent_page`` with its descendants."""
|
|
return user_can_relocate_descendants(
|
|
user, page, site, parent_page, keep_restrictions=copy_permissions
|
|
)
|
|
|
|
|
|
def user_can_move_descendants(user, page, site, parent_page):
|
|
"""Whether ``page`` may be moved below ``parent_page`` with its descendants.
|
|
|
|
A move always preserves the subtree's view restrictions, so only the
|
|
destination's own grants can widen access.
|
|
"""
|
|
return user_can_relocate_descendants(user, page, site, parent_page)
|
|
|
|
|
|
def get_add_perm_tuples(user, site, check_global=True, use_cache=True):
|
|
"""
|
|
Give a list of page where the user has add page rights or the string
|
|
"All" if the user has all rights.
|
|
"""
|
|
perm_tuples = _get_page_permission_tuples_for_action(
|
|
user=user,
|
|
site=site,
|
|
action='add_page',
|
|
check_global=check_global,
|
|
use_cache=use_cache,
|
|
)
|
|
return perm_tuples
|
|
|
|
|
|
def get_change_perm_tuples(user, site, check_global=True, use_cache=True):
|
|
"""
|
|
Give a list of page where the user has edit rights or the string "All" if
|
|
the user has all rights.
|
|
"""
|
|
perm_tuples = _get_page_permission_tuples_for_action(
|
|
user=user,
|
|
site=site,
|
|
action='change_page',
|
|
check_global=check_global,
|
|
use_cache=use_cache,
|
|
)
|
|
return perm_tuples
|
|
|
|
|
|
def get_change_advanced_settings_perm_tuples(user, site, check_global=True, use_cache=True):
|
|
"""
|
|
Give a list of page where the user can change advanced settings or the
|
|
string "All" if the user has all rights.
|
|
"""
|
|
perm_tuples = _get_page_permission_tuples_for_action(
|
|
user=user,
|
|
site=site,
|
|
action='change_page_advanced_settings',
|
|
check_global=check_global,
|
|
use_cache=use_cache,
|
|
)
|
|
return perm_tuples
|
|
|
|
|
|
def get_change_permissions_perm_tuples(user, site, check_global=True, use_cache=True):
|
|
"""Give a list of page where the user can change permissions.
|
|
"""
|
|
perm_tuples = _get_page_permission_tuples_for_action(
|
|
user=user,
|
|
site=site,
|
|
action='change_page_permissions',
|
|
check_global=check_global,
|
|
use_cache=use_cache,
|
|
)
|
|
return perm_tuples
|
|
|
|
|
|
def get_delete_perm_tuples(user, site, check_global=True, use_cache=True):
|
|
"""
|
|
Give a list of page where the user has delete rights or the string "All" if
|
|
the user has all rights.
|
|
"""
|
|
perm_tuples = _get_page_permission_tuples_for_action(
|
|
user=user,
|
|
site=site,
|
|
action='delete_page',
|
|
check_global=check_global,
|
|
use_cache=use_cache,
|
|
)
|
|
return perm_tuples
|
|
|
|
|
|
def get_move_page_perm_tuples(user, site, check_global=True, use_cache=True):
|
|
"""Give a list of pages which user can move.
|
|
"""
|
|
perm_tuples = _get_page_permission_tuples_for_action(
|
|
user=user,
|
|
site=site,
|
|
action='move_page',
|
|
check_global=check_global,
|
|
use_cache=use_cache,
|
|
)
|
|
return perm_tuples
|
|
|
|
|
|
def get_publish_perm_tuples(user, site, check_global=True, use_cache=True):
|
|
"""
|
|
Give a list of page where the user has publish rights or the string "All" if
|
|
the user has all rights.
|
|
"""
|
|
perm_tuples = _get_page_permission_tuples_for_action(
|
|
user=user,
|
|
site=site,
|
|
action='publish_page',
|
|
check_global=check_global,
|
|
use_cache=use_cache,
|
|
)
|
|
return perm_tuples
|
|
|
|
|
|
def get_view_perm_tuples(user, site, check_global=True, use_cache=True):
|
|
"""Give a list of pages which user can view.
|
|
"""
|
|
perm_tuples = _get_page_permission_tuples_for_action(
|
|
user=user,
|
|
site=site,
|
|
action='view_page',
|
|
check_global=check_global,
|
|
use_cache=use_cache,
|
|
)
|
|
return perm_tuples
|
|
|
|
|
|
def has_generic_permission(page, user, action, site=None, check_global=True, use_cache=True):
|
|
from django.contrib.sites.models import Site
|
|
if page is None and site is None:
|
|
import warnings
|
|
|
|
from cms.utils.compat.warnings import RemovedInDjangoCMS60Warning
|
|
|
|
warnings.warn(
|
|
"has_generic_permission() called without site. This may lead to unexpected behavior. "
|
|
"Use has_generic_permission(page, user, action, site) instead.",
|
|
RemovedInDjangoCMS60Warning,
|
|
stacklevel=2,
|
|
)
|
|
site = Site.objects.get_current()
|
|
elif site is None:
|
|
site = Site.objects._get_site_by_id(page.site_id)
|
|
|
|
page_path = page.path
|
|
actions_map = {
|
|
'add_page': get_add_perm_tuples,
|
|
'change_page': get_change_perm_tuples,
|
|
'change_page_advanced_settings': get_change_advanced_settings_perm_tuples,
|
|
'change_page_permissions': get_change_permissions_perm_tuples,
|
|
'delete_page': get_delete_perm_tuples,
|
|
'delete_page_translation': get_delete_perm_tuples,
|
|
'publish_page': get_publish_perm_tuples,
|
|
'move_page': get_move_page_perm_tuples,
|
|
'view_page': get_view_perm_tuples,
|
|
}
|
|
|
|
func = actions_map[action]
|
|
|
|
page_perms = func(user, site, check_global=check_global, use_cache=use_cache)
|
|
return page_perms == GRANT_ALL_PERMISSIONS or any(
|
|
PermissionTuple(perm).contains(page_path) for perm in page_perms
|
|
)
|