diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 82edfbc27..03069f807 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -7,6 +7,7 @@ use BookStack\Activity\Tools\CommentTree; use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; +use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\PageRepo; @@ -95,7 +96,7 @@ class PageController extends Controller } /** - * Show form to continue editing a draft page. + * Show a form to continue editing a draft page. * * @throws NotFoundException */ @@ -103,6 +104,7 @@ class PageController extends Controller { $draft = $this->queries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission(Permission::PageCreate, $draft->getParent()); + $this->ensureDraftAccess($draft); $editorData = new PageEditorData($draft, $this->entityQueries, $request->query('editor', '')); $this->setPageTitle(trans('entities.pages_edit_draft')); @@ -124,6 +126,7 @@ class PageController extends Controller $draftPage = $this->queries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission(Permission::PageCreate, $draftPage->getParent()); + $this->ensureDraftAccess($draftPage); $page = $this->pageRepo->publishDraft($draftPage, $request->all()); @@ -235,6 +238,7 @@ class PageController extends Controller * Save a draft update as a revision. * * @throws NotFoundException + * @throws PermissionsException */ public function saveDraft(Request $request, int $pageId) { @@ -245,6 +249,10 @@ class PageController extends Controller return $this->jsonError(trans('errors.guests_cannot_save_drafts'), 500); } + if ($page->draft) { + $this->ensureDraftAccess($page); + } + $draft = $this->pageRepo->updatePageDraft($page, $request->only(['name', 'html', 'markdown'])); $warnings = (new PageEditActivity($page))->getWarningMessagesForDraft($draft); @@ -294,11 +302,14 @@ class PageController extends Controller * Show the deletion page for the specified page. * * @throws NotFoundException + * @throws PermissionsException */ public function showDeleteDraft(string $bookSlug, int $pageId) { $page = $this->queries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission(Permission::PageUpdate, $page); + $this->ensureDraftAccess($page); + $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()])); $usedAsTemplate = $this->entityQueries->books->start()->where('default_template_id', '=', $page->id)->count() > 0 || @@ -340,7 +351,9 @@ class PageController extends Controller $page = $this->queries->findVisibleByIdOrFail($pageId); $book = $page->book; $chapter = $page->chapter; + $this->checkOwnablePermission(Permission::PageUpdate, $page); + $this->ensureDraftAccess($page); $this->pageRepo->destroy($page); @@ -470,4 +483,14 @@ class PageController extends Controller return redirect($pageCopy->getUrl()); } + + /** + * @throws PermissionsException + */ + protected function ensureDraftAccess(Page $draft): void + { + if (!$draft->draft || $draft->created_by !== user()->id) { + throw new PermissionsException('This page is already published or does not belong to you.'); + } + } } diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 4dfca90a7..375bf1d2b 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -82,10 +82,6 @@ class PageRepo */ public function publishDraft(Page $draft, array $input): Page { - if (!$draft->draft || $draft->created_by !== user()->id) { - throw new PermissionsException('This page is already published or does not belong to you.'); - } - return (new DatabaseTransaction(function () use ($draft, $input) { $draft->draft = false; $draft->revision_count = 1; diff --git a/app/Uploads/Controllers/ImageController.php b/app/Uploads/Controllers/ImageController.php index da67639c1..164d10e6b 100644 --- a/app/Uploads/Controllers/ImageController.php +++ b/app/Uploads/Controllers/ImageController.php @@ -153,7 +153,7 @@ class ImageController extends Controller } /** - * Check related page permission and ensure type is drawio or gallery. + * Check related page permission and ensure the type is drawio or gallery. * @throws NotifyException */ protected function checkImagePermission(Image $image): void diff --git a/tests/Entity/PageDraftTest.php b/tests/Entity/PageDraftTest.php index ff2ba01e1..3f1eff1bd 100644 --- a/tests/Entity/PageDraftTest.php +++ b/tests/Entity/PageDraftTest.php @@ -245,7 +245,7 @@ class PageDraftTest extends TestCase ]); } - public function test_draft_published_can_only_be_used_on_draft_pages() + public function test_draft_publish_can_only_be_used_on_draft_pages() { $page = $this->entities->page(); $this->withoutExceptionHandling(); @@ -263,4 +263,27 @@ class PageDraftTest extends TestCase $page->refresh(); $this->assertNotEquals('My published drafty', $page->name); } + + public function test_destroy_draft_can_only_be_used_on_draft_pages() + { + $page = $this->entities->page(); + $this->withoutExceptionHandling(); + + $exception = null; + try { + $this->asEditor()->delete($page->book->getUrl("/draft/{$page->id}")); + } catch (\Exception $e) { + $exception = $e; + } + + $this->assertInstanceOf(PermissionsException::class, $exception); + $this->assertStringContainsString('This page is already published or does not belong to you', $exception->getMessage()); + + $page->refresh(); + $this->assertDatabaseHas('entities', [ + 'id' => $page->id, + 'type' => 'page', + 'deleted_at' => null + ]); + } }