 .../config/optional/rest.resource.entity.node.yml  |  31 ++--
 core/modules/rest/config/schema/rest.schema.yml    |  24 ++-
 core/modules/rest/rest.install                     |   2 +
 core/modules/rest/rest.post_update.php             |  42 ++++-
 .../modules/rest/src/Entity/ConfigDependencies.php | 117 ++++++++++---
 .../modules/rest/src/Entity/RestResourceConfig.php |  43 ++---
 .../rest/src/RestResourceConfigInterface.php       |   5 +
 .../Tests/Update/ResourceGranularityUpdateTest.php |  71 ++++++++
 .../Update/RestConfigurationEntitiesUpdateTest.php |  14 +-
 ....rest-rest_post_update_resource_granularity.php | Bin 0 -> 5322 bytes
 .../rest.resource.entity.comment_2721595.yml       |  32 ++++
 .../update/rest.resource.entity.node_2721595.yml   |  29 ++++
 .../update/rest.resource.entity.user_2721595.yml   |  32 ++++
 .../src/Kernel/Entity/ConfigDependenciesTest.php   | 187 ++++++++++++++++-----
 .../src/Tests/System/ResponseGeneratorTest.php     |  13 +-
 15 files changed, 515 insertions(+), 127 deletions(-)

diff --git a/core/modules/rest/config/optional/rest.resource.entity.node.yml b/core/modules/rest/config/optional/rest.resource.entity.node.yml
index caeed78..1388245 100644
--- a/core/modules/rest/config/optional/rest.resource.entity.node.yml
+++ b/core/modules/rest/config/optional/rest.resource.entity.node.yml
@@ -7,25 +7,14 @@ dependencies:
     - node
 id: entity.node
 plugin_id: 'entity:node'
-granularity: method
+granularity: resource
 configuration:
-  GET:
-    supported_formats:
-      - hal_json
-    supported_auth:
-      - basic_auth
-  POST:
-    supported_formats:
-      - hal_json
-    supported_auth:
-      - basic_auth
-  PATCH:
-    supported_formats:
-      - hal_json
-    supported_auth:
-      - basic_auth
-  DELETE:
-    supported_formats:
-      - hal_json
-    supported_auth:
-      - basic_auth
+  methods:
+    - GET
+    - POST
+    - PATCH
+    - DELETE
+  formats:
+    - hal_json
+  authentication:
+    - basic_auth
diff --git a/core/modules/rest/config/schema/rest.schema.yml b/core/modules/rest/config/schema/rest.schema.yml
index 41bc2bf..04f88a6 100644
--- a/core/modules/rest/config/schema/rest.schema.yml
+++ b/core/modules/rest/config/schema/rest.schema.yml
@@ -8,7 +8,6 @@ rest.settings:
       label: 'Domain of the relation'
 
 # Method-level granularity of REST resource configuration.
-# @todo Add resource-level granularity in https://www.drupal.org/node/2721595.
 rest_resource.method:
   type: mapping
   mapping:
@@ -25,6 +24,29 @@ rest_resource.method:
       type: rest_request
       label: 'DELETE method settings'
 
+# Resource-level granularity of REST resource configuration.
+rest_resource.resource:
+  type: mapping
+  mapping:
+    methods:
+      type: sequence
+      label: 'Supported methods'
+      sequence:
+        type: string
+        label: 'HTTP method'
+    formats:
+      type: sequence
+      label: 'Supported formats'
+      sequence:
+        type: string
+        label: 'Format'
+    authentication:
+      type: sequence
+      label: 'Supported authentication providers'
+      sequence:
+        type: string
+        label: 'Authentication provider'
+
 rest_request:
   type: mapping
   mapping:
diff --git a/core/modules/rest/rest.install b/core/modules/rest/rest.install
index 918b4f5..4cfaa11 100644
--- a/core/modules/rest/rest.install
+++ b/core/modules/rest/rest.install
@@ -30,6 +30,8 @@ function rest_requirements($phase) {
 
 /**
  * Install the REST config entity type and fix old settings-based config.
+ *
+ * @see rest_post_update_create_rest_resource_config_entities()
  */
 function rest_update_8201() {
   \Drupal::entityDefinitionUpdateManager()->installEntityType(\Drupal::entityTypeManager()->getDefinition('rest_resource_config'));
diff --git a/core/modules/rest/rest.post_update.php b/core/modules/rest/rest.post_update.php
index 7068662..ecbc73c 100644
--- a/core/modules/rest/rest.post_update.php
+++ b/core/modules/rest/rest.post_update.php
@@ -16,11 +16,8 @@
 /**
  * Create REST resource configuration entities.
  *
- * @todo https://www.drupal.org/node/2721595 Automatically upgrade those REST
- *   resource config entities that have the same formats/auth mechanisms for all
- *   methods to "granular: resource".
- *
  * @see rest_update_8201()
+ * @see https://www.drupal.org/node/2308745
  */
 function rest_post_update_create_rest_resource_config_entities() {
   $resources = \Drupal::state()->get('rest_update_8201_resources', []);
@@ -34,6 +31,43 @@ function rest_post_update_create_rest_resource_config_entities() {
   }
 }
 
+/**
+ * Simplify method-granularity REST resource config to resource-granularity.
+ *
+ * @see https://www.drupal.org/node/2721595
+ */
+function rest_post_update_resource_granularity() {
+  /** @var \Drupal\rest\RestResourceConfigInterface[] $resource_config_entities */
+  $resource_config_entities = RestResourceConfig::loadMultiple();
+
+  foreach ($resource_config_entities as $resource_config_entity) {
+    if ($resource_config_entity->get('granularity') === RestResourceConfigInterface::METHOD_GRANULARITY) {
+      $configuration = $resource_config_entity->get('configuration');
+
+      $format_and_auth_configuration = [];
+      foreach (array_keys($configuration) as $method) {
+        $format_and_auth_configuration['format'][$method] = implode(',', $configuration[$method]['supported_formats']);
+        $format_and_auth_configuration['auth'][$method] = implode(',', $configuration[$method]['supported_auth']);
+      }
+
+      // If each method has the same formats and the same authentication
+      // providers configured, convert it to 'granularity: resource', which has
+      // a simpler/less verbose configuration.
+      if (count(array_unique($format_and_auth_configuration['format'])) === 1 && count(array_unique($format_and_auth_configuration['auth'])) === 1) {
+        $first_method = array_keys($configuration)[0];
+        $resource_config_entity->set('configuration', [
+          'methods' => array_keys($configuration),
+          'formats' => $configuration[$first_method]['supported_formats'],
+          'authentication' => $configuration[$first_method]['supported_auth']
+        ]);
+        $resource_config_entity->set('granularity', RestResourceConfigInterface::RESOURCE_GRANULARITY);
+        $resource_config_entity->save();
+      }
+    }
+  }
+}
+
+
 /**
  * @} End of "addtogroup updates-8.1.x-to-8.2.x".
  */
diff --git a/core/modules/rest/src/Entity/ConfigDependencies.php b/core/modules/rest/src/Entity/ConfigDependencies.php
index 72fc8cc..3b39ccf 100644
--- a/core/modules/rest/src/Entity/ConfigDependencies.php
+++ b/core/modules/rest/src/Entity/ConfigDependencies.php
@@ -61,31 +61,25 @@ public static function create(ContainerInterface $container) {
    */
   public function calculateDependencies(RestResourceConfigInterface $rest_config) {
     $granularity = $rest_config->get('granularity');
-    if ($granularity === RestResourceConfigInterface::METHOD_GRANULARITY) {
-      return $this->calculateDependenciesForMethodGranularity($rest_config);
-    }
-    else {
-      throw new \InvalidArgumentException("A different granularity then 'method' is not supported yet.");
-      // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595.
+
+    // Dependency calculation is the same for either granularity, the most
+    // notable difference is that for the 'resource' granularity, the same
+    // authentication providers and formats are supported for every method.
+    switch ($granularity) {
+      case RestResourceConfigInterface::METHOD_GRANULARITY:
+        $methods = $rest_config->getMethods();
+        break;
+      case RestResourceConfigInterface::RESOURCE_GRANULARITY:
+        $methods = array_slice($rest_config->getMethods(), 0, 1);
+        break;
+      default:
+        throw new \InvalidArgumentException('Invalid granularity specified.');
     }
-  }
 
-  /**
-   * Calculates dependencies of a specific rest resource configuration.
-   *
-   * @param \Drupal\rest\RestResourceConfigInterface $rest_config
-   *   The rest configuration.
-   *
-   * @return string[][]
-   *   Dependencies keyed by dependency type.
-   *
-   * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::calculateDependencies()
-   */
-  protected function calculateDependenciesForMethodGranularity(RestResourceConfigInterface $rest_config) {
     // The dependency lists for authentication providers and formats
     // generated on container build.
     $dependencies = [];
-    foreach (array_keys($rest_config->get('configuration')) as $request_method) {
+    foreach ($methods as $request_method) {
       // Add dependencies based on the supported authentication providers.
       foreach ($rest_config->getAuthenticationProviders($request_method) as $auth) {
         if (isset($this->authProviders[$auth])) {
@@ -102,6 +96,10 @@ protected function calculateDependenciesForMethodGranularity(RestResourceConfigI
       }
     }
 
+    if (isset($dependencies['module'])) {
+      sort($dependencies['module']);
+    }
+
     return $dependencies;
   }
 
@@ -121,12 +119,13 @@ protected function calculateDependenciesForMethodGranularity(RestResourceConfigI
    */
   public function onDependencyRemoval(RestResourceConfigInterface $rest_config, array $dependencies) {
     $granularity = $rest_config->get('granularity');
-    if ($granularity === RestResourceConfigInterface::METHOD_GRANULARITY) {
-      return $this->onDependencyRemovalForMethodGranularity($rest_config, $dependencies);
-    }
-    else {
-      throw new \InvalidArgumentException("A different granularity then 'method' is not supported yet.");
-      // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595.
+    switch ($granularity) {
+      case RestResourceConfigInterface::METHOD_GRANULARITY:
+        return $this->onDependencyRemovalForMethodGranularity($rest_config, $dependencies);
+      case RestResourceConfigInterface::RESOURCE_GRANULARITY:
+        return $this->onDependencyRemovalForResourceGranularity($rest_config, $dependencies);
+      default:
+        throw new \InvalidArgumentException('Invalid granularity specified.');
     }
   }
 
@@ -183,7 +182,71 @@ protected function onDependencyRemovalForMethodGranularity(RestResourceConfigInt
           }
         }
       }
-      if (!empty($configuration_before != $configuration)) {
+      if ($configuration_before != $configuration && !empty($configuration)) {
+        $rest_config->set('configuration', $configuration);
+        // Only mark the dependencies problems as fixed if there is any
+        // configuration left.
+        $changed = TRUE;
+      }
+    }
+    // If the dependency problems are not marked as fixed at this point they
+    // should be related to the resource plugin and the config entity should
+    // be deleted.
+    return $changed;
+  }
+
+  /**
+   * Informs the entity that entities it depends on will be deleted.
+   *
+   * @param \Drupal\rest\RestResourceConfigInterface $rest_config
+   *   The rest configuration.
+   * @param array $dependencies
+   *   An array of dependencies that will be deleted keyed by dependency type.
+   *   Dependency types are, for example, entity, module and theme.
+   *
+   * @return bool
+   *   TRUE if the entity has been changed as a result, FALSE if not.
+   */
+  public function onDependencyRemovalForResourceGranularity(RestResourceConfigInterface $rest_config, array $dependencies) {
+    $changed = FALSE;
+    // Only module-related dependencies can be fixed. All other types of
+    // dependencies cannot, because they were not generated based on supported
+    // authentication providers or formats.
+    if (isset($dependencies['module'])) {
+      // Try to fix dependencies.
+      $removed_auth = array_keys(array_intersect($this->authProviders, $dependencies['module']));
+      $removed_formats = array_keys(array_intersect($this->formatProviders, $dependencies['module']));
+      $configuration_before = $configuration = $rest_config->get('configuration');
+      if (!empty($removed_auth) || !empty($removed_formats)) {
+        // Try to fix dependency problems by removing affected
+        // authentication providers and formats.
+        foreach ($removed_formats as $format) {
+          if (in_array($format, $rest_config->getFormats('GET'))) {
+            $configuration['formats'] = array_diff($configuration['formats'], $removed_formats);
+          }
+        }
+        foreach ($removed_auth as $auth) {
+          if (in_array($auth, $rest_config->getAuthenticationProviders('GET'))) {
+            $configuration['authentication'] = array_diff($configuration['authentication'], $removed_auth);
+          }
+        }
+        if (empty($configuration['authentication'])) {
+          // Remove the key if there are no more authentication providers
+          // supported.
+          unset($configuration['authentication']);
+        }
+        if (empty($configuration['formats'])) {
+          // Remove the key if there are no more formats supported.
+          unset($configuration['formats']);
+        }
+        if (empty($configuration['authentication']) || empty($configuration['formats'])) {
+          // If there no longer are any supported authentication providers or
+          // formats, this REST resource can no longer function, and so we
+          // cannot fix this config entity to keep it working.
+          $configuration = [];
+        }
+      }
+      if ($configuration_before != $configuration && !empty($configuration)) {
         $rest_config->set('configuration', $configuration);
         // Only mark the dependencies problems as fixed if there is any
         // configuration left.
diff --git a/core/modules/rest/src/Entity/RestResourceConfig.php b/core/modules/rest/src/Entity/RestResourceConfig.php
index f2b8b0e..a31a1a4 100644
--- a/core/modules/rest/src/Entity/RestResourceConfig.php
+++ b/core/modules/rest/src/Entity/RestResourceConfig.php
@@ -45,7 +45,9 @@ class RestResourceConfig extends ConfigEntityBase implements RestResourceConfigI
   /**
    * The REST resource configuration granularity.
    *
-   * @todo Currently only 'method', but https://www.drupal.org/node/2721595 will add 'resource'
+   * Currently either:
+   * - \Drupal\rest\RestResourceConfigInterface::METHOD_GRANULARITY
+   * - \Drupal\rest\RestResourceConfigInterface::RESOURCE_GRANULARITY
    *
    * @var string
    */
@@ -112,12 +114,13 @@ public function getResourcePlugin() {
    * {@inheritdoc}
    */
   public function getMethods() {
-    if ($this->granularity === RestResourceConfigInterface::METHOD_GRANULARITY) {
-      return $this->getMethodsForMethodGranularity();
-    }
-    else {
-      throw new \InvalidArgumentException("A different granularity then 'method' is not supported yet.");
-      // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595.
+    switch ($this->granularity) {
+      case RestResourceConfigInterface::METHOD_GRANULARITY:
+        return $this->getMethodsForMethodGranularity();
+      case RestResourceConfigInterface::RESOURCE_GRANULARITY:
+        return $this->configuration['methods'];
+      default:
+        throw new \InvalidArgumentException('Invalid granularity specified.');
     }
   }
 
@@ -136,12 +139,13 @@ protected function getMethodsForMethodGranularity() {
    * {@inheritdoc}
    */
   public function getAuthenticationProviders($method) {
-    if ($this->granularity === RestResourceConfigInterface::METHOD_GRANULARITY) {
-      return $this->getAuthenticationProvidersForMethodGranularity($method);
-    }
-    else {
-      throw new \InvalidArgumentException("A different granularity then 'method' is not supported yet.");
-      // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595.
+    switch ($this->granularity) {
+      case RestResourceConfigInterface::METHOD_GRANULARITY:
+        return $this->getAuthenticationProvidersForMethodGranularity($method);
+      case RestResourceConfigInterface::RESOURCE_GRANULARITY:
+        return $this->configuration['authentication'];
+      default:
+        throw new \InvalidArgumentException('Invalid granularity specified.');
     }
   }
 
@@ -166,12 +170,13 @@ public function getAuthenticationProvidersForMethodGranularity($method) {
    * {@inheritdoc}
    */
   public function getFormats($method) {
-    if ($this->granularity === RestResourceConfigInterface::METHOD_GRANULARITY) {
-      return $this->getFormatsForMethodGranularity($method);
-    }
-    else {
-      throw new \InvalidArgumentException("A different granularity then 'method' is not supported yet.");
-      // @todo Add resource-level granularity support in https://www.drupal.org/node/2721595.
+    switch ($this->granularity) {
+      case RestResourceConfigInterface::METHOD_GRANULARITY:
+        return $this->getFormatsForMethodGranularity($method);
+      case RestResourceConfigInterface::RESOURCE_GRANULARITY:
+        return $this->configuration['formats'];
+      default:
+        throw new \InvalidArgumentException('Invalid granularity specified.');
     }
   }
 
diff --git a/core/modules/rest/src/RestResourceConfigInterface.php b/core/modules/rest/src/RestResourceConfigInterface.php
index 7f34bc4..601bb0e 100644
--- a/core/modules/rest/src/RestResourceConfigInterface.php
+++ b/core/modules/rest/src/RestResourceConfigInterface.php
@@ -15,6 +15,11 @@
    */
   const METHOD_GRANULARITY = 'method';
 
+  /**
+   * Granularity value for per-resource configuration.
+   */
+  const RESOURCE_GRANULARITY = 'resource';
+
   /**
    * Retrieves the REST resource plugin.
    *
diff --git a/core/modules/rest/src/Tests/Update/ResourceGranularityUpdateTest.php b/core/modules/rest/src/Tests/Update/ResourceGranularityUpdateTest.php
new file mode 100644
index 0000000..c8cf89a
--- /dev/null
+++ b/core/modules/rest/src/Tests/Update/ResourceGranularityUpdateTest.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Drupal\rest\Tests\Update;
+
+use Drupal\system\Tests\Update\UpdatePathTestBase;
+
+/**
+ * Tests method-granularity REST config is simplified to resource-granularity.
+ *
+ * @see https://www.drupal.org/node/2721595
+ * @see rest_post_update_resource_granularity()
+ *
+ * @group rest
+ */
+class ResourceGranularityUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['rest', 'serialization'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
+      __DIR__ . '/../../../../rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php',
+    ];
+  }
+
+  /**
+   * Tests rest_post_update_simplify_resource_granularity().
+   */
+  public function testMethodGranularityConvertedToResourceGranularity() {
+    /** @var \Drupal\Core\Entity\EntityStorageInterface $resource_config_storage */
+    $resource_config_storage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');
+
+    // Make sure we have the expected values before the update.
+    $resource_config_entities = $resource_config_storage->loadMultiple();
+    $this->assertIdentical(['entity.comment', 'entity.node', 'entity.user'], array_keys($resource_config_entities));
+    $this->assertIdentical('method', $resource_config_entities['entity.node']->get('granularity'));
+    $this->assertIdentical('method', $resource_config_entities['entity.comment']->get('granularity'));
+    $this->assertIdentical('method', $resource_config_entities['entity.user']->get('granularity'));
+
+    // Read the existing 'entity:comment' and 'entity:user' resource
+    // configuration so we can verify it after the update.
+    $comment_resource_configuration = $resource_config_entities['entity.comment']->get('configuration');
+    $user_resource_configuration = $resource_config_entities['entity.user']->get('configuration');
+
+    $this->runUpdates();
+
+    // Make sure we have the expected values after the update.
+    $resource_config_entities = $resource_config_storage->loadMultiple();
+    $this->assertIdentical(['entity.comment', 'entity.node', 'entity.user'], array_keys($resource_config_entities));
+    // 'entity:node' should be updated.
+    $this->assertIdentical('resource', $resource_config_entities['entity.node']->get('granularity'));
+    $this->assertidentical($resource_config_entities['entity.node']->get('configuration'), [
+      'methods' => ['GET', 'POST', 'PATCH', 'DELETE'],
+      'formats' => ['hal_json'],
+      'authentication' => ['basic_auth'],
+    ]);
+    // 'entity:comment' should be unchanged.
+    $this->assertIdentical('method', $resource_config_entities['entity.comment']->get('granularity'));
+    $this->assertIdentical($comment_resource_configuration, $resource_config_entities['entity.comment']->get('configuration'));
+    // 'entity:user' should be unchanged.
+    $this->assertIdentical('method', $resource_config_entities['entity.user']->get('granularity'));
+    $this->assertIdentical($user_resource_configuration, $resource_config_entities['entity.user']->get('configuration'));
+  }
+
+}
diff --git a/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php b/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php
index 9100f16..097da35 100644
--- a/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php
+++ b/core/modules/rest/src/Tests/Update/RestConfigurationEntitiesUpdateTest.php
@@ -9,6 +9,8 @@
  * Tests that rest.settings is converted to rest_resource_config entities.
  *
  * @see https://www.drupal.org/node/2308745
+ * @see rest_update_8201()
+ * @see rest_post_update_create_rest_resource_config_entities()
  *
  * @group rest
  */
@@ -43,10 +45,6 @@ public function testResourcesConvertedToConfigEntities() {
     $resource_config_entities = $resource_config_storage->loadMultiple();
     $this->assertIdentical([], array_keys($resource_config_entities));
 
-    // Read the existing 'entity:node' resource configuration so we can verify
-    // it after the update.
-    $node_configuration = $rest_settings->getRawData()['resources']['entity:node'];
-
     $this->runUpdates();
 
     // Make sure we have the expected values after the update.
@@ -55,8 +53,12 @@ public function testResourcesConvertedToConfigEntities() {
     $resource_config_entities = $resource_config_storage->loadMultiple();
     $this->assertIdentical(['entity.node'], array_keys($resource_config_entities));
     $node_resource_config_entity = $resource_config_entities['entity.node'];
-    $this->assertIdentical(RestResourceConfigInterface::METHOD_GRANULARITY, $node_resource_config_entity->get('granularity'));
-    $this->assertIdentical($node_configuration, $node_resource_config_entity->get('configuration'));
+    $this->assertIdentical(RestResourceConfigInterface::RESOURCE_GRANULARITY, $node_resource_config_entity->get('granularity'));
+    $this->assertIdentical([
+      'methods' => ['GET'],
+      'formats' => ['json'],
+      'authentication' => ['basic_auth'],
+    ], $node_resource_config_entity->get('configuration'));
     $this->assertIdentical(['module' => ['basic_auth', 'node', 'serialization']], $node_resource_config_entity->getDependencies());
   }
 
diff --git a/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php b/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_post_update_resource_granularity.php
new file mode 100644
index 0000000000000000000000000000000000000000..d657afcf9c13b177c952c1f144825d7b6a007ee3
GIT binary patch
literal 5322
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

literal 0
HcmV?d00001

diff --git a/core/modules/rest/tests/fixtures/update/rest.resource.entity.comment_2721595.yml b/core/modules/rest/tests/fixtures/update/rest.resource.entity.comment_2721595.yml
new file mode 100644
index 0000000..7e86bab
--- /dev/null
+++ b/core/modules/rest/tests/fixtures/update/rest.resource.entity.comment_2721595.yml
@@ -0,0 +1,32 @@
+id: entity.comment
+plugin_id: 'entity:comment'
+granularity: method
+configuration:
+  GET:
+    supported_formats:
+      - hal_json
+      # This resource has a method-specific format.
+      # @see \Drupal\rest\Tests\Update\ResourceGranularityUpdateTest
+      - xml
+    supported_auth:
+      - basic_auth
+  POST:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+  PATCH:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+  DELETE:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+dependencies:
+  module:
+    - node
+    - basic_auth
+    - hal
diff --git a/core/modules/rest/tests/fixtures/update/rest.resource.entity.node_2721595.yml b/core/modules/rest/tests/fixtures/update/rest.resource.entity.node_2721595.yml
new file mode 100644
index 0000000..0cf4d78
--- /dev/null
+++ b/core/modules/rest/tests/fixtures/update/rest.resource.entity.node_2721595.yml
@@ -0,0 +1,29 @@
+id: entity.node
+plugin_id: 'entity:node'
+granularity: method
+configuration:
+  GET:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+  POST:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+  PATCH:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+  DELETE:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+dependencies:
+  module:
+    - node
+    - basic_auth
+    - hal
diff --git a/core/modules/rest/tests/fixtures/update/rest.resource.entity.user_2721595.yml b/core/modules/rest/tests/fixtures/update/rest.resource.entity.user_2721595.yml
new file mode 100644
index 0000000..593a260
--- /dev/null
+++ b/core/modules/rest/tests/fixtures/update/rest.resource.entity.user_2721595.yml
@@ -0,0 +1,32 @@
+id: entity.user
+plugin_id: 'entity:user'
+granularity: method
+configuration:
+  GET:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+      # This resource has a method-specific authentication.
+      # @see \Drupal\rest\Tests\Update\ResourceGranularityUpdateTest
+      - oauth
+  POST:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+  PATCH:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+  DELETE:
+    supported_formats:
+      - hal_json
+    supported_auth:
+      - basic_auth
+dependencies:
+  module:
+    - node
+    - basic_auth
+    - hal
diff --git a/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php b/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php
index b9a9062..86e80f2 100644
--- a/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php
+++ b/core/modules/rest/tests/src/Kernel/Entity/ConfigDependenciesTest.php
@@ -21,72 +21,78 @@ class ConfigDependenciesTest extends KernelTestBase {
 
   /**
    * @covers ::calculateDependencies
-   * @covers ::calculateDependenciesForMethodGranularity
+   *
+   * @dataProvider providerBasicDependencies
    */
-  public function testCalculateDependencies() {
+  public function testCalculateDependencies(array $configuration) {
     $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']);
 
-    $rest_config = RestResourceConfig::create([
-      'plugin_id' => 'entity:entity_test',
-      'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
-      'configuration' => [
-        'GET' => [
-          'supported_auth' => ['cookie'],
-          'supported_formats' => ['json'],
-        ],
-        'POST' => [
-          'supported_auth' => ['basic_auth'],
-          'supported_formats' => ['hal_json'],
-        ],
-      ],
-    ]);
+    $rest_config = RestResourceConfig::create($configuration);
 
     $result = $config_dependencies->calculateDependencies($rest_config);
     $this->assertEquals(['module' => [
-      'serialization', 'basic_auth', 'hal',
+      'basic_auth', 'hal', 'serialization',
     ]], $result);
   }
 
   /**
    * @covers ::onDependencyRemoval
-   * @covers ::calculateDependenciesForMethodGranularity
+   * @covers ::onDependencyRemovalForMethodGranularity
+   * @covers ::onDependencyRemovalForResourceGranularity
+   *
+   * @dataProvider providerBasicDependencies
    */
-  public function testOnDependencyRemovalRemoveUnrelatedDependency() {
+  public function testOnDependencyRemovalRemoveUnrelatedDependency(array $configuration) {
     $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']);
 
-    $rest_config = RestResourceConfig::create([
-      'plugin_id' => 'entity:entity_test',
-      'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
-      'configuration' => [
-        'GET' => [
-          'supported_auth' => ['cookie'],
-          'supported_formats' => ['json'],
-        ],
-        'POST' => [
-          'supported_auth' => ['basic_auth'],
-          'supported_formats' => ['hal_json'],
-        ],
-      ],
-    ]);
+    $rest_config = RestResourceConfig::create($configuration);
 
     $this->assertFalse($config_dependencies->onDependencyRemoval($rest_config, ['module' => ['node']]));
-    $this->assertEquals([
-      'GET' => [
-        'supported_auth' => ['cookie'],
-        'supported_formats' => ['json'],
+    $this->assertEquals($configuration['configuration'], $rest_config->get('configuration'));
+  }
+
+  /**
+   * @return array
+   *   An array with numerical keys:
+   *   0. The original REST resource configuration.
+   */
+  public function providerBasicDependencies() {
+    return [
+      'method' => [
+        [
+          'plugin_id' => 'entity:entity_test',
+          'granularity' => RestResourceConfigInterface::METHOD_GRANULARITY,
+          'configuration' => [
+            'GET' => [
+              'supported_auth' => ['cookie'],
+              'supported_formats' => ['json'],
+            ],
+            'POST' => [
+              'supported_auth' => ['basic_auth'],
+              'supported_formats' => ['hal_json'],
+            ],
+          ],
+        ],
       ],
-      'POST' => [
-        'supported_auth' => ['basic_auth'],
-        'supported_formats' => ['hal_json'],
+      'resource' => [
+        [
+          'plugin_id' => 'entity:entity_test',
+          'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
+          'configuration' => [
+            'methods' => ['GET', 'POST'],
+            'formats' => ['json', 'hal_json'],
+            'authentication' => ['cookie', 'basic_auth'],
+          ],
+        ],
       ],
-    ], $rest_config->get('configuration'));
+    ];
   }
 
   /**
    * @covers ::onDependencyRemoval
-   * @covers ::calculateDependenciesForMethodGranularity
+   * @covers ::onDependencyRemovalForMethodGranularity
    */
-  public function testOnDependencyRemovalRemoveFormat() {
+  public function testOnDependencyRemovalRemoveFormatForMethodGranularity() {
     $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']);
 
     $rest_config = RestResourceConfig::create([
@@ -120,7 +126,7 @@ public function testOnDependencyRemovalRemoveFormat() {
 
   /**
    * @covers ::onDependencyRemoval
-   * @covers ::calculateDependenciesForMethodGranularity
+   * @covers ::onDependencyRemovalForMethodGranularity
    */
   public function testOnDependencyRemovalRemoveAuth() {
     $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']);
@@ -156,7 +162,7 @@ public function testOnDependencyRemovalRemoveAuth() {
 
   /**
    * @covers ::onDependencyRemoval
-   * @covers ::calculateDependenciesForMethodGranularity
+   * @covers ::onDependencyRemovalForMethodGranularity
    */
   public function testOnDependencyRemovalRemoveAuthAndFormats() {
     $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']);
@@ -189,4 +195,95 @@ public function testOnDependencyRemovalRemoveAuthAndFormats() {
     ], $rest_config->get('configuration'));
   }
 
+  /**
+   * @covers ::onDependencyRemoval
+   * @covers ::onDependencyRemovalForResourceGranularity
+   *
+   * @dataProvider providerOnDependencyRemovalForResourceGranularity
+   */
+  public function testOnDependencyRemovalForResourceGranularity(array $configuration, $module, $expected_configuration) {
+    assert('is_string($module)');
+    assert('$expected_configuration === FALSE || is_array($expected_configuration)');
+    $config_dependencies = new ConfigDependencies(['hal_json' => 'hal', 'json' => 'serialization'], ['basic_auth' => 'basic_auth']);
+
+    $rest_config = RestResourceConfig::create($configuration);
+
+    $this->assertSame(!empty($expected_configuration), $config_dependencies->onDependencyRemoval($rest_config, ['module' => [$module]]));
+    if (!empty($expected_configuration)) {
+      $this->assertEquals($expected_configuration, $rest_config->get('configuration'));
+    }
+  }
+
+  /**
+   * @return array
+   *   An array with numerical keys:
+   *   0. The original REST resource configuration.
+   *   1. The module to uninstall (the dependency that is about to be removed).
+   *   2. The expected configuration after uninstalling this module.
+   */
+  public function providerOnDependencyRemovalForResourceGranularity() {
+    return [
+      'resource with multiple formats' => [
+        [
+          'plugin_id' => 'entity:entity_test',
+          'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
+          'configuration' => [
+            'methods' => ['GET', 'POST'],
+            'formats' => ['json', 'hal_json'],
+            'authentication' => ['cookie', 'basic_auth'],
+          ],
+        ],
+        'hal',
+        [
+          'methods' => ['GET', 'POST'],
+          'formats' => ['json'],
+          'authentication' => ['cookie', 'basic_auth'],
+        ]
+      ],
+      'resource with only HAL+JSON format' => [
+        [
+          'plugin_id' => 'entity:entity_test',
+          'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
+          'configuration' => [
+            'methods' => ['GET', 'POST'],
+            'formats' => ['hal_json'],
+            'authentication' => ['cookie', 'basic_auth'],
+          ],
+        ],
+        'hal',
+        FALSE
+      ],
+      'resource with multiple authentication providers' => [
+        [
+          'plugin_id' => 'entity:entity_test',
+          'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
+          'configuration' => [
+            'methods' => ['GET', 'POST'],
+            'formats' => ['json', 'hal_json'],
+            'authentication' => ['cookie', 'basic_auth'],
+          ],
+        ],
+        'basic_auth',
+        [
+          'methods' => ['GET', 'POST'],
+          'formats' => ['json', 'hal_json'],
+          'authentication' => ['cookie'],
+        ]
+      ],
+      'resource with only basic_auth authentication' => [
+        [
+          'plugin_id' => 'entity:entity_test',
+          'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
+          'configuration' => [
+            'methods' => ['GET', 'POST'],
+            'formats' => ['json', 'hal_json'],
+            'authentication' => ['basic_auth'],
+          ],
+        ],
+        'basic_auth',
+        FALSE,
+      ],
+    ];
+  }
+
 }
diff --git a/core/modules/system/src/Tests/System/ResponseGeneratorTest.php b/core/modules/system/src/Tests/System/ResponseGeneratorTest.php
index d0d22a2..d2f8f5f 100644
--- a/core/modules/system/src/Tests/System/ResponseGeneratorTest.php
+++ b/core/modules/system/src/Tests/System/ResponseGeneratorTest.php
@@ -53,13 +53,18 @@ function testGeneratorHeaderAdded() {
     $this->assertEqual('text/html; charset=UTF-8', $this->drupalGetHeader('Content-Type'));
     $this->assertEqual($expectedGeneratorHeader, $this->drupalGetHeader('X-Generator'));
 
-    // Enable rest API for nodes
-    $this->enableService('entity:node', 'GET', 'json');
+    // Enable cookie-based authentication for the entity:node REST resource.
+    /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */
+    $resource_config = $this->resourceConfigStorage->load('entity.node');
+    $configuration = $resource_config->get('configuration');
+    $configuration['authentication'][] = 'cookie';
+    $resource_config->set('configuration', $configuration)->save();
+    $this->rebuildCache();
 
     // Tests to see if this also works for a non-html request
-    $this->httpRequest($node->urlInfo()->setOption('query', ['_format' => 'json']), 'GET');
+    $this->httpRequest($node->urlInfo()->setOption('query', ['_format' => 'hal_json']), 'GET');
     $this->assertResponse(200);
-    $this->assertEqual('application/json', $this->drupalGetHeader('Content-Type'));
+    $this->assertEqual('application/hal+json', $this->drupalGetHeader('Content-Type'));
     $this->assertEqual($expectedGeneratorHeader, $this->drupalGetHeader('X-Generator'));
 
   }
