diff --git a/cms/admin/pageadmin.py b/cms/admin/pageadmin.py index e7758bfd2b4..82cb14e228f 100644 --- a/cms/admin/pageadmin.py +++ b/cms/admin/pageadmin.py @@ -646,13 +646,14 @@ def get_permissions(self, request, page_id): ) if not can_change_global_permissions: - allowed_pages = frozenset(page_permissions.get_change_id_list(user, site, check_global=False)) + allowed_pages = page_permissions.get_change_perm_tuples(user, site, check_global=False) for permission in _page_permissions.iterator(): if can_change_global_permissions: can_change = True else: - can_change = permission.page_id in allowed_pages + page_path = permission.page.node.path + can_change = any(perm_tuple.contains(page_path) for perm_tuple in allowed_pages) row = PermissionRow( is_global=False, @@ -1048,7 +1049,7 @@ def has_change_permission(self, request, obj=None): return page_permissions.user_can_change_page(request.user, page=obj.page, site=site) can_change_page = page_permissions.user_can_change_at_least_one_page( user=request.user, - site=get_site(request), + site=site, use_cache=False, ) return can_change_page diff --git a/cms/cms_menus.py b/cms/cms_menus.py index 49abfb17ced..75cb115ef50 100644 --- a/cms/cms_menus.py +++ b/cms/cms_menus.py @@ -2,13 +2,11 @@ from typing import Optional from django.db.models.query import Prefetch, prefetch_related_objects -from django.urls import reverse from django.utils.functional import SimpleLazyObject -from django.utils.translation import override as force_language from cms import constants from cms.apphook_pool import apphook_pool -from cms.models import EmptyPageContent, PageContent, PageUrl +from cms.models import EmptyPageContent, PageContent, PagePermission, PageUrl from cms.toolbar.utils import get_object_preview_url, get_toolbar_from_request from cms.utils.conf import get_cms_setting from cms.utils.i18n import ( @@ -19,7 +17,6 @@ ) from cms.utils.page import get_page_queryset from cms.utils.page_permissions import user_can_view_all_pages -from cms.utils.permissions import get_view_restrictions from menus.base import Menu, Modifier, NavigationNode from menus.menu_pool import menu_pool @@ -29,45 +26,51 @@ def get_visible_nodes(request, pages, site): This code is a many-pages-at-once version of cms.utils.page_permissions.user_can_view_page. `pages` contains all published pages. """ - user = request.user public_for = get_cms_setting("PUBLIC_FOR") - can_see_unrestricted = public_for == "all" or (public_for == "staff" and user.is_staff) + can_see_unrestricted = public_for == "all" or (public_for == "staff" and request.user.is_staff) - if not user.is_authenticated and not can_see_unrestricted: + if not request.user.is_authenticated and not can_see_unrestricted: # User is not authenticated and can't see unrestricted pages, # no need to check for page restrictions because if there's some, # user is anon and if there is not any, user can't see unrestricted. return [] - if user_can_view_all_pages(user, site): + if user_can_view_all_pages(request.user, site): return list(pages) - restricted_pages = get_view_restrictions(pages) - - if not restricted_pages: + if not get_cms_setting('PERMISSION'): # If there's no restrictions, let the user see all pages # only if he can see unrestricted, otherwise return no pages. return list(pages) if can_see_unrestricted else [] - user_id = user.pk - user_groups = SimpleLazyObject(lambda: frozenset(user.groups.values_list("pk", flat=True))) - is_auth_user = user.is_authenticated - - def user_can_see_page(page): - page_permissions = restricted_pages.get(page.pk, []) + restrictions = PagePermission.objects.filter( + page__in=pages, + can_view=True, + ) + restriction_map = {perm.page_id: perm for perm in restrictions} - if not page_permissions: - # Page has no view restrictions, fallback to the project's - # CMS_PUBLIC_FOR setting. - return can_see_unrestricted + user_id = request.user.pk + user_groups = SimpleLazyObject(lambda: frozenset(request.user.groups.values_list("pk", flat=True))) + is_auth_user = request.user.is_authenticated - if not is_auth_user: - return False + def user_can_see_page(page): + if page.pk in restriction_map: + # set internal fk cache to our page with loaded ancestors and descendants + PagePermission.page.field.set_cached_value(restriction_map[page.pk], page) + + restricted = False + for perm in restrictions: + if perm.get_page_permission_tuple().contains(page.node.path): + if not is_auth_user: + return False + if perm.user_id == user_id or perm.group_id in user_groups: + return True + restricted = True + + # Page has no view restrictions, fallback to the project's + # CMS_PUBLIC_FOR setting. + return can_see_unrestricted and not restricted - for perm in page_permissions: - if perm.user_id == user_id or perm.group_id in user_groups: - return True - return False return [page for page in pages if user_can_see_page(page)] diff --git a/cms/menu_bases.py b/cms/menu_bases.py index f16c4ce1044..7bb0b5edca4 100644 --- a/cms/menu_bases.py +++ b/cms/menu_bases.py @@ -7,7 +7,7 @@ class CMSAttachMenu(Menu): - """Base class that can be subclassed to allow your app to attach its oqn menus.""" + """Base class that can be subclassed to allow your app to attach its own menus.""" cms_enabled = True instance = None name = None diff --git a/cms/models/managers.py b/cms/models/managers.py index dd32afd6d7d..a2cfb02a91d 100644 --- a/cms/models/managers.py +++ b/cms/models/managers.py @@ -286,8 +286,8 @@ def subordinate_to_user(self, user, site): users C,X,D,Y,I,J but not A, because A user in higher in hierarchy. If permission object holds group, this permission object can be visible - to user only if all of the group members are lover in hierarchy. If any - of members is higher then given user, this entry must stay invisible. + to user only if all the group members are lover in hierarchy. If any + of members is higher than given user, this entry must stay invisible. If user is superuser, or haves global can_change_permission permissions, show him everything. @@ -295,7 +295,7 @@ def subordinate_to_user(self, user, site): Result of this is used in admin for page permissions inline. """ # get user level - from cms.utils.page_permissions import get_change_permissions_id_list + from cms.utils.page_permissions import get_change_permissions_perm_tuples from cms.utils.permissions import get_user_permission_level try: @@ -307,12 +307,15 @@ def subordinate_to_user(self, user, site): return self.all() # get all permissions - page_id_allow_list = get_change_permissions_id_list(user, site, check_global=False) + from cms.models import PermissionTuple + allow_list = Q() + for perm_tuple in get_change_permissions_perm_tuples(user, site, check_global=False): + allow_list |= PermissionTuple(perm_tuple).allow_list("page__node") # get permission set, but without objects targeting user, or any group # in which he can be qs = self.filter( - page__id__in=page_id_allow_list, + allow_list, page__node__depth__gte=user_level, ) qs = qs.exclude(user=user).exclude(group__user=user) diff --git a/cms/models/permissionmodels.py b/cms/models/permissionmodels.py index e54f1e8c9c8..f0dec0f29d4 100644 --- a/cms/models/permissionmodels.py +++ b/cms/models/permissionmodels.py @@ -4,10 +4,11 @@ from django.contrib.sites.models import Site from django.core.exceptions import ImproperlyConfigured, ValidationError from django.db import models +from django.db.models import Q from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ -from cms.models import Page +from cms.models import Page, TreeNode from cms.models.managers import ( GlobalPagePermissionManager, PagePermissionManager, @@ -214,6 +215,38 @@ def __str__(self): return "%s :: GLOBAL" % self.audience +class PermissionTuple(tuple): + def contains(self, path: str, steplen: int = TreeNode.steplen) -> bool: + grant_on, perm_path = self + if grant_on == ACCESS_PAGE: + return path == perm_path + elif grant_on == ACCESS_CHILDREN: + return path.startswith(perm_path) and len(path) == len(perm_path) + steplen + elif grant_on == ACCESS_DESCENDANTS: + return path.startswith(perm_path) and len(path) > len(perm_path) + elif grant_on == ACCESS_PAGE_AND_DESCENDANTS: + return path.startswith(perm_path) + elif grant_on == ACCESS_PAGE_AND_CHILDREN: + return path.startswith(perm_path) and len(path) <= len(perm_path) + steplen + return False + + def allow_list(self, filter: str = "", steplen: int = TreeNode.steplen) -> Q: + if filter !="": + filter = f"{filter}__" + grant_on, path = self + if grant_on == ACCESS_PAGE: + return Q(**{f"{filter}path": path}) + elif grant_on == ACCESS_CHILDREN: + return Q(**{f"{filter}path__startswith": path, f"{filter}__path__length": len(path) + steplen}) + elif grant_on == ACCESS_DESCENDANTS: + return Q(**{f"{filter}path__startswith": path, f"{filter}__path__length__gt": len(path)}) + elif grant_on == ACCESS_PAGE_AND_DESCENDANTS: + return Q(**{f"{filter}path__startswith": path}) + elif grant_on == ACCESS_PAGE_AND_CHILDREN: + return Q(**{f"{filter}path__startswith": path, f"{filter}__path__length__lte": len(path) + steplen}) + return Q() + + class PagePermission(AbstractPagePermission): """Page permissions for a single page """ @@ -236,14 +269,27 @@ def clean(self): if self.can_add and self.grant_on == ACCESS_PAGE: # this is a misconfiguration - user can add/move page to current - # page but after he does this, he will not have permissions to + # page, but after he does this, he will not have permissions to # access this page anymore, so avoid this. message = _("Add page permission requires also access to children, " "or descendants, otherwise added page can't be changed " "by its creator.") raise ValidationError(message) + def get_page_permission_tuple(self): + node = self.page.node + return PermissionTuple((self.grant_on, node.path)) + def get_page_ids(self): + import warnings + + from cms.utils.compat.warnings import RemovedInDjangoCMS43Warning + warnings.warn("get_page_ids is deprecated and will be removed in django CMS 4.3, " + "use get_page_permission_tuple instead", RemovedInDjangoCMS43Warning, stacklevel=2) + + return self._get_page_ids() + + def _get_page_ids(self): if self.grant_on & MASK_PAGE: yield self.page_id diff --git a/cms/templates/admin/cms/page/tree/menu.html b/cms/templates/admin/cms/page/tree/menu.html index f5c6be98eda..63b2b1d4d60 100644 --- a/cms/templates/admin/cms/page/tree/menu.html +++ b/cms/templates/admin/cms/page/tree/menu.html @@ -138,7 +138,7 @@ {% get_admin_url_for_language page preview_language as content_admin_url %} - {% if content_admin_url and page_content %} + {% if has_change_permission and page_content and content_admin_url %} {% endif %} {% trans "Page settings (SHIFT click for advanced settings)" %} - {% if has_change_permission and content_admin_url or has_change_advanced_settings_permission %} + {% if has_change_permission and page_content and content_admin_url %} {% endif %} diff --git a/cms/tests/test_page_admin.py b/cms/tests/test_page_admin.py index fb7f05efcfd..444aa5d59c1 100644 --- a/cms/tests/test_page_admin.py +++ b/cms/tests/test_page_admin.py @@ -1948,7 +1948,7 @@ def test_user_cant_delete_non_empty_page(self): self._add_plugin_to_page(page) self.add_permission(staff_user, 'change_page') - self.add_permission(staff_user, 'delete_page') + self.remove_permission(staff_user, 'delete_page') gp = self.add_global_permission(staff_user, can_change=True, can_delete=True) with self.login_user_context(staff_user): @@ -2066,7 +2066,7 @@ def test_user_cant_delete_non_empty_translation(self): self._add_plugin_to_page(page, language=translation.language) self.add_permission(staff_user, 'change_page') - self.add_permission(staff_user, 'delete_page') + self.remove_permission(staff_user, 'delete_page') self.add_global_permission(staff_user, can_change=True, can_delete=True) with self.login_user_context(staff_user): @@ -3410,8 +3410,8 @@ def test_user_cant_delete_non_empty_page(self): page_perm = self.add_page_permission( staff_user, page, - can_change=True, - can_delete=True, + can_change=False, + can_delete=False, ) with self.login_user_context(staff_user): @@ -3548,7 +3548,7 @@ def test_user_cant_delete_non_empty_translation(self): self.add_page_permission( staff_user, page, - can_change=True, + can_change=False, can_delete=True, ) diff --git a/cms/tests/test_permissions.py b/cms/tests/test_permissions.py index fe32cf6deeb..7579e06af8c 100644 --- a/cms/tests/test_permissions.py +++ b/cms/tests/test_permissions.py @@ -7,10 +7,10 @@ get_permission_cache, set_permission_cache, ) -from cms.models.permissionmodels import GlobalPagePermission +from cms.models.permissionmodels import ACCESS_PAGE_AND_DESCENDANTS, GlobalPagePermission from cms.test_utils.testcases import CMSTestCase from cms.utils.page_permissions import ( - get_change_id_list, + get_change_perm_tuples, user_can_publish_page, ) @@ -59,10 +59,10 @@ def test_permission_manager(self): cached_permissions = get_permission_cache(self.user_normal, "change_page") self.assertIsNone(cached_permissions) - live_permissions = get_change_id_list(self.user_normal, Site.objects.get_current()) + live_permissions = get_change_perm_tuples(self.user_normal, Site.objects.get_current()) cached_permissions_permissions = get_permission_cache(self.user_normal, "change_page") - self.assertEqual(live_permissions, [page_b.id]) + self.assertEqual(live_permissions, [(ACCESS_PAGE_AND_DESCENDANTS, page_b.node.path)]) self.assertEqual(cached_permissions_permissions, live_permissions) def test_cached_permission_precedence(self): diff --git a/cms/tests/test_permmod.py b/cms/tests/test_permmod.py index f1ad9525a04..d45919cdf4f 100644 --- a/cms/tests/test_permmod.py +++ b/cms/tests/test_permmod.py @@ -488,13 +488,12 @@ def test_page_permissions(self): request = self.get_request(user) PagePermission.objects.create(can_view=True, user=user, page=self.page, grant_on=ACCESS_PAGE) - with self.assertNumQueries(6): + with self.assertNumQueries(5): """ The queries are: PagePermission query (is this page restricted) content type lookup (x2) GlobalpagePermission query for user - TreeNode lookup PagePermission query for this user """ self.assertViewAllowed(self.page, user) @@ -506,13 +505,12 @@ def test_page_group_permissions(self): user.groups.add(self.group) request = self.get_request(user) - with self.assertNumQueries(6): + with self.assertNumQueries(5): """ The queries are: PagePermission query (is this page restricted) content type lookup (x2) GlobalpagePermission query for user - TreeNode lookup PagePermission query for user """ self.assertViewAllowed(self.page, user) @@ -540,13 +538,12 @@ def test_basic_perm_denied(self): user = self.get_staff_user_with_no_permissions() request = self.get_request(user) - with self.assertNumQueries(6): + with self.assertNumQueries(5): """ The queries are: PagePermission query (is this page restricted) content type lookup x2 GlobalpagePermission query for user - TreeNode lookup PagePermission query for this user """ self.assertViewNotAllowed(self.page, user) diff --git a/cms/utils/mail.py b/cms/utils/mail.py index 8f6f53e4d72..e6b01055033 100644 --- a/cms/utils/mail.py +++ b/cms/utils/mail.py @@ -14,7 +14,7 @@ def send_mail(subject, txt_template, to, context=None, html_template=None, fail_ context = context or {} context.update({ - 'login_url': "http://%s" % urljoin(site.domain, admin_reverse('index')), + 'login_url': "https://%s" % urljoin(site.domain, admin_reverse('index')), 'title': subject, }) diff --git a/cms/utils/page_permissions.py b/cms/utils/page_permissions.py index cf5796bab36..2d5475c6caa 100644 --- a/cms/utils/page_permissions.py +++ b/cms/utils/page_permissions.py @@ -2,9 +2,10 @@ from cms.cache.permissions import get_permission_cache, set_permission_cache from cms.constants import GRANT_ALL_PERMISSIONS -from cms.models import Page +from cms.models import Page, PermissionTuple from cms.utils import get_current_site from cms.utils.compat.dj import available_attrs +from cms.utils.compat.warnings import RemovedInDjangoCMS43Warning from cms.utils.conf import get_cms_setting from cms.utils.permissions import ( cached_func, @@ -33,15 +34,26 @@ } -def _get_draft_placeholders(page, language): - return page.get_placeholders(language) +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(Placeholder) + 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_ids_for_action(user, site, action, check_global=True, use_cache=True): +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 @@ -62,9 +74,10 @@ def _get_page_ids_for_action(user, site, action, check_global=True, use_cache=Tr return cached page_actions = get_page_actions(user, site) - page_ids = list(page_actions[action]) - set_permission_cache(user, action, page_ids) - return page_ids + # Set cache for all actions calculated + for act, page_paths in page_actions.items(): + set_permission_cache(user, act, list(page_paths)) + return page_actions[action] def auth_permission_required(action): @@ -161,15 +174,10 @@ def user_can_delete_page(user, page, site=None): if not has_perm: return False - for language in page.get_languages(): - placeholders = ( - _get_draft_placeholders(page, language) - .filter(cmsplugin__language=language) - .distinct() - ) - for placeholder in placeholders: - if not placeholder.has_delete_plugins_permission(user, [language]): - return False + placeholders = _get_all_placeholders(page) + for placeholder in placeholders: + if not placeholder.has_delete_plugins_permission(user, [placeholders.source.language]): + return False return True @@ -187,7 +195,7 @@ def user_can_delete_page_translation(user, page, language, site=None): return False placeholders = ( - _get_draft_placeholders(page, language) + _get_all_placeholders(page, language) .filter(cmsplugin__language=language) .distinct() ) @@ -310,13 +318,13 @@ def user_can_change_all_pages(user, site): @auth_permission_required('change_page') def user_can_change_at_least_one_page(user, site, use_cache=True): - page_ids = get_change_id_list( + perm_tuples = get_change_perm_tuples( user=user, site=site, check_global=True, use_cache=use_cache, ) - return page_ids == GRANT_ALL_PERMISSIONS or bool(page_ids) + return perm_tuples == GRANT_ALL_PERMISSIONS or bool(perm_tuples) @cached_func @@ -344,137 +352,207 @@ def user_can_view_all_pages(user, site): return has_global_permission(user, site, action='view_page') -def get_add_id_list(user, site, check_global=True, use_cache=True): +def _perm_tuples_to_ids(perm_tuples): + import inspect + import warnings + + from django.db.models import Q + + fn_name = "_".join(inspect.stack()[1][3].split("_")[:-1]) # Calling function's name + warnings.warn(f"{fn_name}_ids is deprecated. Use {fn_name}_perm_tuples instead.", + RemovedInDjangoCMS43Warning, stacklevel=3) + + allowed_pages = Q() + for perm in perm_tuples: + allowed_pages |= PermissionTuple(perm).allow_list("node") + + return list(Page.objects.filter(allowed_pages).values_list('pk', flat=True)) + + +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. """ - page_ids = _get_page_ids_for_action( + perm_tuples = _get_page_permission_tuples_for_action( user=user, site=site, action='add_page', check_global=check_global, use_cache=use_cache, ) - return page_ids + return perm_tuples + +def get_add_ids(user, site, check_global=True, use_cache=True): + perm_tuples = get_add_perm_tuples(user, site, check_global=check_global, use_cache=use_cache) + return _perm_tuples_to_ids(perm_tuples) -def get_change_id_list(user, site, check_global=True, use_cache=True): + +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. """ - page_ids = _get_page_ids_for_action( + perm_tuples = _get_page_permission_tuples_for_action( user=user, site=site, action='change_page', check_global=check_global, use_cache=use_cache, ) - return page_ids + return perm_tuples + + +def get_change_ids(user, site, check_global=True, use_cache=True): + perm_tuples = get_change_perm_tuples(user, site, check_global=check_global, use_cache=use_cache) + return _perm_tuples_to_ids(perm_tuples) -def get_change_advanced_settings_id_list(user, site, check_global=True, use_cache=True): +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. """ - page_ids = _get_page_ids_for_action( + 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 page_ids + return perm_tuples + + +def get_change_advanced_settings_ids(user, site, check_global=True, use_cache=True): + perm_tuples = get_change_advanced_settings_perm_tuples( + user=user, + site=site, + check_global=check_global, + use_cache=use_cache, + ) + return _perm_tuples_to_ids(perm_tuples) -def get_change_permissions_id_list(user, site, check_global=True, use_cache=True): +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. """ - page_ids = _get_page_ids_for_action( + 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 page_ids + return perm_tuples + + +def get_change_permissions_ids(user, site, check_global=True, use_cache=True): + perm_tuples = get_change_permissions_perm_tuples( + user=user, + site=site, + check_global=check_global, + use_cache=use_cache, + ) + return _perm_tuples_to_ids(perm_tuples) -def get_delete_id_list(user, site, check_global=True, use_cache=True): +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. """ - page_ids = _get_page_ids_for_action( + perm_tuples = _get_page_permission_tuples_for_action( user=user, site=site, action='delete_page', check_global=check_global, use_cache=use_cache, ) - return page_ids + return perm_tuples + +def get_delete_ids(user, site, check_global=True, use_cache=True): + perm_tuples = get_delete_perm_tuples(user, site, check_global=check_global, use_cache=use_cache) + return _perm_tuples_to_ids(perm_tuples) -def get_move_page_id_list(user, site, check_global=True, use_cache=True): + +def get_move_page_perm_tuples(user, site, check_global=True, use_cache=True): """Give a list of pages which user can move. """ - page_ids = _get_page_ids_for_action( + perm_tuples = _get_page_permission_tuples_for_action( user=user, site=site, action='move_page', check_global=check_global, use_cache=use_cache, ) - return page_ids + return perm_tuples + + +def get_move_page_ids(user, site, check_global=True, use_cache=True): + perm_tuples = get_move_page_perm_tuples(user, site, check_global=check_global, use_cache=use_cache) + return _perm_tuples_to_ids(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_publish_id_list(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. - """ - page_ids = _get_page_ids_for_action( - user=user, - site=site, - action='publish_page', - check_global=check_global, - use_cache=use_cache, - ) - return page_ids +def get_publish_ids(user, site, check_global=True, use_cache=True): + perm_tuples = get_publish_perm_tuples(user, site, check_global=check_global, use_cache=use_cache) + return _perm_tuples_to_ids(perm_tuples) -def get_view_id_list(user, site, check_global=True, use_cache=True): +def get_view_perm_tuples(user, site, check_global=True, use_cache=True): """Give a list of pages which user can view. """ - page_ids = _get_page_ids_for_action( + perm_tuples = _get_page_permission_tuples_for_action( user=user, site=site, action='view_page', check_global=check_global, use_cache=use_cache, ) - return page_ids + return perm_tuples + +def get_view_ids(user, site, check_global=True, use_cache=True): + perm_tuples = get_view_perm_tuples(user, site, check_global=check_global, use_cache=use_cache) + return _perm_tuples_to_ids(perm_tuples) -def has_generic_permission(page, user, action, site=None, check_global=True): + +def has_generic_permission(page, user, action, site=None, check_global=True, use_cache=True): if site is None: site = get_current_site() - page_id = page.pk + page_path = page.node.path actions_map = { - 'add_page': get_add_id_list, - 'change_page': get_change_id_list, - 'change_page_advanced_settings': get_change_advanced_settings_id_list, - 'change_page_permissions': get_change_permissions_id_list, - 'delete_page': get_delete_id_list, - 'delete_page_translation': get_delete_id_list, - 'publish_page': get_publish_id_list, - 'move_page': get_move_page_id_list, - 'view_page': get_view_id_list, + '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_ids = func(user, site, check_global=check_global) - return page_ids == GRANT_ALL_PERMISSIONS or page_id in page_ids + + 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 + ) diff --git a/cms/utils/permissions.py b/cms/utils/permissions.py index e7b175b3349..c0277f8cfa2 100644 --- a/cms/utils/permissions.py +++ b/cms/utils/permissions.py @@ -1,3 +1,4 @@ +import warnings from collections import defaultdict from contextlib import contextmanager from functools import lru_cache, wraps @@ -173,37 +174,20 @@ def get_global_actions_for_user(user, site): @cached_func def get_page_actions_for_user(user, site): - actions = defaultdict(set) - pages = ( - Page - .objects - .on_site(site) - .select_related('node') - .order_by('node__path') - ) - nodes = [page.node for page in pages] - pages_by_id = {} - - for page in pages: - if page.node.is_root(): - page.node._set_hierarchy(nodes) - page.node.__dict__['item'] = page - pages_by_id[page.pk] = page + actions = defaultdict(list) page_permissions = ( PagePermission .objects .with_user(user) - .filter(page__in=pages_by_id) + .select_related('page__node') + .filter(page__node__site=site) ) for perm in page_permissions.iterator(): - PagePermission.page.field.set_cached_value(perm, pages_by_id[perm.page_id]) - - page_ids = frozenset(perm.get_page_ids()) - + permission_tuple = perm.grant_on, perm.page.node.path for action in perm.get_configured_actions(): - actions[action].update(page_ids) + actions[action].append(permission_tuple) return actions @@ -216,11 +200,16 @@ def has_global_permission(user, site, action, use_cache=True): def has_page_permission(user, page, action, use_cache=True): - if use_cache: - actions = get_page_actions_for_user(user, page.node.site) - else: - actions = get_page_actions_for_user.without_cache(user, page.node.site) - return page.pk in actions[action] + import warnings + + from cms.utils.compat.warnings import RemovedInDjangoCMS43Warning + from cms.utils.page_permissions import has_generic_permission + + warnings.warn("has_page_permission is deprecated and will be removed in django CMS 4.3. " + "Use cms.utils.page_permissions.has_generic_permission instead.", + RemovedInDjangoCMS43Warning, stacklevel=2) + + return has_generic_permission(page, user, action, site=None, check_global=False, use_cache=use_cache) def get_subordinate_users(user, site): @@ -253,7 +242,7 @@ def get_subordinate_users(user, site): Will return [user, C, X, D, Y, Z]. W was created by user, but is also assigned to higher level. """ - from cms.utils.page_permissions import get_change_permissions_id_list + from cms.utils.page_permissions import get_change_permissions_perm_tuples try: user_level = get_user_permission_level(user, site) @@ -270,12 +259,15 @@ def get_subordinate_users(user, site): if user_level == ROOT_USER_LEVEL: return get_user_model().objects.all() - page_id_allow_list = get_change_permissions_id_list(user, site, check_global=False) + from cms.models import PermissionTuple + allow_list = Q() + for perm_tuple in get_change_permissions_perm_tuples(user, site, check_global=False): + allow_list |= PermissionTuple(perm_tuple).allow_list("pagepermission__page__node") # normal query qs = get_user_model().objects.distinct().filter( Q(is_staff=True) & ( - Q(pagepermission__page__id__in=page_id_allow_list) & Q(pagepermission__page__node__depth__gte=user_level) + allow_list & Q(pagepermission__page__node__depth__gte=user_level) ) | ( Q(pageuser__created_by=user) & Q(pagepermission__page=None) ) @@ -289,7 +281,7 @@ def get_subordinate_groups(user, site): Similar to get_subordinate_users, but returns queryset of Groups instead of Users. """ - from cms.utils.page_permissions import get_change_permissions_id_list + from cms.utils.page_permissions import get_change_permissions_perm_tuples try: user_level = get_user_permission_level(user, site) @@ -312,11 +304,14 @@ def get_subordinate_groups(user, site): if user_level == ROOT_USER_LEVEL: return Group.objects.all() - page_id_allow_list = get_change_permissions_id_list(user, site, check_global=False) + from cms.models import PermissionTuple + allow_list = Q() + for perm_tuple in get_change_permissions_perm_tuples(user, site, check_global=False): + allow_list |= PermissionTuple(perm_tuple).allow_list("pagepermission__page__node") return Group.objects.distinct().filter( ( - Q(pagepermission__page__id__in=page_id_allow_list) & Q(pagepermission__page__node__depth__gte=user_level) + allow_list & Q(pagepermission__page__node__depth__gte=user_level) ) | ( Q(pageusergroup__created_by=user) & Q(pagepermission__page__isnull=True) ) @@ -327,6 +322,12 @@ def get_view_restrictions(pages): """ Load all view restrictions for the pages """ + + from cms.utils.compat.warnings import RemovedInDjangoCMS43Warning + + warnings.warn("get_view_restrictions will be removed in django CMS 4.3", + RemovedInDjangoCMS43Warning, stacklevel=2) + restricted_pages = defaultdict(list) if not get_cms_setting('PERMISSION'): @@ -354,7 +355,7 @@ def get_view_restrictions(pages): # set internal fk cache to our page with loaded ancestors and descendants PagePermission.page.field.set_cached_value(perm, pages_by_id[perm.page_id]) - for page_id in perm.get_page_ids(): + for page_id in perm._get_page_ids(): restricted_pages[page_id].append(perm) return restricted_pages