Problem/Motivation

Unpublished books appear in the default listing at /book.

Steps to reproduce:

  1. Install Drupal
  2. Enable book module
  3. Create a book page that is unpublished
  4. Visit /book as an anonymous user
  5. Confirm the unpublished book appears in the list
  6. Click the link and get access denied

Expected result: Unpublished books should not appear in the listing /book for users who do not have permission to view unpublished content.

Proposed resolution

Update this page to check permissions to ensure content displayed corresponds with user access.

Remaining tasks

  1. Write a patch
  2. Review

--

I created new drupal 8 site from drupal 6 (via Migrate module).
Unpublished books became available in books list.
There were only main pages unpublished of these books. I tried to unpublish all pages of these books but result is the same.
When I click on one of these books in the list, 404 is shown.

have a look http://русбой.рф/книга
for example the third book in the list is unpublished

module list:
bootstrap_layouts
captcha
colorbox
ctools
ds
google_analytics
pathauto
recaptcha
redirect
tagclouds
token
video

theme: bootstrap sub theme

Comments

lapaev created an issue. See original summary.

lapaev’s picture

Priority: Major » Critical
lapaev’s picture

Issue summary: View changes
lapaev’s picture

Workaround: create custom view which overrides system path (/book).

cilefen’s picture

Component: book.module » migration system
Priority: Critical » Major

Good. This is not critical with a workaround.

Version: 8.6.10 » 8.6.x-dev

Core issues are now filed against the dev versions where changes will be made. Document the specific release you are using in your issue comment. More information about choosing a version.

Version: 8.6.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Branches prior to 8.8.x are not supported, and Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

pameeela’s picture

Title: unpublished books appear in the list of books » Unpublished books appear in the list of books at /book
Version: 8.9.x-dev » 9.1.x-dev
Component: migration system » book.module
Issue summary: View changes
Priority: Major » Normal
Issue tags: +Bug Smash Initiative

Thanks for reporting this issue. We rely on issue reports like this one to resolve bugs and improve Drupal core.

As part of the Bug Smash Initiative, we are triaging Drupal core issues with the priority 'Major'.

I don't think this meets the criteria for major since there is a document workaround of creating a view for this page.

I was able to reproduce this in 9.0.x so it is a bug in book module, nothing to do with a migration. I've also added steps to test issue summary.

bthompson1’s picture

StatusFileSize
new1.97 KB
paulocs’s picture

Assigned: Unassigned » paulocs

Hello, I tested patch #9 but it does not work for me.

I'll create a new patch.

paulocs’s picture

Assigned: paulocs » Unassigned
Status: Active » Needs review
StatusFileSize
new730 bytes
new1.97 KB

A patch for it and interdiff.

ultrabob’s picture

Status: Needs review » Needs work

I looked at the code, it makes sense to me. The patch applied cleanly, and had exactly the effect it claimed to. The unpublished book appeared in the list for an anonymous user before the patch, it disappeared from the list after the patch. Just one nitpick. `use Drupal\user\Entity\User;` has been added, and I see no reason for it. Really small change, I'll provide a patch shortly.

ultrabob’s picture

Status: Needs work » Needs review
StatusFileSize
new1.94 KB

Tiny fix to #11 as promised.

Status: Needs review » Needs work

The last submitted patch, 13: 3040181-unpublished-books-13.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

lendude’s picture

Issue tags: +Needs tests

Great progress so far!

Not sure about the major/normal, since this is borderline security (users can view unpublished content labels when they don't have the permission to do so) major might be right.

Couple of things:

  1. +++ b/core/modules/book/src/BookOutlineStorage.php
    @@ -16,18 +17,30 @@ class BookOutlineStorage implements BookOutlineStorageInterface {
    +  public function __construct(Connection $connection, AccountInterface $currentUser) {
    

    The new argument needs to default to NULL and we need to add a deprecation message about not settings the current user, see #3101738: Exposed term filters should not show term options that the user does not have access to for a similar situation and how to solve this.

  2. +++ b/core/modules/book/src/BookOutlineStorage.php
    @@ -16,18 +17,30 @@ class BookOutlineStorage implements BookOutlineStorageInterface {
    +      return $this->connection->query("SELECT DISTINCT(bid) FROM {book} b INNER JOIN {node_field_data} f ON b.nid = f.nid WHERE f.status = 1")->fetchCol();
    

    do we really need to write out all this in query()? Can't we use a select()? (I'm asking, dunno if there is a reason to do it like this)

Also, this needs tests.

mindbet’s picture

in this section:

+    if ($this->currentUser->hasPermission('view any unpublished content')) {

instead of

'view any unpublished content'

you may want

''view own unpublished content'

The permission 'view any unpublished content' is introduced in the content_moderation and workflows modules,
and isn't defined in the book module.

(It may be desirable to use the book module with content_moderation and workflows but you need to make it explicit).
See issue: https://www.drupal.org/project/drupal/issues/2004

As the code in #13 is now written, it prevents anonymous users from seeing unpublished books.
But it also prevents bookAuthors from seeing their own unpublished books.

(See https://www.drupal.org/project/drupal/issues/26552)

paulocs’s picture

StatusFileSize
new1.58 KB
new2.56 KB

Hello @mindbet,

I understand what you mean on comment #16.

What do you think about this?

public function getBooks() {
    $currentUserId = $this->currentUser->id();
    if ($this->currentUser->hasPermission('view any unpublished content')) {
      return $this->connection->query("SELECT DISTINCT(bid) FROM {book}")->fetchCol();
    } 
    elseif ($this->currentUser->hasPermission('view own unpublished content')) {
      return $this->connection->query("SELECT DISTINCT(bid) FROM {book} b INNER JOIN {node_field_data} f ON b.nid = f.nid WHERE f.uid = $currentUserId OR f.status = 1")->fetchCol();
    } 
    else {
      return $this->connection->query("SELECT DISTINCT(bid) FROM {book} b INNER JOIN {node_field_data} f ON b.nid = f.nid WHERE f.status = 1")->fetchCol();
    }
  }

I wrote a new patch with what @Lendude proposed on comment #15 except the second observation. It still needs tests for it.

Cheers, Paulo.

lendude’s picture

Status: Needs work » Needs review

Setting to needs review to kick the testbot into gear, but still needs work for tests

The last submitted patch, 9: 3040181-unpublished-books.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

lendude’s picture

Status: Needs review » Needs work

Back to needs work for the tests.

Also, not sure the book module should worry about integrating with the content_moderation module, feels like it should be the other way around.

quietone’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new1.16 KB
new3.72 KB

Here is a test. I added this to the existing testBookListing() to avoid creating yet another Funcational test.

This just adds a test, making no changes to the patch in #17, so the fail patch is the interdiff.

The last submitted patch, 21: 3040181-21-fail.patch, failed testing. View results

mindbet’s picture

StatusFileSize
new3.75 KB
new1.12 KB

This is a suggested revision for the logic in #17.

Imagine a situation where a top book page is unpublished, but one or more child pages are published.

The select statement:

"SELECT DISTINCT(bid) FROM {book} b INNER JOIN {node_field_data} f ON b.nid = f.nid WHERE f.status = 1"

will return the book id of the child pages.

As a result, the top book page will be included in the book listing even if it shouldn't be shown because of its status.

This revised select statement checks only for top book pages to be included in the listing.

"SELECT DISTINCT(bid) FROM {book} b INNER JOIN {node_field_data} f ON b.nid = f.nid WHERE f.status = 1 AND b.pid = 0"

The select statement for 'view own unpublished content' is similarly revised.

paulocs’s picture

Status: Needs review » Reviewed & tested by the community

Good one @mindbet,
patch looks good.

I tested it with a book that the top page is unpublished and the child page is published.
The book is not displayed as expected.

Moving to RTBC.

quietone’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new1.28 KB
new4.01 KB

However, since the logic of the patch has changed the test needs to change as well. The test currently unpublishes all nodes in the book and that is not testing the new code.

Changed the test with just the top page unpublished and then with the top page published and one of the children unpublished. I think that covers the cases.

Also the comment needs tweaking.

+++ b/core/modules/book/tests/src/Functional/BookTest.php
@@ -510,6 +510,17 @@ class BookTest extends BrowserTestBase {
+    // Unpublish all the book nodes and Confirm that the created book title

s/Confirm/confirm

paulocs’s picture

StatusFileSize
new741 bytes
new4.38 KB

I think we're almost there. IMHO the tests should cover what @mindbet pointed on comment #23.

Imagine a situation where a top book page is unpublished, but one or more child pages are published.

I did a patch for it. Please review it.

quietone’s picture

Status: Needs review » Needs work

I agree we need the test but the test is already there. The first test is of the top page unpublished and a book page published, in fact all the other pages are published at that time. The second test is the reverse, the top page published and a child page unpublished.

+++ b/core/modules/book/tests/src/Functional/BookTest.php
@@ -510,6 +510,34 @@ public function testBookListing() {
+    // Unpublish the top book page and publish a page in the book and confirm
+    // that the created book title is not displayed for anonymous.

This is a duplicate of the test that starts on line 515.

Or, have I missed something?

paulocs’s picture

Status: Needs work » Needs review
StatusFileSize
new4.01 KB
new741 bytes

Oh yes, my mistake.
I didn't realize that the book pages are published and the only thing we have to do is set the top book to unpublished.
I attach the patch #25 again.

Thanks @quietone!

quietone’s picture

@paulocs, thanks!

lendude’s picture

Status: Needs review » Needs work

Needs a reroll after #26552: Allow users with access to unpublished nodes to create unpublished books landed.

Also, can we get a new test-only patch with this so we can see that the new test logic still triggers the bug?

ravi.shankar’s picture

Status: Needs work » Needs review
StatusFileSize
new3.69 KB
new1.1 KB

Rerolled patch #28 and added test only patch.

Status: Needs review » Needs work

The last submitted patch, 31: 3040181_test-only.patch, failed testing. View results

quietone’s picture

@ravi.shankar, Thanks for the reroll. Can you upload the interdiff, or diff, please. Oh, and it is best to upload the fail/test-only patch first and then the complete patch, that way the failure of the test-only patch won't set the issue to NW.

snehalgaikwad’s picture

StatusFileSize
new1.1 KB

Uploading test-only patch.

snehalgaikwad’s picture

Status: Needs work » Needs review
StatusFileSize
new3.69 KB
new2.11 KB

Here attaching re-rolled patch with interdiff.

The last submitted patch, 34: 3040181-test-only.patch, failed testing. View results

quietone’s picture

Yes, that look right now. RTBC +1

lendude’s picture

Status: Needs review » Needs work
+++ b/core/modules/book/src/BookOutlineStorage.php
@@ -16,18 +17,39 @@ class BookOutlineStorage implements BookOutlineStorageInterface {
+    if ($this->currentUser->hasPermission('view any unpublished content')) {
...
+    elseif ($this->currentUser->hasPermission('view own unpublished content')) {
...
+    else {

Sorry, should have spotted this sooner, but we are missing test coverage here. We are setting 3 possible queries, but only testing the third one. The first one might have coverage elsewhere (didn't check), but the middle one has no coverage.
So we need to add coverage for the middle one and maybe to be safe some explicit coverage for the first one

paulocs’s picture

Assigned: Unassigned » paulocs
paulocs’s picture

Assigned: paulocs » Unassigned
Status: Needs work » Needs review
StatusFileSize
new4.99 KB
new1.55 KB

Fixing test coverage that @Lendude pointed on comment #38.

lendude’s picture

Status: Needs review » Needs work

@paulocs nice! Looks good, just nits on the comments and then I think this is ready.

  1. +++ b/core/modules/book/tests/src/Functional/BookTest.php
    @@ -561,6 +564,21 @@
    +    // displayed for people who has 'view own unpublished content' permission.
    

    has => have OR people => user (I would do the 'user' one)

  2. +++ b/core/modules/book/tests/src/Functional/BookTest.php
    @@ -561,6 +564,21 @@
    +    // Confirm that the created book title is displayed for
    +    // people who has 'view any unpublished content' permission.
    

    has => have OR people => user (I would do the 'user' one)
    and the first line can wrap later, some more words fit on the line before you hit 80 characters

paulocs’s picture

Status: Needs work » Needs review
StatusFileSize
new4.99 KB
new4.99 KB

New patch.

paulocs’s picture

StatusFileSize
new1.16 KB

I attached the patch two times instead of the interdiff.

lendude’s picture

Status: Needs review » Reviewed & tested by the community

Nice, thanks! Looks good to me now

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new5.13 KB
new3.78 KB
+++ b/core/modules/book/src/BookOutlineStorage.php
@@ -16,18 +17,39 @@ class BookOutlineStorage implements BookOutlineStorageInterface {
+      return $this->connection->query("SELECT DISTINCT(bid) FROM {book} b INNER JOIN {node_field_data} f ON b.nid = f.nid WHERE b.pid = 0 AND (f.uid = $currentUserId OR f.status = 1)")->fetchCol();
...
+      return $this->connection->query("SELECT DISTINCT(bid) FROM {book} b INNER JOIN {node_field_data} f ON b.nid = f.nid WHERE f.status = 1 AND b.pid = 0")->fetchCol();

These queries should use the query builder. I also don't understand why the new queries are limited to books where pid = 0. This is a big change. Also joining like this into node_field_data will break if alternate entity storage is used. Plus this only supports status based access checking - nothing more complex.

Also we're already checking status in \Drupal\book\BookManager::loadBooks() so that's interesting and I ponder if that's even working atm and how it really affects this patch.

I think a better solution here is to change the status check to a view access check instead of the status check. But then again considering all the places getAllBooks is used it might be better to do the access check in \Drupal\book\Controller\BookController::bookRender() and not affect anything else.

alexpott’s picture

Yeah the existing code in BookManager doesn't work as expected because when it does if (isset($nodes[$nid]) && $nodes[$nid]->status) { it expects $nodes[$nid]->status to be a boolean but it's actually a FieldItemList object. Obviously there was 0 test coverage of this and this explains why we probably don't have this issue in D7.

paulocs’s picture

Status: Needs review » Reviewed & tested by the community

So I tested patch #45 and I think it is a better solution. Not big changes are required and it updates the code that was not working previously.
Moving to RTBC.

  • catch committed 989f135 on 9.1.x
    Issue #3040181 by paulocs, quietone, snehalgaikwad, ravi.shankar,...
catch’s picture

Version: 9.1.x-dev » 8.9.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

We normally try to avoid node access checks for listings - however that relies on the listing query having been rewritten by node grants, and the book tree manager doesn't do anything like that. Since this was wrongly (or more, not-) ported code from 7.x, makes sense to fix unobtrusively and add test coverage, and we could look at converting the query later on.

Committed 989f135 and pushed to 9.1.x. Thanks!

Needs a re-roll for 8.9/9.0

acbramley’s picture

Status: Patch (to be ported) » Needs review
StatusFileSize
new3.95 KB
new3.83 KB

Re-rolled for 9.0.x and 8.9.x.

paulocs’s picture

Status: Needs review » Reviewed & tested by the community

I tested patch 3040181-45-8.9.x.patch and 3040181-45-9.0.x.patch.from #50.
They look good.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Backporting this seems important due to the fact it is a security improvement.

Committed 45cf678 and pushed to 9.0.x. Thanks!
Committed 652574f and pushed to 8.9.x. Thanks!

  • alexpott committed 45cf678 on 9.0.x
    Issue #3040181 by paulocs, quietone, snehalgaikwad, ravi.shankar,...

  • alexpott committed 652574f on 8.9.x
    Issue #3040181 by paulocs, quietone, snehalgaikwad, ravi.shankar,...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

tstoeckler’s picture

This caused a huge performance regression on a site with a few thousand book nodes. The underlying issue that on book node edit forms, all books are loaded in order to fill the book select element options. This was already the case before this patch. Now with this patch we are not only loading thousands of nodes but also doing an access check on each one, which adds about 20 seconds to the load time on a node edit page.

To make matters worse, due to #502430: Allow the book outline functionality on non-book-enabled node types to be hidden from users with the "Administer book outlines" permission this is happening on all node pages, even ones which have nothing to do with books.

Since the book form is provided by the BookManager service which can nicely be overridden, we are able to circumvent this issue without too much hassle. (A regular form alter (which we already doing before) is useless, because it comes after all the loading and access checking has already happened.)

I opened #3187277: Book selection form can load thousands of nodes (turn to autocomplete) for the underlying issue of loading all the nodes in the first place, as I couldn't find an existing issue for that. Since that is the real culprit and this just made things a lot worse, I am not asking for this be rolled back, but wanted to note it in case release managers come to a different conclusion and also in case anyone is suffering from the same performance issues since updating to 8.9.8.