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 )