from unittest.mock import patch from django.contrib.admin.sites import site from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser, Group, Permission from django.contrib.sites.models import Site from django.db.models import Q from django.test.client import RequestFactory from django.test.utils import override_settings from cms.admin.forms import save_permissions from cms.api import assign_user_to_page, create_page, create_page_user from cms.cms_menus import get_visible_page_contents from cms.models import ACCESS_PAGE, CMSPlugin, Page, PageContent from cms.models.permissionmodels import ( ACCESS_PAGE_AND_DESCENDANTS, GlobalPagePermission, PagePermission, ) from cms.test_utils.testcases import CMSTestCase from cms.test_utils.util.context_managers import override_placeholder_conf from cms.test_utils.util.fuzzy_int import FuzzyInt from cms.utils.page_permissions import user_can_view_page from cms.utils.urlutils import admin_reverse @override_settings(CMS_PERMISSION=True) class PermissionModeratorTests(CMSTestCase): """Permissions and moderator together Fixtures contains 3 users and 1 published page and some other stuff Users: 1. `super`: superuser 2. `master`: user with permissions to all applications 3. `slave`: user assigned to page `slave-home` Pages: 1. `home`: - published page - master can do anything on its subpages, but not on home! 2. `master`: - published page - created by super - `master` can do anything on it and its descendants - subpages: 3. `slave-home`: - not published - assigned slave user which can add/change/delete/ move/publish this page and its descendants - `master` user want to moderate this page and all descendants 4. `pageA`: - created by super - master can add/change/delete on it and descendants """ # TODO: Split this test case into one that tests publish functionality, and # TODO: one that tests permission inheritance. This is too complex. def setUp(self): # create super user self.user_super = self._create_user("super", is_staff=True, is_superuser=True) self.user_staff = self._create_user("staff", is_staff=True, add_default_permissions=True) self.add_permission(self.user_staff, 'publish_page') self.user_master = self._create_user("master", is_staff=True, add_default_permissions=True) self.add_permission(self.user_master, 'publish_page') self.user_slave = self._create_user("slave", is_staff=True, add_default_permissions=True) self.user_normal = self._create_user("normal", is_staff=False) self.user_normal.user_permissions.add(Permission.objects.get(codename='publish_page')) with self.login_user_context(self.user_super): self.home_page = create_page("home", "nav_playground.html", "en", created_by=self.user_super) # master page & master user self.master_page = create_page("master", "nav_playground.html", "en") # create non global, non staff user self.user_non_global = self._create_user("nonglobal") # assign master user under home page assign_user_to_page(self.home_page, self.user_master, grant_on=ACCESS_PAGE_AND_DESCENDANTS, grant_all=True) # and to master page assign_user_to_page(self.master_page, self.user_master, grant_on=ACCESS_PAGE_AND_DESCENDANTS, grant_all=True) # slave page & slave user self.slave_page = create_page("slave-home", "col_two.html", "en", parent=self.master_page, created_by=self.user_super) assign_user_to_page(self.slave_page, self.user_slave, grant_all=True) # create page_b self.page_b = create_page("pageB", "nav_playground.html", "en", created_by=self.user_super) # Normal user # it's allowed for the normal user to view the page assign_user_to_page(self.page_b, self.user_normal, can_view=True) # create page_a - sample page from master page_a = create_page("pageA", "nav_playground.html", "en", created_by=self.user_super) assign_user_to_page(page_a, self.user_master, can_add=True, can_change=True, can_delete=True, can_publish=True, can_move_page=True) def _add_plugin(self, user, page): """ Add a plugin using the test client to check for permissions. """ with self.login_user_context(user): placeholder = page.get_placeholders('en')[0] data = {'name': 'A Link', 'external_link': 'https://www.django-cms.org'} endpoint = self.get_add_plugin_uri( placeholder=placeholder, plugin_type='LinkPlugin', language='en', ) response = self.client.post(endpoint, data) self.assertEqual(response.status_code, 200) return response.content.decode('utf8') def test_super_can_add_page_to_root(self): with self.login_user_context(self.user_super): response = self.client.get(self.get_page_add_uri('en')) self.assertEqual(response.status_code, 200) def test_master_cannot_add_page_to_root(self): with self.login_user_context(self.user_master): response = self.client.get(self.get_page_add_uri('en')) self.assertEqual(response.status_code, 403) def test_slave_cannot_add_page_to_root(self): with self.login_user_context(self.user_slave): response = self.client.get(self.get_page_add_uri('en')) self.assertEqual(response.status_code, 403) @override_placeholder_conf( { 'col_left': { 'default_plugins': [ { 'plugin_type': 'TextPlugin', 'values': { 'body': 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Culpa, repellendus, ' 'delectus, quo quasi ullam inventore quod quam aut voluptatum aliquam voluptatibus ' 'harum officiis officia nihil minus unde accusamus dolorem repudiandae.' }, }, ] }, }, ) def test_default_plugins(self): with self.login_user_context(self.user_slave): self.assertEqual(CMSPlugin.objects.count(), 0) response = self.client.get(self.slave_page.get_absolute_url(), {'edit': 1}) self.assertEqual(response.status_code, 200) self.assertEqual(CMSPlugin.objects.count(), 1) def test_super_can_add_plugin(self): self._add_plugin(self.user_super, page=self.slave_page) def test_master_can_add_plugin(self): self._add_plugin(self.user_master, page=self.slave_page) def test_slave_can_add_plugin(self): self._add_plugin(self.user_slave, page=self.slave_page) def test_superuser_can_view(self): url = self.page_b.get_absolute_url(language='en') with self.login_user_context(self.user_super): response = self.client.get(url) self.assertEqual(response.status_code, 200) def test_staff_can_view(self): url = self.page_b.get_absolute_url(language='en') all_view_perms = PagePermission.objects.filter(can_view=True) # verify that the user_staff has access to this page has_perm = False for perm in all_view_perms: if perm.page == self.page_b: if perm.user == self.user_staff: has_perm = True self.assertEqual(has_perm, False) login_ok = self.client.login(username=getattr(self.user_staff, get_user_model().USERNAME_FIELD), password=getattr(self.user_staff, get_user_model().USERNAME_FIELD)) self.assertTrue(login_ok) # really logged in self.assertTrue('_auth_user_id' in self.client.session) login_user_id = self.client.session.get('_auth_user_id') user = get_user_model().objects.get(pk=self.user_staff.pk) self.assertEqual(str(login_user_id), str(user.id)) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_user_normal_can_view(self): url = self.page_b.get_absolute_url(language='en') all_view_perms = PagePermission.objects.filter(can_view=True) # verify that the normal_user has access to this page normal_has_perm = False for perm in all_view_perms: if perm.page == self.page_b: if perm.user == self.user_normal: normal_has_perm = True self.assertTrue(normal_has_perm) with self.login_user_context(self.user_normal): response = self.client.get(url) self.assertEqual(response.status_code, 200) # verify that the user_non_global has not access to this page non_global_has_perm = False for perm in all_view_perms: if perm.page == self.page_b: if perm.user == self.user_non_global: non_global_has_perm = True self.assertFalse(non_global_has_perm) with self.login_user_context(self.user_non_global): response = self.client.get(url) self.assertEqual(response.status_code, 404) # non logged in user response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_user_globalpermission(self): # Global user user_global = self._create_user("global") with self.login_user_context(self.user_super): user_global = create_page_user(user_global, user_global) user_global.is_staff = False user_global.save() # Prevent is_staff permission global_page = create_page("global", "nav_playground.html", "en") # Removed call since global page user doesn't have publish permission # global_page = publish_page(global_page, user_global) # it's allowed for the normal user to view the page assign_user_to_page(global_page, user_global, global_permission=True, can_view=True) url = global_page.get_absolute_url('en') all_view_perms = PagePermission.objects.filter(can_view=True) has_perm = False for perm in all_view_perms: if perm.page == self.page_b and perm.user == user_global: has_perm = True self.assertEqual(has_perm, False) global_page_perm_q = Q(user=user_global) & Q(can_view=True) global_view_perms = GlobalPagePermission.objects.filter(global_page_perm_q).exists() self.assertEqual(global_view_perms, True) # user_global with self.login_user_context(user_global): response = self.client.get(url) self.assertEqual(response.status_code, 200) # self.non_user_global has_perm = False for perm in all_view_perms: if perm.page == self.page_b and perm.user == self.user_non_global: has_perm = True self.assertEqual(has_perm, False) global_page_perm_q = Q(user=self.user_non_global) & Q(can_view=True) global_view_perms = GlobalPagePermission.objects.filter(global_page_perm_q).exists() self.assertEqual(global_view_perms, False) with self.login_user_context(self.user_non_global): response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_anonymous_user_public_for_all(self): url = self.page_b.get_absolute_url('en') with self.settings(CMS_PUBLIC_FOR='all'): response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_anonymous_user_public_for_none(self): # default of when to show pages to anonymous user doesn't take # global permissions into account url = self.page_b.get_absolute_url('en') with self.settings(CMS_PUBLIC_FOR=None): response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_page_content_reflects_page_publish_permission(self): """ Test that the page content object gets its publish permission from the page object. """ page = create_page('test', 'nav_playground.html', 'en') page_content = page.get_content_obj("en") user = self.get_standard_user() assign_user_to_page(page, user, can_publish=True) with patch.object(page, "has_publish_permission") as page_has_publish_permission: self.assertTrue(page_content.has_publish_permission(user)) page_has_publish_permission.assert_called_once_with(user) class ViewPermissionBaseTests(CMSTestCase): def setUp(self): self.page = create_page('testpage', 'nav_playground.html', 'en') self.page_content = self.page.get_content_obj("en") self.site = Site.objects.get_current() def get_request(self, user=None): attrs = { 'user': user or AnonymousUser(), 'REQUEST': {}, 'POST': {}, 'GET': {}, 'session': {}, } return type('Request', (object,), attrs) def assertViewAllowed(self, page, user=None): if not user: user = AnonymousUser() self.assertTrue(user_can_view_page(user, page)) def assertViewNotAllowed(self, page, user=None): if not user: user = AnonymousUser() self.assertFalse(user_can_view_page(user, page)) @override_settings( CMS_PERMISSION=False, CMS_PUBLIC_FOR='staff', ) class BasicViewPermissionTests(ViewPermissionBaseTests): """ Test functionality with CMS_PERMISSION set to false, as this is the normal use case """ @override_settings(CMS_PUBLIC_FOR="all") def test_unauth_public(self): request = self.get_request() with self.assertNumQueries(0): self.assertViewAllowed(self.page) self.assertEqual(get_visible_page_contents(request, [self.page_content], self.site), [self.page_content]) def test_unauth_non_access(self): request = self.get_request() with self.assertNumQueries(0): self.assertViewNotAllowed(self.page) self.assertEqual(get_visible_page_contents(request, [self.page_content], self.site), []) @override_settings(CMS_PUBLIC_FOR="all") def test_staff_public_all(self): user = self.get_staff_user_with_no_permissions() request = self.get_request(user) with self.assertNumQueries(0): self.assertViewAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, [self.page_content], self.site), [self.page_content]) def test_staff_public_staff(self): user = self.get_staff_user_with_no_permissions() request = self.get_request(user) with self.assertNumQueries(0): self.assertViewAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, [self.page_content], self.site), [self.page_content]) def test_staff_basic_auth(self): user = self.get_staff_user_with_no_permissions() request = self.get_request(user) with self.assertNumQueries(0): self.assertViewAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, [self.page_content], self.site), [self.page_content]) @override_settings(CMS_PUBLIC_FOR="all") def test_normal_basic_auth(self): user = self.get_standard_user() request = self.get_request(user) with self.assertNumQueries(0): self.assertViewAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, [self.page_content], self.site), [self.page_content]) @override_settings( CMS_PERMISSION=True, CMS_PUBLIC_FOR='none' ) class UnrestrictedViewPermissionTests(ViewPermissionBaseTests): """ Test functionality with CMS_PERMISSION set to True but no restrictions apply to this specific page """ def test_unauth_non_access(self): request = self.get_request() with self.assertNumQueries(1): """ The query is: PagePermission query for the affected page (is the page restricted?) """ self.assertViewNotAllowed(self.page) self.assertEqual(get_visible_page_contents(request, [self.page_content], self.site), []) def test_global_access(self): user = self.get_standard_user() GlobalPagePermission.objects.create(can_view=True, user=user) request = self.get_request(user) with self.assertNumQueries(4): """The queries are: PagePermission query for the affected page (is the page restricted?) Generic django permission lookup content type lookup by permission lookup GlobalPagePermission query for the page site """ self.assertViewAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, [self.page_content], self.site), [self.page_content]) def test_normal_denied(self): user = self.get_standard_user() request = self.get_request(user) with self.assertNumQueries(4): """ The queries are: PagePermission query for the affected page (is the page restricted?) GlobalPagePermission query for the page site User permissions query Content type query """ self.assertViewNotAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, [self.page_content], self.site), []) @override_settings( CMS_PERMISSION=True, CMS_PUBLIC_FOR='all' ) class RestrictedViewPermissionTests(ViewPermissionBaseTests): """ Test functionality with CMS_PERMISSION set to True and view restrictions apply to this specific page """ def setUp(self): super().setUp() self.group = Group.objects.create(name='testgroup') self.pages = [self.page] self.page_contents = [self.page.get_content_obj("en")] self.expected = [self.page.get_content_obj("en")] PagePermission.objects.create(page=self.page, group=self.group, can_view=True, grant_on=ACCESS_PAGE) def test_unauthed(self): request = self.get_request() with self.assertNumQueries(1): """The queries are: PagePermission query for the affected page (is the page restricted?) """ self.assertViewNotAllowed(self.page) self.assertEqual(get_visible_page_contents(request, self.page_contents, self.site), []) def test_page_permissions(self): user = self.get_standard_user() request = self.get_request(user) PagePermission.objects.create(can_view=True, user=user, page=self.page, grant_on=ACCESS_PAGE) with self.assertNumQueries(5): """ The queries are: PagePermission query (is this page restricted) content type lookup (x2) GlobalpagePermission query for user PagePermission query for this user """ self.assertViewAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, self.page_contents, self.site), self.expected) def test_page_group_permissions(self): user = self.get_standard_user() user.groups.add(self.group) request = self.get_request(user) with self.assertNumQueries(5): """ The queries are: PagePermission query (is this page restricted) content type lookup (x2) GlobalpagePermission query for user PagePermission query for user """ self.assertViewAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, self.page_contents, self.site), self.expected) def test_global_permission(self): user = self.get_standard_user() GlobalPagePermission.objects.create(can_view=True, user=user) request = self.get_request(user) with self.assertNumQueries(4): """ The queries are: PagePermission query (is this page restricted) Generic django permission lookup content type lookup by permission lookup GlobalpagePermission query for user """ self.assertViewAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, self.page_contents, self.site), self.expected) def test_basic_perm_denied(self): user = self.get_staff_user_with_no_permissions() request = self.get_request(user) with self.assertNumQueries(5): """ The queries are: PagePermission query (is this page restricted) content type lookup x2 GlobalpagePermission query for user PagePermission query for this user """ self.assertViewNotAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, self.page_contents, self.site), []) def test_basic_perm(self): user = self.get_standard_user() user.user_permissions.add(Permission.objects.get(codename='view_page')) request = self.get_request(user) with self.assertNumQueries(3): """ The queries are: PagePermission query (is this page restricted) Generic django permission lookup content type lookup by permission lookup """ self.assertViewAllowed(self.page, user) self.assertEqual(get_visible_page_contents(request, self.page_contents, self.site), self.expected) class GlobalPermissionTests(CMSTestCase): def test_emulate_admin_index(self): """ Call methods that emulate the adminsite instance's index. This test was basically the reason for the new manager, in light of the problem highlighted in ticket #1120, which asserts that giving a user no site-specific rights when creating a GlobalPagePermission should allow access to all sites. """ # create and then ignore this user. superuser = self._create_user("super", is_staff=True, is_active=True, is_superuser=True) superuser.set_password("super") superuser.save() site_1 = Site.objects.get(pk=1) site_2 = Site.objects.create(domain='example2.com', name='example2.com') SITES = [site_1, site_2] # create 2 staff users USERS = [ self._create_user("staff", is_staff=True, is_active=True), self._create_user("staff_2", is_staff=True, is_active=True), ] for user in USERS: user.set_password('staff') # reuse the same methods the UserPage form does. # Note that it internally calls .save(), as we've not done so. save_permissions({ 'can_add_page': True, 'can_change_page': True, 'can_delete_page': False }, user) GlobalPagePermission.objects.create(can_add=True, can_change=True, can_delete=False, user=USERS[0]) # we're querying here to ensure that even though we've created two users # above, we should have successfully filtered to just one perm. self.assertEqual(1, GlobalPagePermission.objects.with_user(USERS[0]).count()) # this will confirm explicit permissions still work, by adding the first # site instance to the many2many relationship 'sites' GlobalPagePermission.objects.create(can_add=True, can_change=True, can_delete=False, user=USERS[1]).sites.add(SITES[0]) self.assertEqual(1, GlobalPagePermission.objects.with_user(USERS[1]).count()) create_page(title="master", template="nav_playground.html", language="en", in_navigation=True, slug='/') with self.settings(CMS_PERMISSION=True): # for all users, they should have access to site 1 request = RequestFactory().get(path='/') # Fake session request.session = {} request.current_page = None for user in USERS: request.user = user # Note, the query count is inflated by doing additional lookups # because there's a site param in the request. # max_queries = 5 for >dj21 because it's introduce default view permissions max_queries = 5 with self.assertNumQueries(FuzzyInt(3, max_queries)): # internally this calls PageAdmin.has_[add|change|delete|view]_permission() expected_perms = {'add': True, 'change': True, 'delete': False} expected_perms.update({'view': expected_perms['change']}) self.assertEqual(expected_perms, site._registry[PageContent].get_model_perms(request)) # can't use the above loop for this test, as we're testing that # user 1 has access, but user 2 does not, as they are only assigned # to site 1 request = RequestFactory().get(path=f'/?site={site_2.pk}') request.current_page = None request.session = {} # Refresh internal user cache USERS[0] = self.reload(USERS[0]) USERS[1] = self.reload(USERS[1]) # As before, the query count is inflated by doing additional lookups # because there's a site param in the request with self.assertNumQueries(FuzzyInt(5, 15)): # this user shouldn't have access to site 2 request.user = USERS[1] expected_perms = {'add': False, 'change': False, 'delete': False} expected_perms.update({'view': False}) self.assertEqual(expected_perms, site._registry[PageContent].get_model_perms(request)) # but, going back to the first user, they should. request = RequestFactory().get('/', data={'site__exact': site_2.pk}) request.user = USERS[0] request.current_page = None request.session = {} expected_perms = {'add': True, 'change': True, 'delete': False} expected_perms.update({'view': expected_perms['change']}) self.assertEqual(expected_perms, site._registry[PageContent].get_model_perms(request)) def test_has_page_add_permission_with_target(self): page = create_page('Test', 'nav_playground.html', 'en') user = self._create_user('user') request = RequestFactory().get('/', data={'target': page.pk}) request.session = {} request.user = user has_perm = site._registry[Page].has_add_permission(request) self.assertFalse(has_perm) @override_settings(CMS_PERMISSION=True) class AddPageParentPermissionTests(CMSTestCase): """The parent a page is created under is submitted in the POST body, while ``PageAdmin.has_add_permission`` only sees the one in the query string. Both have to be checked, otherwise an editor confined to one subtree can point the permission check at a page they may edit and create the page elsewhere.""" def setUp(self): self.home = create_page('home', 'nav_playground.html', 'en') self.allowed = create_page('allowed', 'nav_playground.html', 'en', parent=self.home) self.forbidden = create_page('forbidden', 'nav_playground.html', 'en', parent=self.home) self.editor = self._create_user('editor', is_staff=True) self.editor.user_permissions.add(*Permission.objects.filter( codename__in=[ 'add_page', 'change_page', 'view_page', 'add_pagecontent', 'change_pagecontent', 'view_pagecontent', ] )) PagePermission.objects.create( page=self.allowed, user=self.editor, can_add=True, can_change=True, grant_on=ACCESS_PAGE_AND_DESCENDANTS, ) def _add_page(self, slug, query_parent=None, body_parent=None): url = admin_reverse('cms_pagecontent_add') + '?language=en' if query_parent: url += f'&parent_page={query_parent.pk}' return self.client.post(url, { 'title': slug, 'slug': slug, 'parent_page': str(body_parent.pk) if body_parent else '', }) def test_add_below_allowed_parent(self): with self.login_user_context(self.editor): response = self._add_page('ok', query_parent=self.allowed, body_parent=self.allowed) self.assertEqual(response.status_code, 302) self.assertEqual(Page.objects.filter(parent=self.allowed).count(), 1) def test_add_below_forbidden_parent_is_denied(self): with self.login_user_context(self.editor): response = self._add_page('nope', query_parent=self.forbidden, body_parent=self.forbidden) self.assertEqual(response.status_code, 403) self.assertFalse(Page.objects.filter(parent=self.forbidden).exists()) def test_forbidden_parent_in_body_is_denied(self): """The query string points at a page the editor may add to, the body at one they may not.""" with self.login_user_context(self.editor): response = self._add_page('nope', query_parent=self.allowed, body_parent=self.forbidden) self.assertContains(response, 'You do not have permission to add a page here.') self.assertFalse(Page.objects.filter(parent=self.forbidden).exists()) def test_empty_parent_in_body_is_denied(self): """An empty parent in the body would create a root page.""" root_pages = Page.objects.filter(parent=None).count() with self.login_user_context(self.editor): response = self._add_page('nope', query_parent=self.allowed, body_parent=None) self.assertContains(response, 'You do not have permission to add a page here.') self.assertEqual(Page.objects.filter(parent=None).count(), root_pages) def test_add_translation_ignores_the_parent(self): """Adding a translation submits no parent, the permission is checked against ``cms_page`` instead. The empty parent must not deny it.""" editor = self._create_user('global_editor', is_staff=True) editor.user_permissions.add(*Permission.objects.filter( codename__in=[ 'add_page', 'change_page', 'view_page', 'add_pagecontent', 'change_pagecontent', 'view_pagecontent', ] )) GlobalPagePermission.objects.create(user=editor, can_add=True, can_change=True) url = admin_reverse('cms_pagecontent_add') + f'?language=de&cms_page={self.allowed.pk}' with self.login_user_context(editor): response = self.client.post(url, { 'title': 'erlaubt', 'slug': 'erlaubt', 'cms_page': str(self.allowed.pk), 'parent_page': '', }) self.assertEqual(response.status_code, 302) self.assertTrue(self.allowed.has_translation('de'))