From 128b319b9990c01ddcda3f827cf9961a050971ac Mon Sep 17 00:00:00 2001
From: Thijs Kramer <thijskramer@gmail.com>
Date: Fri, 18 Feb 2022 08:50:39 +0100
Subject: [PATCH] Fix permission error when sorting pages having page type
 restrictions

---
 CHANGELOG.txt                                 |   1 +
 docs/releases/2.16.2.md                       |   1 +
 wagtail/actions/move_page.py                  |  25 ++-
 wagtail/admin/tests/pages/test_move_page.py   |   6 -
 .../admin/tests/pages/test_reorder_page.py    | 142 ++++++++++++++++++
 5 files changed, 156 insertions(+), 19 deletions(-)
 create mode 100644 wagtail/admin/tests/pages/test_reorder_page.py

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index aef79ee3d0..84f7e3d508 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -44,6 +44,7 @@ Changelog
 ~~~~~~~~~~~~~~~~~~~
 
  * Fix: Update django-treebeard dependency to 4.5.1 or above (Serafeim Papastefanos)
+ * Fix: Fix permission error when sorting pages having page type restrictions (Thijs Kramer)
 
 
 2.16.1 (11.02.2022)
diff --git a/docs/releases/2.16.2.md b/docs/releases/2.16.2.md
index cad4026898..52fdb83151 100644
--- a/docs/releases/2.16.2.md
+++ b/docs/releases/2.16.2.md
@@ -12,3 +12,4 @@ depth: 1
 ### Bug fixes
 
  * Update django-treebeard dependency to 4.5.1 or above (Serafeim Papastefanos)
+ * Fix permission error when sorting pages having page type restrictions (Thijs Kramer)
diff --git a/wagtail/actions/move_page.py b/wagtail/actions/move_page.py
index 57feba5ef8..6ffed3bc53 100644
--- a/wagtail/actions/move_page.py
+++ b/wagtail/actions/move_page.py
@@ -25,25 +25,19 @@ class MovePageAction:
         self.pos = pos
         self.user = user
 
-    def check(self, skip_permission_checks=False):
+    def check(self, parent_after, skip_permission_checks=False):
         if self.user and not skip_permission_checks:
-            if not self.page.permissions_for_user(self.user).can_move_to(self.target):
+            if not self.page.permissions_for_user(self.user).can_move_to(parent_after):
                 raise MovePagePermissionError(
                     "You do not have permission to move the page to the target specified."
                 )
 
-    def _move_page(self, page, target, pos=None):
+    def _move_page(self, page, target, parent_after):
         from wagtail.models import Page
 
-        # Determine old and new parents
-        parent_before = page.get_parent()
-        if pos in ("first-child", "last-child", "sorted-child"):
-            parent_after = target
-        else:
-            parent_after = target.get_parent()
-
         # Determine old and new url_paths
         # Fetching new object to avoid affecting `page`
+        parent_before = page.get_parent()
         old_page = Page.objects.get(id=page.id)
         old_url_path = old_page.url_path
         new_url_path = old_page.set_url_path(parent=parent_after)
@@ -62,7 +56,7 @@ class MovePageAction:
         # Only commit when all descendants are properly updated
         with transaction.atomic():
             # Allow treebeard to update `path` values
-            MP_MoveHandler(page, target, pos).process()
+            MP_MoveHandler(page, target, self.pos).process()
 
             # Treebeard's move method doesn't actually update the in-memory instance,
             # so we need to work with a freshly loaded one now
@@ -103,6 +97,11 @@ class MovePageAction:
         logger.info('Page moved: "%s" id=%d path=%s', page.title, page.id, new_url_path)
 
     def execute(self, skip_permission_checks=False):
-        self.check(skip_permission_checks=skip_permission_checks)
+        if self.pos in ("first-child", "last-child", "sorted-child"):
+            parent_after = self.target
+        else:
+            parent_after = self.target.get_parent()
 
-        return self._move_page(self.page, self.target, pos=self.pos)
+        self.check(parent_after, skip_permission_checks=skip_permission_checks)
+
+        return self._move_page(self.page, self.target, parent_after)
diff --git a/wagtail/admin/tests/pages/test_move_page.py b/wagtail/admin/tests/pages/test_move_page.py
index f51e20ad5a..9022a979b3 100644
--- a/wagtail/admin/tests/pages/test_move_page.py
+++ b/wagtail/admin/tests/pages/test_move_page.py
@@ -196,12 +196,6 @@ class TestPageMove(TestCase, WagtailTestUtils):
             )
         )
 
-    def test_page_set_page_position(self):
-        response = self.client.get(
-            reverse("wagtailadmin_pages:set_page_position", args=(self.test_page_a.id,))
-        )
-        self.assertEqual(response.status_code, 200)
-
     def test_before_move_page_hook(self):
         def hook_func(request, page, destination):
             self.assertIsInstance(request, HttpRequest)
diff --git a/wagtail/admin/tests/pages/test_reorder_page.py b/wagtail/admin/tests/pages/test_reorder_page.py
new file mode 100644
index 0000000000..a4dce82350
--- /dev/null
+++ b/wagtail/admin/tests/pages/test_reorder_page.py
@@ -0,0 +1,142 @@
+from django.test import TestCase
+from django.urls import reverse
+
+from wagtail.core.models import Page
+from wagtail.test.testapp.models import BusinessChild, BusinessIndex, SimplePage
+from wagtail.tests.utils import WagtailTestUtils
+
+
+class TestPageReorder(TestCase, WagtailTestUtils):
+    fixtures = ["test.json"]
+
+    def __init__(self, methodName: str = ...) -> None:
+        super().__init__(methodName)
+
+    def setUp(self):
+        # Find root page
+        self.root_page = Page.objects.get(id=2)
+
+        # root
+        # |- simple_index (SimplePage)
+        # |  |- simple_child_1 (SimplePage)
+        # |  |- simple_child_2 (SimplePage)
+        # |  |- simple_child_3 (SimplePage)
+
+        self.index_page = SimplePage(title="Simple", slug="simple", content="hello")
+        self.root_page.add_child(instance=self.index_page)
+
+        self.child_1 = SimplePage(
+            title="Child 1 of SimplePage", slug="child-1", content="hello"
+        )
+        self.index_page.add_child(instance=self.child_1)
+        self.child_2 = SimplePage(
+            title="Child 2 of SimplePage", slug="child-2", content="hello"
+        )
+        self.index_page.add_child(instance=self.child_2)
+        self.child_3 = SimplePage(
+            title="Child 3 of SimplePage", slug="child-3", content="hello"
+        )
+        self.index_page.add_child(instance=self.child_3)
+
+        # Login
+        self.user = self.login()
+
+    def test_page_set_page_position_get_request_with_simple_page(self):
+        """
+        Test that GET requests to set_page_position view don't alter the page order.
+        """
+        response = self.client.get(
+            reverse("wagtailadmin_pages:set_page_position", args=(self.child_1.id,))
+        )
+        self.assertEqual(response.status_code, 200)
+
+        # Ensure page order does not change:
+        child_slugs = self.index_page.get_children().values_list("slug", flat=True)
+        self.assertListEqual(list(child_slugs), ["child-1", "child-2", "child-3"])
+
+    def test_page_set_page_position_without_position_argument_moves_it_to_the_end(self):
+        response = self.client.post(
+            reverse("wagtailadmin_pages:set_page_position", args=(self.child_1.id,))
+        )
+        self.assertEqual(response.status_code, 200)
+
+        # check if child_1 is the last child page:
+        child_slugs = self.index_page.get_children().values_list("slug", flat=True)
+        self.assertListEqual(list(child_slugs), ["child-2", "child-3", "child-1"])
+
+    def test_page_move_page_position_up(self):
+        """Moves child 3 to the first position."""
+        response = self.client.post(
+            reverse("wagtailadmin_pages:set_page_position", args=(self.child_3.id,))
+            + "?position=0"
+        )
+        self.assertEqual(response.status_code, 200)
+        # check if child_3 is the first child page:
+        child_slugs = self.index_page.get_children().values_list("slug", flat=True)
+        self.assertListEqual(list(child_slugs), ["child-3", "child-1", "child-2"])
+
+    def test_page_move_page_position_down(self):
+        """
+        Moves child 3 to the first position."""
+        response = self.client.post(
+            reverse("wagtailadmin_pages:set_page_position", args=(self.child_1.id,))
+            + "?position=1"
+        )
+        self.assertEqual(response.status_code, 200)
+        # check if child_1 is the second child page:
+        child_slugs = self.index_page.get_children().values_list("slug", flat=True)
+        self.assertListEqual(list(child_slugs), ["child-2", "child-1", "child-3"])
+
+    def test_page_move_page_position_to_the_same_position(self):
+        """
+        Moves child 3 to the first position."""
+        response = self.client.post(
+            reverse("wagtailadmin_pages:set_page_position", args=(self.child_1.id,))
+            + "?position=0"
+        )
+        self.assertEqual(response.status_code, 200)
+        # Ensure page order does not change:
+        child_slugs = self.index_page.get_children().values_list("slug", flat=True)
+        self.assertListEqual(list(child_slugs), ["child-1", "child-2", "child-3"])
+
+    def test_page_set_page_position_with_invalid_target_position(self):
+        response = self.client.post(
+            reverse("wagtailadmin_pages:set_page_position", args=(self.child_3.id,))
+            + "?position=99"
+        )
+        self.assertEqual(response.status_code, 200)
+
+        # Ensure page order does not change:
+        child_slugs = self.index_page.get_children().values_list("slug", flat=True)
+        self.assertListEqual(list(child_slugs), ["child-1", "child-2", "child-3"])
+
+
+class TestPageReorderWithParentPageRestrictions(TestPageReorder):
+    """
+    This testCase is the same as the TestPageReorder class above, but with a different
+    page type: BusinessChild has a parent_page_types restriction.
+    This ensures that this restriction doesn't affect the ability to reorder pages.
+    """
+
+    def setUp(self):
+        # Find root page
+        self.root_page = Page.objects.get(id=2)
+
+        # root
+        # |- index_page (BusinessIndex)
+        # |  |- child_1 (BusinessChild)
+        # |  |- child_2 (BusinessChild)
+        # |  |- child_3 (BusinessChild)
+
+        self.index_page = BusinessIndex(title="Simple", slug="simple")
+        self.root_page.add_child(instance=self.index_page)
+
+        self.child_1 = BusinessChild(title="Child 1 of BusinessIndex", slug="child-1")
+        self.index_page.add_child(instance=self.child_1)
+        self.child_2 = BusinessChild(title="Child 2 of BusinessIndex", slug="child-2")
+        self.index_page.add_child(instance=self.child_2)
+        self.child_3 = BusinessChild(title="Child 3 of BusinessIndex", slug="child-3")
+        self.index_page.add_child(instance=self.child_3)
+
+        # Login
+        self.user = self.login()