summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel Catchpole2015-01-12 17:19:55 +0000
committerNathaniel Catchpole2015-01-12 17:19:55 +0000
commita042df2e2ec37c8b5436c41ff0b8de82fda40dda (patch)
tree52580ab1e00108bb0459e100d2472c017b6b4c21
parente2e00e91d0f7a19444db654f0e62102a155ca30c (diff)
Issue #2172017 by dawehner, larowlan, MegaChriz, kim.pepper, Désiré, Sam Hermans, tim.plunkett, Antti J. Salminen: Bulk operations does not respect entity access
-rw-r--r--core/lib/Drupal/Core/Action/ActionInterface.php21
-rw-r--r--core/modules/action/src/Plugin/Action/EmailAction.php10
-rw-r--r--core/modules/action/src/Plugin/Action/GotoAction.php10
-rw-r--r--core/modules/action/src/Plugin/Action/MessageAction.php10
-rw-r--r--core/modules/action/src/Tests/BulkFormTest.php8
-rw-r--r--core/modules/comment/src/Plugin/Action/PublishComment.php13
-rw-r--r--core/modules/comment/src/Plugin/Action/SaveComment.php9
-rw-r--r--core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php13
-rw-r--r--core/modules/comment/src/Plugin/Action/UnpublishComment.php13
-rw-r--r--core/modules/node/src/Plugin/Action/AssignOwnerNode.php12
-rw-r--r--core/modules/node/src/Plugin/Action/DeleteNode.php8
-rw-r--r--core/modules/node/src/Plugin/Action/DemoteNode.php12
-rw-r--r--core/modules/node/src/Plugin/Action/PromoteNode.php12
-rw-r--r--core/modules/node/src/Plugin/Action/PublishNode.php12
-rw-r--r--core/modules/node/src/Plugin/Action/SaveNode.php9
-rw-r--r--core/modules/node/src/Plugin/Action/StickyNode.php12
-rw-r--r--core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php12
-rw-r--r--core/modules/node/src/Plugin/Action/UnpublishNode.php12
-rw-r--r--core/modules/node/src/Plugin/Action/UnstickyNode.php12
-rw-r--r--core/modules/node/src/Tests/Views/BulkFormAccessTest.php175
-rw-r--r--core/modules/node/src/Tests/Views/BulkFormTest.php2
-rw-r--r--core/modules/system/src/Plugin/views/field/BulkForm.php30
-rw-r--r--core/modules/system/tests/modules/action_test/src/Plugin/Action/NoType.php10
-rw-r--r--core/modules/system/tests/modules/action_test/src/Plugin/Action/SaveEntity.php9
-rw-r--r--core/modules/user/src/Plugin/Action/AddRoleUser.php2
-rw-r--r--core/modules/user/src/Plugin/Action/BlockUser.php12
-rw-r--r--core/modules/user/src/Plugin/Action/CancelUser.php8
-rw-r--r--core/modules/user/src/Plugin/Action/ChangeUserRoleBase.php12
-rw-r--r--core/modules/user/src/Plugin/Action/RemoveRoleUser.php2
-rw-r--r--core/modules/user/src/Plugin/Action/UnblockUser.php12
-rw-r--r--core/modules/user/src/Tests/Views/BulkFormAccessTest.php135
-rw-r--r--core/modules/user/src/Tests/Views/BulkFormTest.php9
-rw-r--r--core/modules/user/tests/modules/user_access_test/user_access_test.info.yml6
-rw-r--r--core/modules/user/tests/modules/user_access_test/user_access_test.module24
-rw-r--r--core/modules/views/tests/modules/views_test_data/src/Plugin/views/access/StaticTest.php3
35 files changed, 661 insertions, 10 deletions
diff --git a/core/lib/Drupal/Core/Action/ActionInterface.php b/core/lib/Drupal/Core/Action/ActionInterface.php
index fe04acb..cbf826f 100644
--- a/core/lib/Drupal/Core/Action/ActionInterface.php
+++ b/core/lib/Drupal/Core/Action/ActionInterface.php
@@ -9,6 +9,7 @@ namespace Drupal\Core\Action;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Executable\ExecutableInterface;
+use Drupal\Core\Session\AccountInterface;
/**
* Provides an interface for an Action plugin.
@@ -44,4 +45,24 @@ interface ActionInterface extends ExecutableInterface, PluginInspectionInterface
*/
public function executeMultiple(array $objects);
+ /**
+ * Checks object access.
+ *
+ * @param mixed $object
+ * The object to execute the action on.
+ * @param \Drupal\Core\Session\AccountInterface $account
+ * (optional) The user for which to check access, or NULL to check access
+ * for the current user. Defaults to NULL.
+ * @param bool $return_as_object
+ * (optional) Defaults to FALSE.
+ *
+ * @return bool|\Drupal\Core\Access\AccessResultInterface
+ * The access result. Returns a boolean if $return_as_object is FALSE (this
+ * is the default) and otherwise an AccessResultInterface object.
+ * When a boolean is returned, the result of AccessInterface::isAllowed() is
+ * returned, i.e. TRUE means access is explicitly allowed, FALSE means
+ * access is either explicitly forbidden or "no opinion".
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE);
+
}
diff --git a/core/modules/action/src/Plugin/Action/EmailAction.php b/core/modules/action/src/Plugin/Action/EmailAction.php
index 5bdc609..d93152d 100644
--- a/core/modules/action/src/Plugin/Action/EmailAction.php
+++ b/core/modules/action/src/Plugin/Action/EmailAction.php
@@ -7,11 +7,13 @@
namespace Drupal\action\Plugin\Action;
+use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Mail\MailManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Utility\Token;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -184,4 +186,12 @@ class EmailAction extends ConfigurableActionBase implements ContainerFactoryPlug
$this->configuration['message'] = $form_state->getValue('message');
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ $result = AccessResult::allowed();
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
}
diff --git a/core/modules/action/src/Plugin/Action/GotoAction.php b/core/modules/action/src/Plugin/Action/GotoAction.php
index 833f304..4ab688e 100644
--- a/core/modules/action/src/Plugin/Action/GotoAction.php
+++ b/core/modules/action/src/Plugin/Action/GotoAction.php
@@ -7,10 +7,12 @@
namespace Drupal\action\Plugin\Action;
+use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -113,4 +115,12 @@ class GotoAction extends ConfigurableActionBase implements ContainerFactoryPlugi
$this->configuration['url'] = $form_state->getValue('url');
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ $access = AccessResult::allowed();
+ return $return_as_object ? $access : $access->isAllowed();
+ }
+
}
diff --git a/core/modules/action/src/Plugin/Action/MessageAction.php b/core/modules/action/src/Plugin/Action/MessageAction.php
index 7996514..c87b606 100644
--- a/core/modules/action/src/Plugin/Action/MessageAction.php
+++ b/core/modules/action/src/Plugin/Action/MessageAction.php
@@ -8,9 +8,11 @@
namespace Drupal\action\Plugin\Action;
use Drupal\Component\Utility\Xss;
+use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Utility\Token;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -89,4 +91,12 @@ class MessageAction extends ConfigurableActionBase implements ContainerFactoryPl
unset($this->configuration['node']);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ $result = AccessResult::allowed();
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
}
diff --git a/core/modules/action/src/Tests/BulkFormTest.php b/core/modules/action/src/Tests/BulkFormTest.php
index 8fd634b..c108a83 100644
--- a/core/modules/action/src/Tests/BulkFormTest.php
+++ b/core/modules/action/src/Tests/BulkFormTest.php
@@ -63,6 +63,14 @@ class BulkFormTest extends WebTestBase {
$edit["node_bulk_form[$i]"] = TRUE;
}
+ // Log in as a user with 'administer nodes' permission to have access to the
+ // bulk operation.
+ $this->drupalCreateContentType(['type' => 'page']);
+ $admin_user = $this->drupalCreateUser(['administer nodes', 'edit any page content']);
+ $this->drupalLogin($admin_user);
+
+ $this->drupalGet('test_bulk_form');
+
// Set all nodes to sticky and check that.
$edit += array('action' => 'node_make_sticky_action');
$this->drupalPostForm(NULL, $edit, t('Apply'));
diff --git a/core/modules/comment/src/Plugin/Action/PublishComment.php b/core/modules/comment/src/Plugin/Action/PublishComment.php
index 41ed873..78fc7fa 100644
--- a/core/modules/comment/src/Plugin/Action/PublishComment.php
+++ b/core/modules/comment/src/Plugin/Action/PublishComment.php
@@ -8,7 +8,7 @@
namespace Drupal\comment\Plugin\Action;
use Drupal\Core\Action\ActionBase;
-use Drupal\comment\CommentInterface;
+use Drupal\Core\Session\AccountInterface;
/**
* Publishes a comment.
@@ -29,4 +29,15 @@ class PublishComment extends ActionBase {
$comment->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\comment\CommentInterface $object */
+ $result = $object->status->access('edit', $account, TRUE)
+ ->andIf($object->access('update', $account, TRUE));
+
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
}
diff --git a/core/modules/comment/src/Plugin/Action/SaveComment.php b/core/modules/comment/src/Plugin/Action/SaveComment.php
index 5ce763e..eb2ee42 100644
--- a/core/modules/comment/src/Plugin/Action/SaveComment.php
+++ b/core/modules/comment/src/Plugin/Action/SaveComment.php
@@ -8,6 +8,7 @@
namespace Drupal\comment\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Saves a comment.
@@ -27,4 +28,12 @@ class SaveComment extends ActionBase {
$comment->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\comment\CommentInterface $object */
+ return $object->access('update', $account, $return_as_object);
+ }
+
}
diff --git a/core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php b/core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php
index 3d10bcb..56706ad 100644
--- a/core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php
+++ b/core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php
@@ -9,8 +9,8 @@ namespace Drupal\comment\Plugin\Action;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Action\ConfigurableActionBase;
-use Drupal\comment\CommentInterface;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Session\AccountInterface;
/**
* Unpublishes a comment containing certain keywords.
@@ -67,4 +67,15 @@ class UnpublishByKeywordComment extends ConfigurableActionBase {
$this->configuration['keywords'] = Tags::explode($form_state->getValue('keywords'));
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\comment\CommentInterface $object */
+ $result = $object->access('update', $account, TRUE)
+ ->andIf($object->status->access('edit', $account, TRUE));
+
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
}
diff --git a/core/modules/comment/src/Plugin/Action/UnpublishComment.php b/core/modules/comment/src/Plugin/Action/UnpublishComment.php
index 74d565a..4fb3d56 100644
--- a/core/modules/comment/src/Plugin/Action/UnpublishComment.php
+++ b/core/modules/comment/src/Plugin/Action/UnpublishComment.php
@@ -8,7 +8,7 @@
namespace Drupal\comment\Plugin\Action;
use Drupal\Core\Action\ActionBase;
-use Drupal\comment\CommentInterface;
+use Drupal\Core\Session\AccountInterface;
/**
* Unpublishes a comment.
@@ -29,4 +29,15 @@ class UnpublishComment extends ActionBase {
$comment->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\comment\CommentInterface $object */
+ $result = $object->status->access('edit', $account, TRUE)
+ ->andIf($object->access('update', $account, TRUE));
+
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
}
diff --git a/core/modules/node/src/Plugin/Action/AssignOwnerNode.php b/core/modules/node/src/Plugin/Action/AssignOwnerNode.php
index 17e62d1..0d1cc38 100644
--- a/core/modules/node/src/Plugin/Action/AssignOwnerNode.php
+++ b/core/modules/node/src/Plugin/Action/AssignOwnerNode.php
@@ -11,6 +11,7 @@ use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -132,4 +133,15 @@ class AssignOwnerNode extends ConfigurableActionBase implements ContainerFactory
$this->configuration['owner_uid'] = $this->connection->query('SELECT uid from {users_field_data} WHERE name = :name AND default_langcode = 1', array(':name' => $form_state->getValue('owner_name')))->fetchField();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\node\NodeInterface $object */
+ $result = $object->access('update', $account, TRUE)
+ ->andIf($object->uid->access('edit', $account, TRUE));
+
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
}
diff --git a/core/modules/node/src/Plugin/Action/DeleteNode.php b/core/modules/node/src/Plugin/Action/DeleteNode.php
index 7b2d554..f01d0d1 100644
--- a/core/modules/node/src/Plugin/Action/DeleteNode.php
+++ b/core/modules/node/src/Plugin/Action/DeleteNode.php
@@ -87,4 +87,12 @@ class DeleteNode extends ActionBase implements ContainerFactoryPluginInterface {
$this->executeMultiple(array($object));
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\node\NodeInterface $object */
+ return $object->access('delete', $account, $return_as_object);
+ }
+
}
diff --git a/core/modules/node/src/Plugin/Action/DemoteNode.php b/core/modules/node/src/Plugin/Action/DemoteNode.php
index e490ecb..4a9fa4e 100644
--- a/core/modules/node/src/Plugin/Action/DemoteNode.php
+++ b/core/modules/node/src/Plugin/Action/DemoteNode.php
@@ -8,6 +8,7 @@
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Demotes a node.
@@ -28,4 +29,15 @@ class DemoteNode extends ActionBase {
$entity->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\node\NodeInterface $object */
+ $result = $object->access('update', $account, TRUE)
+ ->andIf($object->promote->access('edit', $account, TRUE));
+
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
}
diff --git a/core/modules/node/src/Plugin/Action/PromoteNode.php b/core/modules/node/src/Plugin/Action/PromoteNode.php
index 0cfc316..a824c78 100644
--- a/core/modules/node/src/Plugin/Action/PromoteNode.php
+++ b/core/modules/node/src/Plugin/Action/PromoteNode.php
@@ -8,6 +8,7 @@
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Promotes a node.
@@ -24,9 +25,18 @@ class PromoteNode extends ActionBase {
* {@inheritdoc}
*/
public function execute($entity = NULL) {
- $entity->setPublished(TRUE);
$entity->setPromoted(TRUE);
$entity->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\node\NodeInterface $object */
+ $access = $object->access('update', $account, TRUE)
+ ->andif($object->promote->access('edit', $account, TRUE));
+ return $return_as_object ? $access : $access->isAllowed();
+ }
+
}
diff --git a/core/modules/node/src/Plugin/Action/PublishNode.php b/core/modules/node/src/Plugin/Action/PublishNode.php
index 20da55e..f01698f 100644
--- a/core/modules/node/src/Plugin/Action/PublishNode.php
+++ b/core/modules/node/src/Plugin/Action/PublishNode.php
@@ -8,6 +8,7 @@
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Publishes a node.
@@ -28,4 +29,15 @@ class PublishNode extends ActionBase {
$entity->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\node\NodeInterface $object */
+ $result = $object->access('update', $account, TRUE)
+ ->andIf($object->status->access('edit', $account, TRUE));
+
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
}
diff --git a/core/modules/node/src/Plugin/Action/SaveNode.php b/core/modules/node/src/Plugin/Action/SaveNode.php
index b758b72..4e56e8a 100644
--- a/core/modules/node/src/Plugin/Action/SaveNode.php
+++ b/core/modules/node/src/Plugin/Action/SaveNode.php
@@ -8,6 +8,7 @@
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Provides an action that can save any entity.
@@ -27,4 +28,12 @@ class SaveNode extends ActionBase {
$entity->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\node\NodeInterface $object */
+ return $object->access('update', $account, $return_as_object);
+ }
+
}
diff --git a/core/modules/node/src/Plugin/Action/StickyNode.php b/core/modules/node/src/Plugin/Action/StickyNode.php
index c4613ce..8c1e615 100644
--- a/core/modules/node/src/Plugin/Action/StickyNode.php
+++ b/core/modules/node/src/Plugin/Action/StickyNode.php
@@ -8,6 +8,7 @@
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Makes a node sticky.
@@ -24,9 +25,18 @@ class StickyNode extends ActionBase {
* {@inheritdoc}
*/
public function execute($entity = NULL) {
- $entity->status = NODE_PUBLISHED;
$entity->sticky = NODE_STICKY;
$entity->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\node\NodeInterface $object */
+ $access = $object->access('update', $account, TRUE)
+ ->andif($object->sticky->access('edit', $account, TRUE));
+ return $return_as_object ? $access : $access->isAllowed();
+ }
+
}
diff --git a/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php b/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php
index 60bd836..f0d8b13 100644
--- a/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php
+++ b/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php
@@ -10,6 +10,7 @@ namespace Drupal\node\Plugin\Action;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Session\AccountInterface;
/**
* Unpublishes a node containing certain keywords.
@@ -65,4 +66,15 @@ class UnpublishByKeywordNode extends ConfigurableActionBase {
$this->configuration['keywords'] = Tags::explode($form_state->getValue('keywords'));
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\node\NodeInterface $object */
+ $access = $object->access('update', $account, TRUE)
+ ->andIf($object->status->access('edit', $account, TRUE));
+
+ return $return_as_object ? $access : $access->isAllowed();
+ }
+
}
diff --git a/core/modules/node/src/Plugin/Action/UnpublishNode.php b/core/modules/node/src/Plugin/Action/UnpublishNode.php
index d462d6d..deea6a1 100644
--- a/core/modules/node/src/Plugin/Action/UnpublishNode.php
+++ b/core/modules/node/src/Plugin/Action/UnpublishNode.php
@@ -8,6 +8,7 @@
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Unpublishes a node.
@@ -28,4 +29,15 @@ class UnpublishNode extends ActionBase {
$entity->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\node\NodeInterface $object */
+ $access = $object->access('update', $account, TRUE)
+ ->andIf($object->status->access('edit', $account, TRUE));
+
+ return $return_as_object ? $access : $access->isAllowed();
+ }
+
}
diff --git a/core/modules/node/src/Plugin/Action/UnstickyNode.php b/core/modules/node/src/Plugin/Action/UnstickyNode.php
index 204b9d5..e49e076 100644
--- a/core/modules/node/src/Plugin/Action/UnstickyNode.php
+++ b/core/modules/node/src/Plugin/Action/UnstickyNode.php
@@ -8,6 +8,7 @@
namespace Drupal\node\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Makes a node not sticky.
@@ -28,4 +29,15 @@ class UnstickyNode extends ActionBase {
$entity->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\node\NodeInterface $object */
+ $access = $object->access('update', $account, TRUE)
+ ->andIf($object->sticky->access('edit', $account, TRUE));
+
+ return $return_as_object ? $access : $access->isAllowed();
+ }
+
}
diff --git a/core/modules/node/src/Tests/Views/BulkFormAccessTest.php b/core/modules/node/src/Tests/Views/BulkFormAccessTest.php
new file mode 100644
index 0000000..e79cfa0
--- /dev/null
+++ b/core/modules/node/src/Tests/Views/BulkFormAccessTest.php
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\Views\BulkFormAccessTest.
+ */
+
+namespace Drupal\node\Tests\Views;
+use Drupal\Component\Utility\String;
+use Drupal\node\Entity\Node;
+
+/**
+ * Tests if entity access is respected on a node bulk operations form.
+ *
+ * @group node
+ * @see \Drupal\node\Plugin\views\field\BulkForm
+ * @see \Drupal\node\Tests\NodeTestBase
+ * @see \Drupal\node\Tests\NodeAccessBaseTableTest
+ * @see \Drupal\node\Tests\Views\BulkFormTest
+ */
+class BulkFormAccessTest extends NodeTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('node_test_views', 'node_access_test');
+
+ /**
+ * Views used by this test.
+ *
+ * @var array
+ */
+ public static $testViews = array('test_node_bulk_form');
+
+ /**
+ * The node access control handler.
+ *
+ * @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface
+ */
+ protected $accessHandler;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp() {
+ parent::setUp();
+
+ // Create Article node type.
+ $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
+
+ $this->accessHandler = \Drupal::entityManager()->getAccessControlHandler('node');
+
+ node_access_test_add_field(entity_load('node_type', 'article'));
+
+ // After enabling a node access module, the access table has to be rebuild.
+ node_access_rebuild();
+
+ // Enable the private node feature of the node_access_test module.
+ \Drupal::state()->set('node_access_test.private', TRUE);
+ }
+
+ /**
+ * Tests if nodes that may not be edited, can not be edited in bulk.
+ */
+ public function testNodeEditAccess() {
+ // Create an account who will be the author of a private node.
+ $author = $this->drupalCreateUser();
+ // Create a private node (author may view, edit and delete, others may not).
+ $node = $this->drupalCreateNode(array(
+ 'type' => 'article',
+ 'private' => array(array(
+ 'value' => TRUE,
+ )),
+ 'uid' => $author->id(),
+ ));
+ // Create an account that may view the private node, but not edit it.
+ $account = $this->drupalCreateUser(array('node test view'));
+ $this->drupalLogin($account);
+
+ // Ensure the node is published.
+ $this->assertTrue($node->isPublished(), 'Node is initially published.');
+
+ // Ensure that the node can not be edited.
+ $this->assertEqual(FALSE, $this->accessHandler->access($node, 'update', $node->prepareLangcode(), $account), 'The node may not be edited.');
+
+ // Test editing the node using the bulk form.
+ $edit = array(
+ 'node_bulk_form[0]' => TRUE,
+ 'action' => 'node_unpublish_action',
+ );
+ $this->drupalPostForm('test-node-bulk-form', $edit, t('Apply'));
+ $this->assertRaw(String::format('No access to execute %action on the @entity_type_label %entity_label.', [
+ '%action' => 'Unpublish content',
+ '@entity_type_label' => 'Content',
+ '%entity_label' => $node->label(),
+ ]));
+
+ // Re-load the node and check the status.
+ $node = Node::load($node->id());
+ $this->assertTrue($node->isPublished(), 'The node is still published.');
+
+ // Create an account that may view the private node, but can update the
+ // status.
+ $account = $this->drupalCreateUser(array('administer nodes', 'edit any article content', 'node test view'));
+ $this->drupalLogin($account);
+
+ // Ensure the node is published.
+ $this->assertTrue($node->isPublished(), 'Node is initially published.');
+
+ // Ensure that the private node can not be edited.
+ $this->assertEqual(FALSE, $node->access('update', $account), 'The node may not be edited.');
+ $this->assertEqual(TRUE, $node->status->access('edit', $account), 'The node status can be edited.');
+
+ // Test editing the node using the bulk form.
+ $edit = array(
+ 'node_bulk_form[0]' => TRUE,
+ 'action' => 'node_unpublish_action',
+ );
+ $this->drupalPostForm('test-node-bulk-form', $edit, t('Apply'));
+ // Re-load the node and check the status.
+ $node = Node::load($node->id());
+ $this->assertTrue($node->isPublished(), 'The node is still published.');
+ }
+
+ /**
+ * Tests if nodes that may not be deleted, can not be deleted in bulk.
+ */
+ public function testNodeDeleteAccess() {
+ // Create an account who will be the author of a private node.
+ $author = $this->drupalCreateUser();
+ // Create a private node (author may view, edit and delete, others may not).
+ $private_node = $this->drupalCreateNode(array(
+ 'type' => 'article',
+ 'private' => array(array(
+ 'value' => TRUE,
+ )),
+ 'uid' => $author->id(),
+ ));
+ // Create an account that may view the private node, but not delete it.
+ $account = $this->drupalCreateUser(array('access content', 'administer nodes', 'delete own article content', 'node test view'));
+ // Create a node that may be deleted too, to ensure the delete confirmation
+ // page is shown later. In node_access_test.module, nodes may only be
+ // deleted by the author.
+ $own_node = $this->drupalCreateNode(array(
+ 'type' => 'article',
+ 'private' => array(array(
+ 'value' => TRUE,
+ )),
+ 'uid' => $account->id(),
+ ));
+ $this->drupalLogin($account);
+
+ // Ensure that the private node can not be deleted.
+ $this->assertEqual(FALSE, $this->accessHandler->access($private_node, 'delete', $private_node->prepareLangcode(), $account), 'The private node may not be deleted.');
+ // Ensure that the public node may be deleted.
+ $this->assertEqual(TRUE, $this->accessHandler->access($own_node, 'delete', $own_node->prepareLangcode(), $account), 'The own node may be deleted.');
+
+ // Try to delete the node using the bulk form.
+ $edit = array(
+ 'node_bulk_form[0]' => TRUE,
+ 'node_bulk_form[1]' => TRUE,
+ 'action' => 'node_delete_action',
+ );
+ $this->drupalPostForm('test-node-bulk-form', $edit, t('Apply'));
+ $this->drupalPostForm(NULL, array(), t('Delete'));
+ // Ensure the private node still exists.
+ $private_node = Node::load($private_node->id());
+ $this->assertNotNull($private_node, 'The private node has not been deleted.');
+ // Ensure the own node is deleted.
+ $own_node = Node::load($own_node->id());
+ $this->assertNull($own_node, 'The own node is deleted.');
+ }
+}
diff --git a/core/modules/node/src/Tests/Views/BulkFormTest.php b/core/modules/node/src/Tests/Views/BulkFormTest.php
index 7da2042..42dc34a 100644
--- a/core/modules/node/src/Tests/Views/BulkFormTest.php
+++ b/core/modules/node/src/Tests/Views/BulkFormTest.php
@@ -29,7 +29,7 @@ class BulkFormTest extends NodeTestBase {
*/
public function testBulkForm() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
- $this->drupalLogin($this->drupalCreateUser(array('administer nodes')));
+ $this->drupalLogin($this->drupalCreateUser(array('administer nodes', 'access content overview', 'bypass node access')));
$node = $this->drupalCreateNode(array(
'promote' => FALSE,
));
diff --git a/core/modules/system/src/Plugin/views/field/BulkForm.php b/core/modules/system/src/Plugin/views/field/BulkForm.php
index 06a7d3a..85c6e901 100644
--- a/core/modules/system/src/Plugin/views/field/BulkForm.php
+++ b/core/modules/system/src/Plugin/views/field/BulkForm.php
@@ -15,6 +15,7 @@ use Drupal\views\Plugin\views\style\Table;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* Defines a actions-based bulk operation form element.
@@ -33,7 +34,7 @@ class BulkForm extends FieldPluginBase {
/**
* An array of actions that can be executed.
*
- * @var array
+ * @var \Drupal\system\ActionConfigEntityInterface[]
*/
protected $actions = array();
@@ -249,18 +250,35 @@ class BulkForm extends FieldPluginBase {
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
+ *
+ * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ * Thrown when the user tried to access an action without access to it.
*/
public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
if ($form_state->get('step') == 'views_form_views_form') {
// Filter only selected checkboxes.
$selected = array_filter($form_state->getValue($this->options['id']));
$entities = array();
+ $action = $this->actions[$form_state->getValue('action')];
+ $count = 0;
foreach (array_intersect_key($this->view->result, $selected) as $row) {
$entity = $this->getEntity($row);
+
+ // Skip execution if the user did not have access.
+ if (!$action->getPlugin()->access($entity, $this->view->getUser())) {
+ $this->drupalSetMessage($this->t('No access to execute %action on the @entity_type_label %entity_label.', [
+ '%action' => $action->label(),
+ '@entity_type_label' => $entity->getEntityType()->getLabel(),
+ '%entity_label' => $entity->label()
+ ]), 'error');
+ continue;
+ }
+
+ $count++;
+
$entities[$entity->id()] = $entity;
}
- $action = $this->actions[$form_state->getValue('action')];
$action->execute($entities);
$operation_definition = $action->getPluginDefinition();
@@ -268,7 +286,6 @@ class BulkForm extends FieldPluginBase {
$form_state->setRedirect($operation_definition['confirm_form_route_name']);
}
- $count = count(array_filter($form_state->getValue($this->options['id'])));
if ($count) {
drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', array(
'%action' => $action->label(),
@@ -311,4 +328,11 @@ class BulkForm extends FieldPluginBase {
return FALSE;
}
+ /**
+ * Wraps drupal_set_message().
+ */
+ protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
+ drupal_set_message($message, $type, $repeat);
+ }
+
}
diff --git a/core/modules/system/tests/modules/action_test/src/Plugin/Action/NoType.php b/core/modules/system/tests/modules/action_test/src/Plugin/Action/NoType.php
index 7f18b57..aa92076 100644
--- a/core/modules/system/tests/modules/action_test/src/Plugin/Action/NoType.php
+++ b/core/modules/system/tests/modules/action_test/src/Plugin/Action/NoType.php
@@ -7,7 +7,9 @@
namespace Drupal\action_test\Plugin\Action;
+use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Provides an operation with no type specified.
@@ -25,4 +27,12 @@ class NoType extends ActionBase {
public function execute($entity = NULL) {
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ $result = AccessResult::allowed();
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
}
diff --git a/core/modules/system/tests/modules/action_test/src/Plugin/Action/SaveEntity.php b/core/modules/system/tests/modules/action_test/src/Plugin/Action/SaveEntity.php
index e3d296f..f26af5a 100644
--- a/core/modules/system/tests/modules/action_test/src/Plugin/Action/SaveEntity.php
+++ b/core/modules/system/tests/modules/action_test/src/Plugin/Action/SaveEntity.php
@@ -8,6 +8,7 @@
namespace Drupal\action_test\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Provides an operation to save user entities.
@@ -27,4 +28,12 @@ class SaveEntity extends ActionBase {
$entity->save();
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\Core\Entity\EntityInterface $object */
+ return $object->access('update', $account, $return_as_object);
+ }
+
}
diff --git a/core/modules/user/src/Plugin/Action/AddRoleUser.php b/core/modules/user/src/Plugin/Action/AddRoleUser.php
index acf4dd9..0ff48c6 100644
--- a/core/modules/user/src/Plugin/Action/AddRoleUser.php
+++ b/core/modules/user/src/Plugin/Action/AddRoleUser.php
@@ -7,6 +7,8 @@
namespace Drupal\user\Plugin\Action;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Session\AccountInterface;
use Drupal\user\Plugin\Action\ChangeUserRoleBase;
/**
diff --git a/core/modules/user/src/Plugin/Action/BlockUser.php b/core/modules/user/src/Plugin/Action/BlockUser.php
index a488f31..6c897af 100644
--- a/core/modules/user/src/Plugin/Action/BlockUser.php
+++ b/core/modules/user/src/Plugin/Action/BlockUser.php
@@ -8,6 +8,7 @@
namespace Drupal\user\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Blocks a user.
@@ -34,4 +35,15 @@ class BlockUser extends ActionBase {
}
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\user\UserInterface $object */
+ $access = $object->status->access('edit', $account, TRUE)
+ ->andIf($object->access('update', $account, TRUE));
+
+ return $return_as_object ? $access : $access->isAllowed();
+ }
+
}
diff --git a/core/modules/user/src/Plugin/Action/CancelUser.php b/core/modules/user/src/Plugin/Action/CancelUser.php
index 87eb60a..3219b62 100644
--- a/core/modules/user/src/Plugin/Action/CancelUser.php
+++ b/core/modules/user/src/Plugin/Action/CancelUser.php
@@ -87,4 +87,12 @@ class CancelUser extends ActionBase implements ContainerFactoryPluginInterface {
$this->executeMultiple(array($object));
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\user\UserInterface $object */
+ return $object->access('delete', $account, $return_as_object);
+ }
+
}
diff --git a/core/modules/user/src/Plugin/Action/ChangeUserRoleBase.php b/core/modules/user/src/Plugin/Action/ChangeUserRoleBase.php
index 5e5c176..506bb0e 100644
--- a/core/modules/user/src/Plugin/Action/ChangeUserRoleBase.php
+++ b/core/modules/user/src/Plugin/Action/ChangeUserRoleBase.php
@@ -12,6 +12,7 @@ use Drupal\Core\Entity\DependencyTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -91,4 +92,15 @@ abstract class ChangeUserRoleBase extends ConfigurableActionBase implements Cont
return $this->dependencies;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\user\UserInterface $object */
+ $access = $object->access('update', $account, TRUE)
+ ->andIf($object->roles->access('edit', $account, TRUE));
+
+ return $return_as_object ? $access : $access->isAllowed();
+ }
+
}
diff --git a/core/modules/user/src/Plugin/Action/RemoveRoleUser.php b/core/modules/user/src/Plugin/Action/RemoveRoleUser.php
index e63a70a..a0f7bdb 100644
--- a/core/modules/user/src/Plugin/Action/RemoveRoleUser.php
+++ b/core/modules/user/src/Plugin/Action/RemoveRoleUser.php
@@ -7,6 +7,8 @@
namespace Drupal\user\Plugin\Action;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Session\AccountInterface;
use Drupal\user\Plugin\Action\ChangeUserRoleBase;
/**
diff --git a/core/modules/user/src/Plugin/Action/UnblockUser.php b/core/modules/user/src/Plugin/Action/UnblockUser.php
index 9c30ebc..09ca3d2 100644
--- a/core/modules/user/src/Plugin/Action/UnblockUser.php
+++ b/core/modules/user/src/Plugin/Action/UnblockUser.php
@@ -8,6 +8,7 @@
namespace Drupal\user\Plugin\Action;
use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
/**
* Unblocks a user.
@@ -31,4 +32,15 @@ class UnblockUser extends ActionBase {
}
}
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ /** @var \Drupal\user\UserInterface $object */
+ $access = $object->status->access('edit', $account, TRUE)
+ ->andIf($object->access('update', $account, TRUE));
+
+ return $return_as_object ? $access : $access->isAllowed();
+ }
+
}
diff --git a/core/modules/user/src/Tests/Views/BulkFormAccessTest.php b/core/modules/user/src/Tests/Views/BulkFormAccessTest.php
new file mode 100644
index 0000000..8f9b613
--- /dev/null
+++ b/core/modules/user/src/Tests/Views/BulkFormAccessTest.php
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Views\BulkFormAccessTest.
+ */
+
+namespace Drupal\user\Tests\Views;
+use Drupal\Component\Utility\String;
+use Drupal\user\Entity\User;
+
+/**
+ * Tests if entity access is respected on a user bulk form.
+ *
+ * @group user
+ * @see \Drupal\user\Plugin\views\field\UserBulkForm
+ * @see \Drupal\user\Tests\Views\BulkFormTest
+ */
+class BulkFormAccessTest extends UserTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('user_access_test');
+
+ /**
+ * Views used by this test.
+ *
+ * @var array
+ */
+ public static $testViews = array('test_user_bulk_form');
+
+ /**
+ * Tests if users that may not be edited, can not be edited in bulk.
+ */
+ public function testUserEditAccess() {
+ // Create an authenticated user.
+ $no_edit_user = $this->drupalCreateUser(array(), 'no_edit');
+ // Ensure this account is not blocked.
+ $this->assertFalse($no_edit_user->isBlocked(), 'The user is not blocked.');
+
+ // Login as user admin.
+ $admin_user = $this->drupalCreateUser(array('administer users'));
+ $this->drupalLogin($admin_user);
+
+ // Ensure that the account "no_edit" can not be edited.
+ $this->drupalGet('user/' . $no_edit_user->id() . '/edit');
+ $this->assertFalse($no_edit_user->access('update', $admin_user));
+ $this->assertResponse(403, 'The user may not be edited.');
+
+ // Test blocking the account "no_edit".
+ $edit = array(
+ 'user_bulk_form[' . ($no_edit_user->id() -1) . ']' => TRUE,
+ 'action' => 'user_block_user_action',
+ );
+ $this->drupalPostForm('test-user-bulk-form', $edit, t('Apply'));
+ $this->assertResponse(200);
+
+ $this->assertRaw(String::format('No access to execute %action on the @entity_type_label %entity_label.', [
+ '%action' => 'Block the selected user(s)',
+ '@entity_type_label' => 'User',
+ '%entity_label' => $no_edit_user->label(),
+ ]));
+
+ // Re-load the account "no_edit" and ensure it is not blocked.
+ $no_edit_user = User::load($no_edit_user->id());
+ $this->assertFalse($no_edit_user->isBlocked(), 'The user is not blocked.');
+
+ // Create a normal user which can be edited by the admin user
+ $normal_user = $this->drupalCreateUser();
+ $this->assertTrue($normal_user->access('update', $admin_user));
+
+ $edit = array(
+ 'user_bulk_form[' . ($normal_user->id() -1) . ']' => TRUE,
+ 'action' => 'user_block_user_action',
+ );
+ $this->drupalPostForm('test-user-bulk-form', $edit, t('Apply'));
+
+ $normal_user = User::load($normal_user->id());
+ $this->assertTrue($normal_user->isBlocked(), 'The user is blocked.');
+
+ // Login as user without the 'administer users' permission.
+ $this->drupalLogin($this->drupalCreateUser());
+
+ $edit = array(
+ 'user_bulk_form[' . ($normal_user->id() -1) . ']' => TRUE,
+ 'action' => 'user_unblock_user_action',
+ );
+ $this->drupalPostForm('test-user-bulk-form', $edit, t('Apply'));
+
+ // Re-load the normal user and ensure it is still blocked.
+ $normal_user = User::load($normal_user->id());
+ $this->assertTrue($normal_user->isBlocked(), 'The user is still blocked.');
+ }
+
+ /**
+ * Tests if users that may not be deleted, can not be deleted in bulk.
+ */
+ public function testUserDeleteAccess() {
+ // Create two authenticated users.
+ $account = $this->drupalCreateUser(array(), 'no_delete');
+ $account2 = $this->drupalCreateUser(array(), 'may_delete');
+
+ // Login as user admin.
+ $this->drupalLogin($this->drupalCreateUser(array('administer users')));
+
+ // Ensure that the account "no_delete" can not be deleted.
+ $this->drupalGet('user/' . $account->id() . '/cancel');
+ $this->assertResponse(403, 'The user "no_delete" may not be deleted.');
+ // Ensure that the account "may_delete" *can* be deleted.
+ $this->drupalGet('user/' . $account2->id() . '/cancel');
+ $this->assertResponse(200, 'The user "may_delete" may be deleted.');
+
+ // Test deleting the accounts "no_delete" and "may_delete".
+ $edit = array(
+ 'user_bulk_form[' . ($account->id() -1) . ']' => TRUE,
+ 'user_bulk_form[' . ($account2->id() -1) . ']' => TRUE,
+ 'action' => 'user_cancel_user_action',
+ );
+ $this->drupalPostForm('test-user-bulk-form', $edit, t('Apply'));
+ $edit = array(
+ 'user_cancel_method' => 'user_cancel_delete',
+ );
+ $this->drupalPostForm(NULL, $edit, t('Cancel accounts'));
+
+ // Ensure the account "no_delete" still exists.
+ $account = User::load($account->id());
+ $this->assertNotNull($account, 'The user "no_delete" is not deleted.');
+ // Ensure the account "may_delete" no longer exists.
+ $account = User::load($account2->id());
+ $this->assertNull($account, 'The user "may_delete" is deleted.');
+ }
+}
diff --git a/core/modules/user/src/Tests/Views/BulkFormTest.php b/core/modules/user/src/Tests/Views/BulkFormTest.php
index 289c1ea..1b9d5bf 100644
--- a/core/modules/user/src/Tests/Views/BulkFormTest.php
+++ b/core/modules/user/src/Tests/Views/BulkFormTest.php
@@ -35,8 +35,15 @@ class BulkFormTest extends UserTestBase {
* Tests the user bulk form.
*/
public function testBulkForm() {
+ // Login as a user without 'administer users'.
$this->drupalLogin($this->drupalCreateUser(array('administer permissions')));
+ // Create an user which actually can change users.
+ $this->drupalLogin($this->drupalCreateUser(array('administer users')));
+ $this->drupalGet('test-user-bulk-form');
+ $result = $this->cssSelect('#edit-action option');
+ $this->assertTrue(count($result) > 0);
+
// Test submitting the page with no selection.
$edit = array(
'action' => 'user_block_user_action',
@@ -101,7 +108,7 @@ class BulkFormTest extends UserTestBase {
$this->assertTrue($anonymous_account->isBlocked(), 'Ensure the anonymous user got blocked.');
// Test the list of available actions with a value that contains a dot.
- $this->drupalLogin($this->drupalCreateUser(array('administer permissions', 'administer views')));
+ $this->drupalLogin($this->drupalCreateUser(array('administer permissions', 'administer views', 'administer users')));
$action_id = 'user_add_role_action.' . $role;
$edit = [
'options[include_exclude]' => 'exclude',
diff --git a/core/modules/user/tests/modules/user_access_test/user_access_test.info.yml b/core/modules/user/tests/modules/user_access_test/user_access_test.info.yml
new file mode 100644
index 0000000..5b13963
--- /dev/null
+++ b/core/modules/user/tests/modules/user_access_test/user_access_test.info.yml
@@ -0,0 +1,6 @@
+name: 'User access tests'
+type: module
+description: 'Support module for user access testing.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/user/tests/modules/user_access_test/user_access_test.module b/core/modules/user/tests/modules/user_access_test/user_access_test.module
new file mode 100644
index 0000000..470a76a
--- /dev/null
+++ b/core/modules/user/tests/modules/user_access_test/user_access_test.module
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Dummy module implementing hook_user_access() to test if entity access is respected.
+ */
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\user\Entity\User;
+
+/**
+ * Implements hook_ENTITY_TYPE_access() for entity type "user".
+ */
+function user_access_test_user_access(User $entity, $operation, $account) {
+ if ($entity->getUsername() == "no_edit" && $operation == "update") {
+ // Deny edit access.
+ return AccessResult::forbidden();
+ }
+ if ($entity->getUsername() == "no_delete" && $operation == "delete") {
+ // Deny delete access.
+ return AccessResult::forbidden();
+ }
+ return AccessResult::neutral();
+}
diff --git a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/access/StaticTest.php b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/access/StaticTest.php
index d0fa62f..c2be665 100644
--- a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/access/StaticTest.php
+++ b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/access/StaticTest.php
@@ -29,6 +29,9 @@ class StaticTest extends AccessPluginBase {
return $options;
}
+ /**
+ * {@inheritdoc}
+ */
public function access(AccountInterface $account) {
return !empty($this->options['access']);
}