diff --git a/core/lib/Drupal/Core/Annotation/Action.php b/core/lib/Drupal/Core/Annotation/Action.php
index 0464ed2..f91fdf8 100644
--- a/core/lib/Drupal/Core/Annotation/Action.php
+++ b/core/lib/Drupal/Core/Annotation/Action.php
@@ -54,4 +54,16 @@ class Action extends Plugin {
    */
   public $type = '';
 
+  /**
+   * A machine-readable of operations this action performs on the entities.
+   *
+   * This can be:
+   * - view
+   * - update
+   * - create
+   * - delete
+   *
+   * @var array
+   */
+  public $operations = array();
 }
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Action/PublishComment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Action/PublishComment.php
index 41ed873..b21c9b2 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Action/PublishComment.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Action/PublishComment.php
@@ -16,7 +16,10 @@
  * @Action(
  *   id = "comment_publish_action",
  *   label = @Translation("Publish comment"),
- *   type = "comment"
+ *   type = "comment",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class PublishComment extends ActionBase {
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Action/SaveComment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Action/SaveComment.php
index 8cb02be..4ce4320 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Action/SaveComment.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Action/SaveComment.php
@@ -16,7 +16,10 @@
  * @Action(
  *   id = "comment_save_action",
  *   label = @Translation("Save comment"),
- *   type = "comment"
+ *   type = "comment",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class SaveComment extends ActionBase {
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishByKeywordComment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishByKeywordComment.php
index 92d4014..549d93e 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishByKeywordComment.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishByKeywordComment.php
@@ -16,7 +16,10 @@
  * @Action(
  *   id = "comment_unpublish_by_keyword_action",
  *   label = @Translation("Unpublish comment containing keyword(s)"),
- *   type = "comment"
+ *   type = "comment",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class UnpublishByKeywordComment extends ConfigurableActionBase {
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishComment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishComment.php
index 74d565a..d4f8006 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishComment.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Action/UnpublishComment.php
@@ -16,7 +16,10 @@
  * @Action(
  *   id = "comment_unpublish_action",
  *   label = @Translation("Unpublish comment"),
- *   type = "comment"
+ *   type = "comment",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class UnpublishComment extends ActionBase {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/AssignOwnerNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/AssignOwnerNode.php
index 2de926c..800fff2 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Action/AssignOwnerNode.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/AssignOwnerNode.php
@@ -18,7 +18,10 @@
  * @Action(
  *   id = "node_assign_owner_action",
  *   label = @Translation("Change the author of content"),
- *   type = "node"
+ *   type = "node",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class AssignOwnerNode extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/DeleteNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/DeleteNode.php
index 603f097..13a6693 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Action/DeleteNode.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/DeleteNode.php
@@ -19,7 +19,10 @@
  *   id = "node_delete_action",
  *   label = @Translation("Delete selected content"),
  *   type = "node",
- *   confirm_form_path = "admin/content/node/delete"
+ *   confirm_form_path = "admin/content/node/delete",
+ *   operations = {
+ *     "delete"
+ *   }
  * )
  */
 class DeleteNode extends ActionBase implements ContainerFactoryPluginInterface {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/DemoteNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/DemoteNode.php
index e490ecb..1e55d88 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Action/DemoteNode.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/DemoteNode.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "node_unpromote_action",
  *   label = @Translation("Demote selected content from front page"),
- *   type = "node"
+ *   type = "node",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class DemoteNode extends ActionBase {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/PromoteNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/PromoteNode.php
index 0cfc316..05d6af2 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Action/PromoteNode.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/PromoteNode.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "node_promote_action",
  *   label = @Translation("Promote selected content to front page"),
- *   type = "node"
+ *   type = "node",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class PromoteNode extends ActionBase {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/PublishNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/PublishNode.php
index 20da55e..2f1265c 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Action/PublishNode.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/PublishNode.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "node_publish_action",
  *   label = @Translation("Publish selected content"),
- *   type = "node"
+ *   type = "node",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class PublishNode extends ActionBase {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/SaveNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/SaveNode.php
index b758b72..31b1304 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Action/SaveNode.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/SaveNode.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "node_save_action",
  *   label = @Translation("Save content"),
- *   type = "node"
+ *   type = "node",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class SaveNode extends ActionBase {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/StickyNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/StickyNode.php
index c4613ce..f6c79ef 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Action/StickyNode.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/StickyNode.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "node_make_sticky_action",
  *   label = @Translation("Make selected content sticky"),
- *   type = "node"
+ *   type = "node",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class StickyNode extends ActionBase {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishByKeywordNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishByKeywordNode.php
index 953b0f4..57aef47 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishByKeywordNode.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishByKeywordNode.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "node_unpublish_by_keyword_action",
  *   label = @Translation("Unpublish content containing keyword(s)"),
- *   type = "node"
+ *   type = "node",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class UnpublishByKeywordNode extends ConfigurableActionBase {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishNode.php
index d462d6d..b3a9311 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishNode.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/UnpublishNode.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "node_unpublish_action",
  *   label = @Translation("Unpublish selected content"),
- *   type = "node"
+ *   type = "node",
+ *   operations = {
+ *    "update"
+ *   }
  * )
  */
 class UnpublishNode extends ActionBase {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Action/UnstickyNode.php b/core/modules/node/lib/Drupal/node/Plugin/Action/UnstickyNode.php
index 204b9d5..ac48363 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Action/UnstickyNode.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Action/UnstickyNode.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "node_make_unsticky_action",
  *   label = @Translation("Make selected content not sticky"),
- *   type = "node"
+ *   type = "node",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class UnstickyNode extends ActionBase {
diff --git a/core/modules/node/lib/Drupal/node/Tests/Views/BulkFormAccessTest.php b/core/modules/node/lib/Drupal/node/Tests/Views/BulkFormAccessTest.php
new file mode 100644
index 0000000..dbe03f1
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Tests/Views/BulkFormAccessTest.php
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\Views\BulkFormAccessTest.
+ */
+
+namespace Drupal\node\Tests\Views;
+
+/**
+ * Tests if entity access is respected in the views bulk form.
+ */
+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 controller.
+   *
+   * @var \Drupal\Core\Entity\EntityAccessControllerInterface
+   */
+  protected $accessController;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Node: Bulk form entity access',
+      'description' => 'Tests if a node bulk form respects entity access.',
+      'group' => 'Views module integration'
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+
+    // 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);
+
+    $this->accessController = \Drupal::entityManager()->getAccessController('node');
+  }
+
+  /**
+   * 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('private' => TRUE, 'uid' => $author->id()));
+    // Create an account that may view the private node, but not edit it.
+    $account = $this->drupalCreateUser(array('administer nodes', '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->accessController->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'));
+    // Re-load the node and check the status.
+    $node = entity_load('node', $node->id(), TRUE);
+    $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('private' => 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', '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('private' => TRUE, 'uid' => $account->id()));
+    $this->drupalLogin($account);
+
+    // Ensure that the private node can not be deleted.
+    $this->assertEqual(FALSE, $this->accessController->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->accessController->access($own_node, 'delete', $own_node->prepareLangcode(), $account), 'The public 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 = entity_load('node', $private_node->id(), TRUE);
+    $this->assertNotNull($private_node, 'The private node has not been deleted.');
+    // Ensure the own node is deleted.
+    $own_node = entity_load('node', $own_node->id(), TRUE);
+    $this->assertNull($own_node, 'The public node is deleted.');
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkForm.php b/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkForm.php
index ad784e3..faa42f5 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkForm.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/views/field/BulkForm.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\system\Plugin\views\field;
 
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\field\FieldPluginBase;
 use Drupal\views\Plugin\views\style\Table;
@@ -259,14 +261,40 @@ public function viewsFormSubmit(&$form, &$form_state) {
       }
 
       $action = $this->actions[$form_state['values']['action']];
-      $action->execute($entities);
 
       $operation_definition = $action->getPluginDefinition();
       if (!empty($operation_definition['confirm_form_path'])) {
         $form_state['redirect'] = $operation_definition['confirm_form_path'];
       }
 
-      $count = count(array_filter($form_state['values'][$this->options['id']]));
+      // Check entity access.
+      $errors = array();
+      foreach ($entities as $id => $entity) {
+        if (!$this->checkAccess($action, $entity)) {
+          $errors[] = $this->t('Skipped %operation on @type %title due to insufficient permissions.', array(
+            '%operation' => $action->label(),
+            '@type' => $entity->getEntityType()->getLabel(),
+            '%title' => $entity->label(),
+          ));
+          unset($entities[$id]);
+        }
+      }
+      $this->executeAction($action, $entities);
+
+      // Check if there were errors.
+      if (count($errors) > 1) {
+        $message = array(
+          '#theme' => 'item_list',
+          '#items' => $errors,
+        );
+        $message = drupal_render($message);
+        drupal_set_message($message, 'warning');
+      }
+      elseif (count($errors) == 1) {
+        drupal_set_message(reset($errors), 'warning');
+      }
+
+      $count = count($entities);
       $action = $this->actions[$form_state['values']['action']];
       if ($count) {
         drupal_set_message($this->translationManager()->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', array(
@@ -278,6 +306,42 @@ public function viewsFormSubmit(&$form, &$form_state) {
   }
 
   /**
+   * Checks if action may be performed on entity.
+   *
+   * @param Drupal\system\Entity\Action $action
+   *   The action to perform.
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to perform operation on.
+   *
+   * @return bool
+   *   TRUE if the action may be performed.
+   *   FALSE otherwise.
+   */
+  protected function checkAccess($action, EntityInterface $entity, AccountInterface $account = NULL) {
+    $operation_definition = $action->getPluginDefinition();
+    foreach ($operation_definition['operations'] as $operation) {
+      if (!$entity->access($operation, $account)) {
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * Executes action on entities.
+   *
+   * @param Drupal\system\Entity\Action $action
+   *   The action to perform.
+   * @param array $entities
+   *   An array of entities to perform the action on.
+   *
+   * @return void
+   */
+  protected function executeAction($action, array $entities) {
+    $action->execute($entities);
+  }
+
+  /**
    * Returns the message to be displayed when there are no selected items.
    *
    * @return string
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/AddRoleUser.php b/core/modules/user/lib/Drupal/user/Plugin/Action/AddRoleUser.php
index acf4dd9..ab26761 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Action/AddRoleUser.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/AddRoleUser.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "user_add_role_action",
  *   label = @Translation("Add a role to the selected users"),
- *   type = "user"
+ *   type = "user",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class AddRoleUser extends ChangeUserRoleBase {
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/BlockUser.php b/core/modules/user/lib/Drupal/user/Plugin/Action/BlockUser.php
index a488f31..c7db5bc 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Action/BlockUser.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/BlockUser.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "user_block_user_action",
  *   label = @Translation("Block the selected users"),
- *   type = "user"
+ *   type = "user",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class BlockUser extends ActionBase {
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/CancelUser.php b/core/modules/user/lib/Drupal/user/Plugin/Action/CancelUser.php
index b404ba0..de1e494 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Action/CancelUser.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/CancelUser.php
@@ -19,7 +19,10 @@
  *   id = "user_cancel_user_action",
  *   label = @Translation("Cancel the selected user accounts"),
  *   type = "user",
- *   confirm_form_path = "admin/people/cancel"
+ *   confirm_form_path = "admin/people/cancel",
+ *   operations = {
+ *     "delete"
+ *   }
  * )
  */
 class CancelUser extends ActionBase implements ContainerFactoryPluginInterface {
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/RemoveRoleUser.php b/core/modules/user/lib/Drupal/user/Plugin/Action/RemoveRoleUser.php
index e63a70a..0d93e75 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Action/RemoveRoleUser.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/RemoveRoleUser.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "user_remove_role_action",
  *   label = @Translation("Remove a role from the selected users"),
- *   type = "user"
+ *   type = "user",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class RemoveRoleUser extends ChangeUserRoleBase {
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Action/UnblockUser.php b/core/modules/user/lib/Drupal/user/Plugin/Action/UnblockUser.php
index 9c30ebc..84e382c 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Action/UnblockUser.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Action/UnblockUser.php
@@ -15,7 +15,10 @@
  * @Action(
  *   id = "user_unblock_user_action",
  *   label = @Translation("Unblock the selected users"),
- *   type = "user"
+ *   type = "user",
+ *   operations = {
+ *     "update"
+ *   }
  * )
  */
 class UnblockUser extends ActionBase {
diff --git a/core/modules/user/lib/Drupal/user/Tests/Views/BulkFormAccessTest.php b/core/modules/user/lib/Drupal/user/Tests/Views/BulkFormAccessTest.php
new file mode 100644
index 0000000..15574ea
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Tests/Views/BulkFormAccessTest.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Views\BulkFormAccessTest.
+ */
+
+namespace Drupal\user\Tests\Views;
+
+/**
+ * Tests if entity access is respected in the views bulk form.
+ */
+class BulkFormAccessTest extends UserTestBase {
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('user_test_views', 'node', 'user_access_test');
+
+  /**
+   * Views used by this test.
+   *
+   * @var array
+   */
+  public static $testViews = array('test_user_bulk_form');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'User: Bulk form entity access',
+      'description' => 'Tests if a user bulk form respects entity access.',
+      'group' => 'Views module integration'
+    );
+  }
+
+  /**
+   * Tests if users that may not be edited, can not be edited in bulk.
+   */
+  public function testUserEditAccess() {
+    // Create an authenticated user.
+    $account = $this->drupalCreateUser(array(), 'no_edit');
+    // Ensure this account is not blocked.
+    $this->assertFalse($account->isBlocked(), 'The user is not blocked.');
+
+    // Login as user admin.
+    $this->drupalLogin($this->drupalCreateUser(array('administer users')));
+
+    // Ensure that the account "no_edit" can not be edited.
+    $this->drupalGet('user/' . $account->id() . '/edit');
+    $this->assertResponse(403, 'The user may not be edited.');
+
+    // Test blocking the account "no_edit".
+    $edit = array(
+      'user_bulk_form[' . ($account->id() -1) . ']' => TRUE,
+      'action' => 'user_block_user_action',
+    );
+    $this->drupalPostForm('test-user-bulk-form', $edit, t('Apply'));
+
+    // Re-load the account "no_edit" and ensure it is still not blocked.
+    $account = entity_load('user', $account->id(), TRUE);
+    $this->assertFalse($account->isBlocked(), 'The user is not 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 = entity_load('user', $account->id(), TRUE);
+    $this->assertNotNull($account, 'The user "no_delete" is not deleted.');
+    // Ensure the account "may_delete" no longer exists.
+    $account = entity_load('user', $account2->id(), TRUE);
+    $this->assertNull($account, 'The user "may_delete" is deleted.');
+  }
+}
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..77e0149
--- /dev/null
+++ b/core/modules/user/tests/modules/user_access_test/user_access_test.info.yml
@@ -0,0 +1,9 @@
+name: 'User access tests'
+type: module
+description: 'Support module for user access testing.'
+package: Testing
+version: VERSION
+core: 8.x
+dependencies:
+  - user
+hidden: true
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..84b8c87
--- /dev/null
+++ b/core/modules/user/tests/modules/user_access_test/user_access_test.module
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Dummy module implementing hook_user_access() to test if entity access is respected.
+ */
+
+/**
+ * Implements hook_ENTITY_TYPE_access() for entity type "user".
+ */
+function user_access_test_user_access(\Drupal\user\Entity\User $entity, $operation, $account) {
+  if ($entity->getUsername() == "no_edit" && $operation == "update") {
+    // Deny edit access.
+    return FALSE;
+  }
+  if ($entity->getUsername() == "no_delete" && $operation == "delete") {
+    // Deny delete access.
+    return FALSE;
+  }
+}
