diff --git a/core/lib/Drupal/Core/Action/ActionInterface.php b/core/lib/Drupal/Core/Action/ActionInterface.php
index fe04acb..4f1b98c 100644
--- a/core/lib/Drupal/Core/Action/ActionInterface.php
+++ b/core/lib/Drupal/Core/Action/ActionInterface.php
@@ -9,6 +9,8 @@
 
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Executable\ExecutableInterface;
+use Drupal\Core\Access\AccessibleInterface;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Provides an interface for an Action plugin.
@@ -44,4 +46,27 @@
    */
   public function executeMultiple(array $objects);
 
+  /**
+   * Checks data value access.
+   *
+   * @param string $operation
+   *   The operation to be performed. 'execute' for checking access whether the
+   *   $account can execute the action on the particular $object.
+   * @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($operation, $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..febcef4 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 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
     $this->configuration['message'] = $form_state->getValue('message');
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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..96d590d 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 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
     $this->configuration['url'] = $form_state->getValue('url');
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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/MessageAction.php b/core/modules/action/src/Plugin/Action/MessageAction.php
index 7996514..a1e980f 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 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
     unset($this->configuration['node']);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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..f04b9ce 100644
--- a/core/modules/action/src/Tests/BulkFormTest.php
+++ b/core/modules/action/src/Tests/BulkFormTest.php
@@ -63,6 +63,17 @@ public function testBulkForm() {
       $edit["node_bulk_form[$i]"] = TRUE;
     }
 
+    // As a anonymous user you should not have actions available.
+    $result = $this->cssSelect('#edit-action option');
+    $this->assertEqual(0, count($result));
+
+    // Login as a user with 'administer nodes' permission to have access
+    // to the bulk operation.
+    $admin_user = $this->drupalCreateUser(['administer nodes']);
+    $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..0fdfd1b 100644
--- a/core/modules/comment/src/Plugin/Action/PublishComment.php
+++ b/core/modules/comment/src/Plugin/Action/PublishComment.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\comment\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
 use Drupal\comment\CommentInterface;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Publishes a comment.
@@ -29,4 +31,12 @@ public function execute($comment = NULL) {
     $comment->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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/SaveComment.php b/core/modules/comment/src/Plugin/Action/SaveComment.php
index 5ce763e..549f64b 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 @@ public function execute($comment = NULL) {
     $comment->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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..45daaa8 100644
--- a/core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php
+++ b/core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php
@@ -8,9 +8,11 @@
 namespace Drupal\comment\Plugin\Action;
 
 use Drupal\Component\Utility\Tags;
+use Drupal\Core\Access\AccessResult;
 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 +69,12 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
     $this->configuration['keywords'] = Tags::explode($form_state->getValue('keywords'));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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/UnpublishComment.php b/core/modules/comment/src/Plugin/Action/UnpublishComment.php
index 74d565a..6b8b403 100644
--- a/core/modules/comment/src/Plugin/Action/UnpublishComment.php
+++ b/core/modules/comment/src/Plugin/Action/UnpublishComment.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\comment\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
 use Drupal\comment\CommentInterface;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Unpublishes a comment.
@@ -29,4 +31,14 @@ public function execute($comment = NULL) {
     $comment->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    /** @var \Drupal\comment\CommentInterface $object */
+    if ($operation == 'execute') {
+      return $object->access('update', $account, $return_as_object);
+    }
+  }
+
 }
diff --git a/core/modules/node/src/Plugin/Action/AssignOwnerNode.php b/core/modules/node/src/Plugin/Action/AssignOwnerNode.php
index 17e62d1..2be6af7 100644
--- a/core/modules/node/src/Plugin/Action/AssignOwnerNode.php
+++ b/core/modules/node/src/Plugin/Action/AssignOwnerNode.php
@@ -7,10 +7,12 @@
 
 namespace Drupal\node\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 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 +134,12 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
     $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($operation, $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/DeleteNode.php b/core/modules/node/src/Plugin/Action/DeleteNode.php
index a9ebda9..4e7f386 100644
--- a/core/modules/node/src/Plugin/Action/DeleteNode.php
+++ b/core/modules/node/src/Plugin/Action/DeleteNode.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\node\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\user\TempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -70,4 +72,12 @@ public function execute($object = NULL) {
     $this->executeMultiple(array($object));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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..e260efa 100644
--- a/core/modules/node/src/Plugin/Action/DemoteNode.php
+++ b/core/modules/node/src/Plugin/Action/DemoteNode.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\node\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Demotes a node.
@@ -28,4 +30,12 @@ public function execute($entity = NULL) {
     $entity->save();
   }
 
+    /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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/PromoteNode.php b/core/modules/node/src/Plugin/Action/PromoteNode.php
index 0cfc316..98d84a5 100644
--- a/core/modules/node/src/Plugin/Action/PromoteNode.php
+++ b/core/modules/node/src/Plugin/Action/PromoteNode.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\node\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Promotes a node.
@@ -29,4 +31,12 @@ public function execute($entity = NULL) {
     $entity->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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/PublishNode.php b/core/modules/node/src/Plugin/Action/PublishNode.php
index 20da55e..cefa497 100644
--- a/core/modules/node/src/Plugin/Action/PublishNode.php
+++ b/core/modules/node/src/Plugin/Action/PublishNode.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\node\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Publishes a node.
@@ -28,4 +30,12 @@ public function execute($entity = NULL) {
     $entity->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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/SaveNode.php b/core/modules/node/src/Plugin/Action/SaveNode.php
index b758b72..b32bd2d 100644
--- a/core/modules/node/src/Plugin/Action/SaveNode.php
+++ b/core/modules/node/src/Plugin/Action/SaveNode.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\node\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Provides an action that can save any entity.
@@ -27,4 +29,12 @@ public function execute($entity = NULL) {
     $entity->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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..14ce5f9 100644
--- a/core/modules/node/src/Plugin/Action/StickyNode.php
+++ b/core/modules/node/src/Plugin/Action/StickyNode.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\node\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Makes a node sticky.
@@ -29,4 +31,12 @@ public function execute($entity = NULL) {
     $entity->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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/UnpublishByKeywordNode.php b/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php
index 60bd836..6dcfd2d 100644
--- a/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php
+++ b/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php
@@ -10,6 +10,7 @@
 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,12 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
     $this->configuration['keywords'] = Tags::explode($form_state->getValue('keywords'));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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/UnpublishNode.php b/core/modules/node/src/Plugin/Action/UnpublishNode.php
index d462d6d..23d190f 100644
--- a/core/modules/node/src/Plugin/Action/UnpublishNode.php
+++ b/core/modules/node/src/Plugin/Action/UnpublishNode.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\node\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Unpublishes a node.
@@ -28,4 +30,12 @@ public function execute($entity = NULL) {
     $entity->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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/UnstickyNode.php b/core/modules/node/src/Plugin/Action/UnstickyNode.php
index 204b9d5..ff66489 100644
--- a/core/modules/node/src/Plugin/Action/UnstickyNode.php
+++ b/core/modules/node/src/Plugin/Action/UnstickyNode.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\node\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Makes a node not sticky.
@@ -28,4 +30,12 @@ public function execute($entity = NULL) {
     $entity->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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/Tests/Views/BulkFormAccessTest.php b/core/modules/node/src/Tests/Views/BulkFormAccessTest.php
new file mode 100644
index 0000000..ed622fd
--- /dev/null
+++ b/core/modules/node/src/Tests/Views/BulkFormAccessTest.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\Views\BulkFormAccessTest.
+ */
+
+namespace Drupal\node\Tests\Views;
+
+/**
+ * Tests if entity access is respected on a node bulk 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('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->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'));
+    // 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(
+      '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', '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 = 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 own node is deleted.');
+  }
+}
diff --git a/core/modules/system/src/Entity/Action.php b/core/modules/system/src/Entity/Action.php
index ca3d8cf..4174dd9 100644
--- a/core/modules/system/src/Entity/Action.php
+++ b/core/modules/system/src/Entity/Action.php
@@ -21,6 +21,9 @@
  *   id = "action",
  *   label = @Translation("Action"),
  *   admin_permission = "administer actions",
+ *   handlers = {
+ *     "access" = "Drupal\system\Entity\ActionAccessControlHandler",
+ *   },
  *   entity_keys = {
  *     "id" = "id",
  *     "label" = "label"
diff --git a/core/modules/system/src/Entity/ActionAccessControlHandler.php b/core/modules/system/src/Entity/ActionAccessControlHandler.php
new file mode 100644
index 0000000..8d336f8
--- /dev/null
+++ b/core/modules/system/src/Entity/ActionAccessControlHandler.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Entity\ActionAccessControlHandler.
+ */
+
+namespace Drupal\system\Entity;
+
+use Drupal\Core\Entity\EntityAccessControlHandler;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Provides an access control handler for action entities.
+ */
+class ActionAccessControlHandler extends EntityAccessControlHandler {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
+    /** @var \Drupal\system\ActionConfigEntityInterface $entity */
+    return $entity->getPlugin()->access($operation, $account, TRUE);
+  }
+
+}
diff --git a/core/modules/system/src/Plugin/views/field/BulkForm.php b/core/modules/system/src/Plugin/views/field/BulkForm.php
index 06a7d3a..57055d6 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\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,27 @@ protected function getBulkOptions($filtered = TRUE) {
    *   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')];
       foreach (array_intersect_key($this->view->result, $selected) as $row) {
         $entity = $this->getEntity($row);
+
+        // Skip execution if the user did not had access.
+        if (!$action->getPlugin()->access('execute', $entity, $this->view->getUser())) {
+          continue;
+        }
+
         $entities[$entity->id()] = $entity;
       }
 
-      $action = $this->actions[$form_state->getValue('action')];
       $action->execute($entities);
 
       $operation_definition = $action->getPluginDefinition();
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..4f33acc 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($operation, $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..8b93c75 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
@@ -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 to save user entities.
@@ -27,4 +29,12 @@ public function execute($entity = NULL) {
     $entity->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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..0ada490 100644
--- a/core/modules/user/src/Plugin/Action/BlockUser.php
+++ b/core/modules/user/src/Plugin/Action/BlockUser.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\user\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Blocks a user.
@@ -34,4 +36,12 @@ public function execute($account = NULL) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    /** @var \Drupal\user\UserInterface $object */
+    return $object->access('update', $account, $return_as_object);
+  }
+
 }
diff --git a/core/modules/user/src/Plugin/Action/CancelUser.php b/core/modules/user/src/Plugin/Action/CancelUser.php
index 6c0e392..f67443a 100644
--- a/core/modules/user/src/Plugin/Action/CancelUser.php
+++ b/core/modules/user/src/Plugin/Action/CancelUser.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\user\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\user\TempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -70,4 +72,12 @@ public function execute($object = NULL) {
     $this->executeMultiple(array($object));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $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..fca84be 100644
--- a/core/modules/user/src/Plugin/Action/ChangeUserRoleBase.php
+++ b/core/modules/user/src/Plugin/Action/ChangeUserRoleBase.php
@@ -7,11 +7,13 @@
 
 namespace Drupal\user\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ConfigurableActionBase;
 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 +93,12 @@ public function calculateDependencies() {
     return $this->dependencies;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    /** @var \Drupal\user\UserInterface $object */
+    return $object->access('update', $account, $return_as_object);
+  }
+
 }
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..c9b360e 100644
--- a/core/modules/user/src/Plugin/Action/UnblockUser.php
+++ b/core/modules/user/src/Plugin/Action/UnblockUser.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\user\Plugin\Action;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Unblocks a user.
@@ -31,4 +33,12 @@ public function execute($account = NULL) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function access($operation, $object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    /** @var \Drupal\user\UserInterface $object */
+    return $object->access('update', $account, $return_as_object);
+  }
+
 }
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..16e6942
--- /dev/null
+++ b/core/modules/user/src/Tests/Views/BulkFormAccessTest.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Views\BulkFormAccessTest.
+ */
+
+namespace Drupal\user\Tests\Views;
+
+/**
+ * Tests if entity access is respected on an 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.
+    $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/src/Tests/Views/BulkFormTest.php b/core/modules/user/src/Tests/Views/BulkFormTest.php
index 2bf721b..dc48baf 100644
--- a/core/modules/user/src/Tests/Views/BulkFormTest.php
+++ b/core/modules/user/src/Tests/Views/BulkFormTest.php
@@ -35,8 +35,20 @@ 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')));
 
+    // Ensure that there are no actions available.
+    $this->drupalGet('test-user-bulk-form');
+    $result = $this->cssSelect('#edit-action option');
+    $this->assertEqual(0, count($result));
+
+    // 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 +113,7 @@ public function testBulkForm() {
     $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 @@ protected function defineOptions() {
     return $options;
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public function access(AccountInterface $account) {
     return !empty($this->options['access']);
   }
