From 23ed767a23b96415d73d164e6ceea43a90374eed Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Mon, 1 Aug 2016 17:35:08 +0100
Subject: [PATCH] Revert "Issue #2664780 by Wim Leers, tedbow, dawehner,
 klausi: Remove REST's resource- and verb-specific permissions for
 EntityResource, but provide BC and document why it's necessary for other
 resources"

This reverts commit 85d755a992d89ac101580a9d3fa692b2b3ed7112.
---
 .../src/Tests/Rest/DbLogResourceTest.php      |  4 +-
 .../page_cache/src/Tests/PageCacheTest.php    |  5 ++
 .../rest/config/install/rest.settings.yml     |  8 ---
 .../rest/config/schema/rest.schema.yml        |  3 -
 core/modules/rest/rest.install                | 10 ---
 core/modules/rest/src/Plugin/ResourceBase.php | 47 +++-----------
 .../rest/src/Plugin/ResourceInterface.php     |  4 --
 .../Plugin/rest/resource/EntityResource.php   | 31 +--------
 core/modules/rest/src/RequestHandler.php      | 13 +++-
 core/modules/rest/src/Tests/AuthTest.php      |  1 +
 core/modules/rest/src/Tests/CreateTest.php    | 23 +++----
 core/modules/rest/src/Tests/CsrfTest.php      |  1 +
 core/modules/rest/src/Tests/DeleteTest.php    |  1 +
 core/modules/rest/src/Tests/NodeTest.php      |  1 +
 core/modules/rest/src/Tests/PageCacheTest.php |  4 ++
 core/modules/rest/src/Tests/ReadTest.php      |  8 +++
 .../EntityResourcePermissionsUpdateTest.php   | 56 -----------------
 core/modules/rest/src/Tests/UpdateTest.php    | 23 ++++---
 .../update/drupal-8.rest-rest_update_8203.php | 63 -------------------
 .../Tests/System/ResponseGeneratorTest.php    |  1 +
 .../Render/ConfigurableLanguageRenderer.php   |  2 +-
 .../Entity/Render/DefaultLanguageRenderer.php |  6 +-
 .../src/Entity/Render/EntityFieldRenderer.php |  3 +-
 .../Render/EntityTranslationRenderTrait.php   |  6 +-
 .../Render/EntityTranslationRendererBase.php  | 63 ++++---------------
 .../views/src/Entity/Render/RendererBase.php  |  2 +-
 .../Render/TranslationLanguageRenderer.php    | 23 +++----
 .../views/src/Plugin/views/row/EntityRow.php  | 12 +---
 .../src/Plugin/views/row/RowPluginBase.php    |  2 +-
 .../test_views/views.view.test_entity_row.yml | 13 +---
 .../tests/src/Kernel/Plugin/RowEntityTest.php | 52 ++++-----------
 31 files changed, 114 insertions(+), 377 deletions(-)
 delete mode 100644 core/modules/rest/src/Tests/Update/EntityResourcePermissionsUpdateTest.php
 delete mode 100644 core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8203.php

diff --git a/core/modules/dblog/src/Tests/Rest/DbLogResourceTest.php b/core/modules/dblog/src/Tests/Rest/DbLogResourceTest.php
index 279d202a1456..1dd1f1145460 100644
--- a/core/modules/dblog/src/Tests/Rest/DbLogResourceTest.php
+++ b/core/modules/dblog/src/Tests/Rest/DbLogResourceTest.php
@@ -53,13 +53,13 @@ public function testWatchdog() {
     $response = $this->httpRequest(Url::fromRoute('rest.dblog.GET.' . $this->defaultFormat, ['id' => 9999, '_format' => $this->defaultFormat]), 'GET');
     $this->assertResponse(404);
     $decoded = Json::decode($response);
-    $this->assertEqual($decoded['message'], 'Log entry with ID 9999 was not found', 'Response message is correct.');
+    $this->assertEqual($decoded['error'], 'Log entry with ID 9999 was not found', 'Response message is correct.');
 
     // Make a bad request (a true malformed request would never be a route match).
     $response = $this->httpRequest(Url::fromRoute('rest.dblog.GET.' . $this->defaultFormat, ['id' => 0, '_format' => $this->defaultFormat]), 'GET');
     $this->assertResponse(400);
     $decoded = Json::decode($response);
-    $this->assertEqual($decoded['message'], 'No log entry ID was provided', 'Response message is correct.');
+    $this->assertEqual($decoded['error'], 'No log entry ID was provided', 'Response message is correct.');
   }
 
 }
diff --git a/core/modules/page_cache/src/Tests/PageCacheTest.php b/core/modules/page_cache/src/Tests/PageCacheTest.php
index 22b8b51a1675..64e4ee9f8c37 100644
--- a/core/modules/page_cache/src/Tests/PageCacheTest.php
+++ b/core/modules/page_cache/src/Tests/PageCacheTest.php
@@ -8,6 +8,7 @@
 use Drupal\entity_test\Entity\EntityTest;
 use Drupal\simpletest\WebTestBase;
 use Drupal\Core\Cache\Cache;
+use Drupal\user\Entity\Role;
 use Drupal\user\RoleInterface;
 
 /**
@@ -135,6 +136,10 @@ function testQueryParameterFormatRequests() {
     $node = $this->drupalCreateNode(['type' => 'article']);
     $node_uri = $node->urlInfo();
     $node_url_with_hal_json_format = $node->urlInfo('canonical')->setRouteParameter('_format', 'hal_json');
+    /** @var \Drupal\user\RoleInterface $role */
+    $role = Role::load('anonymous');
+    $role->grantPermission('restful get entity:node');
+    $role->save();
 
     $this->drupalGet($node_uri);
     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
diff --git a/core/modules/rest/config/install/rest.settings.yml b/core/modules/rest/config/install/rest.settings.yml
index eb3da2df4ca6..2d8185e5c45d 100644
--- a/core/modules/rest/config/install/rest.settings.yml
+++ b/core/modules/rest/config/install/rest.settings.yml
@@ -1,11 +1,3 @@
 # Set the domain for REST type and relation links.
 # If left blank, the site's domain will be used.
 link_domain: ~
-
-# Before Drupal 8.2, EntityResource used permissions as well as the entity
-# access system for access checking. This was confusing, and it only did this
-# for historical reasons. New Drupal installations opt out from this by default
-# (hence this is set to false), existing installations opt in to it.
-# @see rest_update_8203()
-# @see https://www.drupal.org/node/2664780
-bc_entity_resource_permissions: false
diff --git a/core/modules/rest/config/schema/rest.schema.yml b/core/modules/rest/config/schema/rest.schema.yml
index 2c255ab88cd4..04f88a6fada0 100644
--- a/core/modules/rest/config/schema/rest.schema.yml
+++ b/core/modules/rest/config/schema/rest.schema.yml
@@ -6,9 +6,6 @@ rest.settings:
     link_domain:
       type: string
       label: 'Domain of the relation'
-    bc_entity_resource_permissions:
-      type: boolean
-      label: 'Whether the pre Drupal 8.2.x behavior of having permissions for EntityResource is enabled or not.'
 
 # Method-level granularity of REST resource configuration.
 rest_resource.method:
diff --git a/core/modules/rest/rest.install b/core/modules/rest/rest.install
index 90f4ec92b292..4cfaa112fb0a 100644
--- a/core/modules/rest/rest.install
+++ b/core/modules/rest/rest.install
@@ -65,16 +65,6 @@ function rest_update_8202() {
   }
 }
 
-/**
- * Enable BC for EntityResource: continue to use permissions.
- */
-function rest_update_8203() {
-  $config_factory = \Drupal::configFactory();
-  $rest_settings = $config_factory->getEditable('rest.settings');
-  $rest_settings->set('bc_entity_resource_permissions', TRUE)
-    ->save(TRUE);
-}
-
 /**
  * @} End of "defgroup updates-8.1.x-to-8.2.x".
  */
diff --git a/core/modules/rest/src/Plugin/ResourceBase.php b/core/modules/rest/src/Plugin/ResourceBase.php
index 93684dbbc415..549ac544a30d 100644
--- a/core/modules/rest/src/Plugin/ResourceBase.php
+++ b/core/modules/rest/src/Plugin/ResourceBase.php
@@ -12,11 +12,6 @@
 /**
  * Common base class for resource plugins.
  *
- * Note that this base class' implementation of the permissions() method
- * generates a permission for every method for a resource. If your resource
- * already has its own access control mechanism, you should opt out from this
- * default permissions() method by overriding it.
- *
  * @see \Drupal\rest\Annotation\RestResource
  * @see \Drupal\rest\Plugin\Type\ResourcePluginManager
  * @see \Drupal\rest\Plugin\ResourceInterface
@@ -184,7 +179,7 @@ public function availableMethods() {
   }
 
   /**
-   * Gets the base route for a particular method.
+   * Setups the base route for all HTTP methods.
    *
    * @param string $canonical_path
    *   The canonical path for the resource.
@@ -195,48 +190,20 @@ public function availableMethods() {
    *   The created base route.
    */
   protected function getBaseRoute($canonical_path, $method) {
-    return new Route($canonical_path, array(
+    $lower_method = strtolower($method);
+
+    $route = new Route($canonical_path, array(
       '_controller' => 'Drupal\rest\RequestHandler::handle',
+    ), array(
+      '_permission' => "restful $lower_method $this->pluginId",
     ),
-      $this->getBaseRouteRequirements($method),
       array(),
       '',
       array(),
       // The HTTP method is a requirement for this route.
       array($method)
     );
-  }
-
-  /**
-   * Gets the base route requirements for a particular method.
-   *
-   * @param $method
-   *   The HTTP method to be used for the route.
-   *
-   * @return array
-   *   An array of requirements for parameters.
-   */
-  protected function getBaseRouteRequirements($method) {
-    $lower_method = strtolower($method);
-    // Every route MUST have requirements that result in the access manager
-    // having access checks to check. If it does not, the route is made
-    // inaccessible. So, we default to granting access to everyone. If a
-    // permission exists, then we add that below. The access manager requires
-    // that ALL access checks must grant access, so this still results in
-    // correct behavior.
-    $requirements = [
-      '_access' => 'TRUE',
-    ];
-
-    // Only specify route requirements if the default permission exists. For any
-    // more advanced route definition, resource plugins extending this base
-    // class must override this method.
-    $permission = "restful $lower_method $this->pluginId";
-    if (isset($this->permissions()[$permission])) {
-      $requirements['_permission'] = $permission;
-    }
-
-    return $requirements;
+    return $route;
   }
 
 }
diff --git a/core/modules/rest/src/Plugin/ResourceInterface.php b/core/modules/rest/src/Plugin/ResourceInterface.php
index 7e92c571c494..0bc2bfb2a83b 100644
--- a/core/modules/rest/src/Plugin/ResourceInterface.php
+++ b/core/modules/rest/src/Plugin/ResourceInterface.php
@@ -33,10 +33,6 @@ public function routes();
    * A resource plugin can define a set of user permissions that are used on the
    * routes for this resource or for other purposes.
    *
-   * It is not required for a resource plugin to specify permissions: if they
-   * have their own access control mechanism, they can use that, and return the
-   * empty array.
-   *
    * @return array
    *   The permission array.
    */
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 5cf42ddf64c9..8b96940e317b 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -6,7 +6,6 @@
 use Drupal\Core\Config\Entity\ConfigEntityType;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
-use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\rest\Plugin\ResourceBase;
@@ -43,13 +42,6 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
    */
   protected $entityType;
 
-  /**
-   * The config factory.
-   *
-   * @var \Drupal\Core\Config\ConfigFactoryInterface
-   */
-  protected $configFactory;
-
   /**
    * Constructs a Drupal\rest\Plugin\rest\resource\EntityResource object.
    *
@@ -65,13 +57,10 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
    *   The available serialization formats.
    * @param \Psr\Log\LoggerInterface $logger
    *   A logger instance.
-   * @param \Drupal\Core\Config\ConfigFactoryInterface
-   *   The config factory.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, $serializer_formats, LoggerInterface $logger, ConfigFactoryInterface $config_factory) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, $serializer_formats, LoggerInterface $logger) {
     parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
     $this->entityType = $entity_type_manager->getDefinition($plugin_definition['entity_type']);
-    $this->configFactory = $config_factory;
   }
 
   /**
@@ -84,8 +73,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_definition,
       $container->get('entity_type.manager'),
       $container->getParameter('serializer.formats'),
-      $container->get('logger.factory')->get('rest'),
-      $container->get('config.factory')
+      $container->get('logger.factory')->get('rest')
     );
   }
 
@@ -309,21 +297,6 @@ protected function validate(EntityInterface $entity) {
     }
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function permissions() {
-    // @see https://www.drupal.org/node/2664780
-    if ($this->configFactory->get('rest.settings')->get('bc_entity_resource_permissions')) {
-      // The default Drupal 8.0.x and 8.1.x behavior.
-      return parent::permissions();
-    }
-    else {
-      // The default Drupal 8.2.x behavior.
-      return [];
-    }
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php
index 088dca298932..d75cfbbd73a2 100644
--- a/core/modules/rest/src/RequestHandler.php
+++ b/core/modules/rest/src/RequestHandler.php
@@ -12,6 +12,7 @@
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\HttpException;
 use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
 use Symfony\Component\Serializer\Exception\UnexpectedValueException;
 use Symfony\Component\Serializer\SerializerInterface;
@@ -130,7 +131,17 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
 
     // Invoke the operation on the resource plugin.
     $format = $this->getResponseFormat($route_match, $request);
-    $response = call_user_func_array(array($resource, $method), array_merge($parameters, array($unserialized, $request)));
+    try {
+      $response = call_user_func_array(array($resource, $method), array_merge($parameters, array($unserialized, $request)));
+    }
+    catch (HttpException $e) {
+      $error['error'] = $e->getMessage();
+      $content = $serializer->serialize($error, $format);
+      // Add the default content type, but only if the headers from the
+      // exception have not specified it already.
+      $headers = $e->getHeaders() + array('Content-Type' => $request->getMimeType($format));
+      return new Response($content, $e->getStatusCode(), $headers);
+    }
 
     return $response instanceof ResourceResponseInterface ?
       $this->renderResponse($request, $response, $serializer, $format, $resource_config) :
diff --git a/core/modules/rest/src/Tests/AuthTest.php b/core/modules/rest/src/Tests/AuthTest.php
index 9f4224f64f1a..e9fb7d736a37 100644
--- a/core/modules/rest/src/Tests/AuthTest.php
+++ b/core/modules/rest/src/Tests/AuthTest.php
@@ -43,6 +43,7 @@ public function testRead() {
     // resources via the REST API, but the request is authenticated
     // with session cookies.
     $permissions = $this->entityPermissions($entity_type, 'view');
+    $permissions[] = 'restful get entity:' . $entity_type;
     $account = $this->drupalCreateUser($permissions);
     $this->drupalLogin($account);
 
diff --git a/core/modules/rest/src/Tests/CreateTest.php b/core/modules/rest/src/Tests/CreateTest.php
index 8c7db07bac1c..d71b9e4a0f79 100644
--- a/core/modules/rest/src/Tests/CreateTest.php
+++ b/core/modules/rest/src/Tests/CreateTest.php
@@ -5,7 +5,6 @@
 use Drupal\comment\Tests\CommentTestTrait;
 use Drupal\Component\Serialization\Json;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Url;
 use Drupal\entity_test\Entity\EntityTest;
 use Drupal\node\Entity\Node;
 use Drupal\user\Entity\User;
@@ -50,6 +49,8 @@ public function testCreateResourceRestApiNotEnabled() {
 
     // Get the necessary user permissions to create the current entity type.
     $permissions = $this->entityPermissions($entity_type, 'create');
+    // POST method must be allowed for the current entity type.
+    $permissions[] = 'restful post entity:' . $entity_type;
 
     // Create the user.
     $account = $this->drupalCreateUser($permissions);
@@ -76,11 +77,7 @@ public function testCreateResourceRestApiNotEnabled() {
   /**
    * Ensure that an entity cannot be created without the restful permission.
    */
-  public function testCreateWithoutPermissionIfBcFlagIsOn() {
-    $rest_settings = $this->config('rest.settings');
-    $rest_settings->set('bc_entity_resource_permissions', TRUE)
-      ->save(TRUE);
-
+  public function testCreateWithoutPermission() {
     $entity_type = 'entity_test';
     // Enables the REST service for 'entity_test' entity type.
     $this->enableService('entity:' . $entity_type, 'POST');
@@ -99,14 +96,6 @@ public function testCreateWithoutPermissionIfBcFlagIsOn() {
     $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
     $this->assertResponse(403);
     $this->assertFalse(EntityTest::loadMultiple(), 'No entity has been created in the database.');
-
-    // Create a user with the 'restful post entity:entity_test permission and
-    // try again. This time, we should be able to create an entity.
-    $permissions[] = 'restful post entity:' . $entity_type;
-    $account = $this->drupalCreateUser($permissions);
-    $this->drupalLogin($account);
-    $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType);
-    $this->assertResponse(201);
   }
 
   /**
@@ -342,6 +331,8 @@ public function createAccountPerEntity($entity_type) {
     $accounts = array();
     // Get the necessary user permissions for the current $entity_type creation.
     $permissions = $this->entityPermissions($entity_type, 'create');
+    // POST method must be allowed for the current entity type.
+    $permissions[] = 'restful post entity:' . $entity_type;
     // Create user without administrative permissions.
     $accounts[] = $this->drupalCreateUser($permissions);
     // Add administrative permissions for nodes and users.
@@ -449,14 +440,14 @@ public function assertCreateEntityInvalidSerialized(EntityInterface $entity, $en
     $entity->set('uuid', $this->randomMachineName(129));
     $invalid_serialized = $this->serializer->serialize($entity, $this->defaultFormat, $context);
 
-    $response = $this->httpRequest(Url::fromRoute("rest.entity.$entity_type.POST")->setRouteParameter('_format', $this->defaultFormat), 'POST', $invalid_serialized, $this->defaultMimeType);
+    $response = $this->httpRequest('entity/' . $entity_type, 'POST', $invalid_serialized, $this->defaultMimeType);
 
     // Unprocessable Entity as response.
     $this->assertResponse(422);
 
     // Verify that the text of the response is correct.
     $error = Json::decode($response);
-    $this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
+    $this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
   }
 
   /**
diff --git a/core/modules/rest/src/Tests/CsrfTest.php b/core/modules/rest/src/Tests/CsrfTest.php
index f1c41a6fa78d..b23e23d433f4 100644
--- a/core/modules/rest/src/Tests/CsrfTest.php
+++ b/core/modules/rest/src/Tests/CsrfTest.php
@@ -43,6 +43,7 @@ protected function setUp() {
     // Create a user account that has the required permissions to create
     // resources via the REST API.
     $permissions = $this->entityPermissions($this->testEntityType, 'create');
+    $permissions[] = 'restful post entity:' . $this->testEntityType;
     $this->account = $this->drupalCreateUser($permissions);
 
     // Serialize an entity to a string to use in the content body of the POST
diff --git a/core/modules/rest/src/Tests/DeleteTest.php b/core/modules/rest/src/Tests/DeleteTest.php
index 60302bcda28f..7de3fb48e4a7 100644
--- a/core/modules/rest/src/Tests/DeleteTest.php
+++ b/core/modules/rest/src/Tests/DeleteTest.php
@@ -31,6 +31,7 @@ public function testDelete() {
       // Create a user account that has the required permissions to delete
       // resources via the REST API.
       $permissions = $this->entityPermissions($entity_type, 'delete');
+      $permissions[] = 'restful delete entity:' . $entity_type;
       $account = $this->drupalCreateUser($permissions);
       $this->drupalLogin($account);
 
diff --git a/core/modules/rest/src/Tests/NodeTest.php b/core/modules/rest/src/Tests/NodeTest.php
index 95dc475e0e83..dbdeaec0c5c6 100644
--- a/core/modules/rest/src/Tests/NodeTest.php
+++ b/core/modules/rest/src/Tests/NodeTest.php
@@ -32,6 +32,7 @@ class NodeTest extends RESTTestBase {
   protected function enableNodeConfiguration($method, $operation) {
     $this->enableService('entity:node', $method);
     $permissions = $this->entityPermissions('node', $operation);
+    $permissions[] = 'restful ' . strtolower($method) . ' entity:node';
     $account = $this->drupalCreateUser($permissions);
     $this->drupalLogin($account);
   }
diff --git a/core/modules/rest/src/Tests/PageCacheTest.php b/core/modules/rest/src/Tests/PageCacheTest.php
index 66e57f5a54d6..d4b09e98fcd2 100644
--- a/core/modules/rest/src/Tests/PageCacheTest.php
+++ b/core/modules/rest/src/Tests/PageCacheTest.php
@@ -48,6 +48,10 @@ public function testConfigChangePageCache() {
     $this->enableService('entity:entity_test', 'POST');
     $permissions = [
       'administer entity_test content',
+      'restful post entity:entity_test',
+      'restful get entity:entity_test',
+      'restful patch entity:entity_test',
+      'restful delete entity:entity_test',
     ];
     $account = $this->drupalCreateUser($permissions);
 
diff --git a/core/modules/rest/src/Tests/ReadTest.php b/core/modules/rest/src/Tests/ReadTest.php
index dc6f5743793d..99ebbaab5379 100644
--- a/core/modules/rest/src/Tests/ReadTest.php
+++ b/core/modules/rest/src/Tests/ReadTest.php
@@ -48,6 +48,7 @@ public function testRead() {
       // Create a user account that has the required permissions to read
       // resources via the REST API.
       $permissions = $this->entityPermissions($entity_type, 'view');
+      $permissions[] = 'restful get entity:' . $entity_type;
       $account = $this->drupalCreateUser($permissions);
       $this->drupalLogin($account);
 
@@ -122,6 +123,12 @@ public function testRead() {
         $data = Json::decode($response);
         $this->assertFalse(isset($data['field_test_text']), 'Field access protected field is not visible in the response.');
       }
+
+      // Try to read an entity without proper permissions.
+      $this->drupalLogout();
+      $response = $this->httpRequest($this->getReadUrl($entity), 'GET');
+      $this->assertResponse(403);
+      $this->assertIdentical('{"message":""}', $response);
     }
     // Try to read a resource, the user entity, which is not REST API enabled.
     $account = $this->drupalCreateUser();
@@ -148,6 +155,7 @@ public function testResourceStructure() {
     // Create a user account that has the required permissions to read
     // resources via the REST API.
     $permissions = $this->entityPermissions('node', 'view');
+    $permissions[] = 'restful get entity:node';
     $account = $this->drupalCreateUser($permissions);
     $this->drupalLogin($account);
 
diff --git a/core/modules/rest/src/Tests/Update/EntityResourcePermissionsUpdateTest.php b/core/modules/rest/src/Tests/Update/EntityResourcePermissionsUpdateTest.php
deleted file mode 100644
index 989159e1ca80..000000000000
--- a/core/modules/rest/src/Tests/Update/EntityResourcePermissionsUpdateTest.php
+++ /dev/null
@@ -1,56 +0,0 @@
-<?php
-
-namespace Drupal\rest\Tests\Update;
-
-use Drupal\system\Tests\Update\UpdatePathTestBase;
-
-/**
- * Tests that existing sites continue to use permissions for EntityResource.
- *
- * @see https://www.drupal.org/node/2664780
- *
- * @group rest
- */
-class EntityResourcePermissionsUpdateTest 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_update_8203.php',
-    ];
-  }
-
-  /**
-   * Tests rest_update_8203().
-   */
-  public function testBcEntityResourcePermissionSettingAdded() {
-    $permission_handler = $this->container->get('user.permissions');
-
-    $is_rest_resource_permission = function ($permission) {
-      return $permission['provider'] === 'rest' && (string) $permission['title'] !== 'Administer REST resource configuration';
-    };
-
-    // Make sure we have the expected values before the update.
-    $rest_settings = $this->config('rest.settings');
-    $this->assertFalse(array_key_exists('bc_entity_resource_permissions', $rest_settings->getRawData()));
-    $this->assertEqual([], array_filter($permission_handler->getPermissions(), $is_rest_resource_permission));
-
-    $this->runUpdates();
-
-    // Make sure we have the expected values after the update.
-    $rest_settings = $this->config('rest.settings');
-    $this->assertTrue(array_key_exists('bc_entity_resource_permissions', $rest_settings->getRawData()));
-    $this->assertTrue($rest_settings->get('bc_entity_resource_permissions'));
-    $rest_permissions = array_keys(array_filter($permission_handler->getPermissions(), $is_rest_resource_permission));
-    $this->assertEqual(['restful delete entity:node', 'restful get entity:node', 'restful patch entity:node', 'restful post entity:node'], $rest_permissions);
-  }
-
-}
diff --git a/core/modules/rest/src/Tests/UpdateTest.php b/core/modules/rest/src/Tests/UpdateTest.php
index 2f8030486fe9..dcab1ff76bd8 100644
--- a/core/modules/rest/src/Tests/UpdateTest.php
+++ b/core/modules/rest/src/Tests/UpdateTest.php
@@ -46,6 +46,7 @@ public function testPatchUpdate() {
     // Create a user account that has the required permissions to create
     // resources via the REST API.
     $permissions = $this->entityPermissions($entity_type, 'update');
+    $permissions[] = 'restful patch entity:' . $entity_type;
     $account = $this->drupalCreateUser($permissions);
     $this->drupalLogin($account);
 
@@ -175,10 +176,10 @@ public function testPatchUpdate() {
     // Send a UUID that is too long.
     $entity->set('uuid', $this->randomMachineName(129));
     $invalid_serialized = $serializer->serialize($entity, $this->defaultFormat, $context);
-    $response = $this->httpRequest($entity->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $invalid_serialized, $this->defaultMimeType);
+    $response = $this->httpRequest($entity->urlInfo(), 'PATCH', $invalid_serialized, $this->defaultMimeType);
     $this->assertResponse(422);
     $error = Json::decode($response);
-    $this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
+    $this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nuuid.0.value: <em class=\"placeholder\">UUID</em>: may not be longer than 128 characters.\n");
 
     // Try to update an entity without proper permissions.
     $this->drupalLogout();
@@ -201,6 +202,7 @@ public function testUpdateUser() {
     // Enables the REST service for 'user' entity type.
     $this->enableService('entity:' . $entity_type, 'PATCH');
     $permissions = $this->entityPermissions($entity_type, 'update');
+    $permissions[] = 'restful patch entity:' . $entity_type;
     $account = $this->drupalCreateUser($permissions);
     $account->set('mail', 'old-email@example.com');
     $this->drupalLogin($account);
@@ -214,18 +216,18 @@ public function testUpdateUser() {
     $context = ['account' => $account];
     $normalized = $serializer->normalize($account, $this->defaultFormat, $context);
     $serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
-    $response = $this->httpRequest($account->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $serialized, $this->defaultMimeType);
+    $response = $this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
     $this->assertResponse(422);
     $error = Json::decode($response);
-    $this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
+    $this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
 
     // Try and send the new email with a password.
     $normalized['pass'][0]['existing'] = 'wrong';
     $serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
-    $response = $this->httpRequest($account->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $serialized, $this->defaultMimeType);
+    $response = $this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
     $this->assertResponse(422);
     $error = Json::decode($response);
-    $this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
+    $this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\nmail: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Email</em>.\n");
 
     // Try again with the password.
     $normalized['pass'][0]['existing'] = $account->pass_raw;
@@ -238,10 +240,10 @@ public function testUpdateUser() {
     $normalized = $serializer->normalize($account, $this->defaultFormat, $context);
     $normalized['pass'][0]['value'] = $new_password;
     $serialized = $serializer->serialize($normalized, $this->defaultFormat, $context);
-    $response = $this->httpRequest($account->toUrl()->setRouteParameter('_format', $this->defaultFormat), 'PATCH', $serialized, $this->defaultMimeType);
+    $response = $this->httpRequest($account->urlInfo(), 'PATCH', $serialized, $this->defaultMimeType);
     $this->assertResponse(422);
     $error = Json::decode($response);
-    $this->assertEqual($error['message'], "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Password</em>.\n");
+    $this->assertEqual($error['error'], "Unprocessable Entity: validation failed.\npass: Your current password is missing or incorrect; it's required to change the <em class=\"placeholder\">Password</em>.\n");
 
     // Try again with the password.
     $normalized['pass'][0]['existing'] = $account->pass_raw;
@@ -262,6 +264,7 @@ public function testUpdateComment() {
     // Enables the REST service for 'comment' entity type.
     $this->enableService('entity:' . $entity_type, 'PATCH', ['hal_json', 'json']);
     $permissions = $this->entityPermissions($entity_type, 'update');
+    $permissions[] = 'restful patch entity:' . $entity_type;
     $account = $this->drupalCreateUser($permissions);
     $account->set('mail', 'old-email@example.com');
     $this->drupalLogin($account);
@@ -333,7 +336,7 @@ public function testUpdateComment() {
   protected function patchEntity(EntityInterface $entity, array $read_only_fields, AccountInterface $account, $format, $mime_type) {
     $serializer = $this->container->get('serializer');
 
-    $url = $entity->toUrl()->setRouteParameter('_format', $this->defaultFormat);
+    $url = $entity->toUrl();
     $context = ['account' => $account];
     // Certain fields are always read-only, others this user simply is not
     // allowed to modify. For all of them, ensure they are not serialized, else
@@ -356,7 +359,7 @@ protected function patchEntity(EntityInterface $entity, array $read_only_fields,
 
       $this->httpRequest($url, 'PATCH', $serialized, $mime_type);
       $this->assertResponse(403);
-      $this->assertResponseBody('{"message":"Access denied on updating field \\u0027' . $field . '\\u0027."}');
+      $this->assertResponseBody('{"error":"Access denied on updating field \'' . $field . '\'."}');
 
       if ($format === 'hal_json') {
         // We've just tried with this read-only field, now unset it.
diff --git a/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8203.php b/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8203.php
deleted file mode 100644
index 3ce8b6abdc04..000000000000
--- a/core/modules/rest/tests/fixtures/update/drupal-8.rest-rest_update_8203.php
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains database additions to drupal-8.bare.standard.php.gz for testing the
- * upgrade path of rest_update_8203().
- */
-
-use Drupal\Core\Database\Database;
-
-$connection = Database::getConnection();
-
-// Set the schema version.
-$connection->insert('key_value')
-  ->fields([
-    'collection' => 'system.schema',
-    'name' => 'rest',
-    'value' => 'i:8000;',
-  ])
-  ->fields([
-    'collection' => 'system.schema',
-    'name' => 'serialization',
-    'value' => 'i:8000;',
-  ])
-  ->execute();
-
-// Update core.extension.
-$extensions = $connection->select('config')
-  ->fields('config', ['data'])
-  ->condition('collection', '')
-  ->condition('name', 'core.extension')
-  ->execute()
-  ->fetchField();
-$extensions = unserialize($extensions);
-$extensions['module']['rest'] = 8000;
-$extensions['module']['serialization'] = 8000;
-$connection->update('config')
-  ->fields([
-    'data' => serialize($extensions),
-  ])
-  ->condition('collection', '')
-  ->condition('name', 'core.extension')
-  ->execute();
-
-// Install the rest configuration.
-$config = [
-  'resources' => [
-    'entity:node' => [
-      'GET' => [
-        'supported_formats' => ['json'],
-        'supported_auth' => ['basic_auth'],
-      ],
-    ],
-  ],
-  'link_domain' => '~',
-];
-$data = $connection->insert('config')
-  ->fields([
-    'name' => 'rest.settings',
-    'data' => serialize($config),
-    'collection' => ''
-  ])
-  ->execute();
diff --git a/core/modules/system/src/Tests/System/ResponseGeneratorTest.php b/core/modules/system/src/Tests/System/ResponseGeneratorTest.php
index f54c6516e84c..d2f8f5fbd51e 100644
--- a/core/modules/system/src/Tests/System/ResponseGeneratorTest.php
+++ b/core/modules/system/src/Tests/System/ResponseGeneratorTest.php
@@ -26,6 +26,7 @@ protected function setUp() {
     $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
 
     $permissions = $this->entityPermissions('node', 'view');
+    $permissions[] = 'restful get entity:node';
     $account = $this->drupalCreateUser($permissions);
     $this->drupalLogin($account);
   }
diff --git a/core/modules/views/src/Entity/Render/ConfigurableLanguageRenderer.php b/core/modules/views/src/Entity/Render/ConfigurableLanguageRenderer.php
index eeea6945f351..2e28f9df3bbe 100644
--- a/core/modules/views/src/Entity/Render/ConfigurableLanguageRenderer.php
+++ b/core/modules/views/src/Entity/Render/ConfigurableLanguageRenderer.php
@@ -39,7 +39,7 @@ public function __construct(ViewExecutable $view, LanguageManagerInterface $lang
   /**
    * {@inheritdoc}
    */
-  public function getLangcode(ResultRow $row, $relationship = 'none') {
+  public function getLangcode(ResultRow $row) {
     return $this->langcode;
   }
 
diff --git a/core/modules/views/src/Entity/Render/DefaultLanguageRenderer.php b/core/modules/views/src/Entity/Render/DefaultLanguageRenderer.php
index 4f90ced9794e..11dbb4ab87cd 100644
--- a/core/modules/views/src/Entity/Render/DefaultLanguageRenderer.php
+++ b/core/modules/views/src/Entity/Render/DefaultLanguageRenderer.php
@@ -12,10 +12,8 @@ class DefaultLanguageRenderer extends EntityTranslationRendererBase {
   /**
    * {@inheritdoc}
    */
-  public function getLangcode(ResultRow $row, $relationship = 'none') {
-    if ($entity = $this->getEntity($row, $relationship)) {
-      return $entity->getUntranslated()->language()->getId();
-    }
+  public function getLangcode(ResultRow $row) {
+    return $row->_entity->getUntranslated()->language()->getId();
   }
 
 }
diff --git a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php
index 93a3dd444d0c..1b05c1b2a360 100644
--- a/core/modules/views/src/Entity/Render/EntityFieldRenderer.php
+++ b/core/modules/views/src/Entity/Render/EntityFieldRenderer.php
@@ -200,8 +200,7 @@ protected function buildFields(array $values) {
       $field = $this->view->field[current($field_ids)];
       foreach ($values as $result_row) {
         if ($entity = $field->getEntity($result_row)) {
-          $relationship = isset($field->options['relationship']) ? $field->options['relationship'] : 'none';
-          $entities_by_bundles[$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row, $relationship);
+          $entities_by_bundles[$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row);
         }
       }
 
diff --git a/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php b/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php
index cdbe3ada5367..e08ef04d301e 100644
--- a/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php
+++ b/core/modules/views/src/Entity/Render/EntityTranslationRenderTrait.php
@@ -62,20 +62,18 @@ protected function getEntityTranslationRenderer() {
    *   The entity object the field value being processed is attached to.
    * @param \Drupal\views\ResultRow $row
    *   The result row the field value being processed belongs to.
-   * @param string $relationship
-   *   The relationship to be used, or 'none' by default.
    *
    * @return \Drupal\Core\Entity\FieldableEntityInterface
    *   The entity translation object for the specified row.
    */
-  public function getEntityTranslation(EntityInterface $entity, ResultRow $row, $relationship = 'none') {
+  public function getEntityTranslation(EntityInterface $entity, ResultRow $row) {
     // We assume the same language should be used for all entity fields
     // belonging to a single row, even if they are attached to different entity
     // types. Below we apply language fallback to ensure a valid value is always
     // picked.
     $translation = $entity;
     if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
-      $langcode = $this->getEntityTranslationRenderer()->getLangcode($row, $relationship);
+      $langcode = $this->getEntityTranslationRenderer()->getLangcode($row);
       $translation = $this->getEntityManager()->getTranslationFromContext($entity, $langcode);
     }
     return $translation;
diff --git a/core/modules/views/src/Entity/Render/EntityTranslationRendererBase.php b/core/modules/views/src/Entity/Render/EntityTranslationRendererBase.php
index c4787a413a75..e77992dd0196 100644
--- a/core/modules/views/src/Entity/Render/EntityTranslationRendererBase.php
+++ b/core/modules/views/src/Entity/Render/EntityTranslationRendererBase.php
@@ -15,13 +15,11 @@ abstract class EntityTranslationRendererBase extends RendererBase {
    *
    * @param \Drupal\views\ResultRow $row
    *   The result row.
-   * @param string $relationship
-   *   The relationship to be used, or 'none' by default.
    *
    * @return string
    *   A language code.
    */
-  abstract public function getLangcode(ResultRow $row, $relationship = 'none');
+  abstract public function getLangcode(ResultRow $row);
 
   /**
    * {@inheritdoc}
@@ -30,62 +28,27 @@ public function query(QueryPluginBase $query, $relationship = NULL) {
   }
 
   /**
-   * Runs before each entity is rendered.
-   *
-   * @param \Drupal\views\ResultRow[] $result
-   *   The full array of results from the query.
-   * @param string $relationship
-   *   The relationship to be used, or 'none' by default.
+   * {@inheritdoc}
    */
-  public function preRender(array $result, $relationship = 'none') {
+  public function preRender(array $result) {
     $view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
 
+    /** @var \Drupal\views\ResultRow $row */
     foreach ($result as $row) {
-      if ($entity = $this->getEntity($row, $relationship)) {
-        $entity->view = $this->view;
-        $this->build[$entity->id()] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $this->getLangcode($row, $relationship));
-      }
+      // @todo Take relationships into account.
+      //   See https://www.drupal.org/node/2457999.
+      $entity = $row->_entity;
+      $entity->view = $this->view;
+      $this->build[$entity->id()] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $this->getLangcode($row));
     }
   }
 
   /**
-   * Renders entity data.
-   *
-   * @param \Drupal\views\ResultRow $row
-   *   A single row of the query result.
-   * @param string $relationship
-   *   The relationship to be used, or 'none' by default.
-   *
-   * @return array
-   *   A renderable array for the entity data contained in the result row.
-   */
-  public function render(ResultRow $row, $relationship = 'none') {
-    if ($entity = $this->getEntity($row, $relationship)) {
-      $entity_id = $entity->id();
-      return $this->build[$entity_id];
-    }
-  }
-
-  /**
-   * Gets the entity assosiated with a row.
-   *
-   * @param \Drupal\views\ResultRow $row
-   *   The result row.
-   * @param string $relationship
-   *   (optional) The relationship.
-   *
-   * @return \Drupal\Core\Entity\EntityInterface|null
-   *   The entity might be optional, because the relationship entity might not
-   *   always exist.
+   * {@inheritdoc}
    */
-  protected function getEntity($row, $relationship = 'none') {
-    if ($relationship === 'none') {
-      return $row->_entity;
-    }
-    elseif (isset($row->_relationship_entities[$relationship])) {
-      return $row->_relationship_entities[$relationship];
-    }
-    return NULL;
+  public function render(ResultRow $row) {
+    $entity_id = $row->_entity->id();
+    return $this->build[$entity_id];
   }
 
 }
diff --git a/core/modules/views/src/Entity/Render/RendererBase.php b/core/modules/views/src/Entity/Render/RendererBase.php
index b47dd430812c..60327f615f87 100644
--- a/core/modules/views/src/Entity/Render/RendererBase.php
+++ b/core/modules/views/src/Entity/Render/RendererBase.php
@@ -93,7 +93,7 @@ abstract public function query(QueryPluginBase $query, $relationship = NULL);
   /**
    * Runs before each entity is rendered.
    *
-   * @param \Drupal\views\ResultRow[] $result
+   * @param $result
    *   The full array of results from the query.
    */
   public function preRender(array $result) {
diff --git a/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php b/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php
index 1d78c9d0951d..00ae52487185 100644
--- a/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php
+++ b/core/modules/views/src/Entity/Render/TranslationLanguageRenderer.php
@@ -40,34 +40,31 @@ public function query(QueryPluginBase $query, $relationship = NULL) {
   /**
    * {@inheritdoc}
    */
-  public function preRender(array $result, $relationship = 'none') {
+  public function preRender(array $result) {
     $view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
 
     /** @var \Drupal\views\ResultRow $row */
     foreach ($result as $row) {
-      if ($entity = $this->getEntity($row, $relationship)) {
-        $entity->view = $this->view;
-        $langcode = $this->getLangcode($row, $relationship);
-        $this->build[$entity->id()][$langcode] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $langcode);
-      }
+      $entity = $row->_entity;
+      $entity->view = $this->view;
+      $langcode = $this->getLangcode($row);
+      $this->build[$entity->id()][$langcode] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $this->getLangcode($row));
     }
   }
 
   /**
    * {@inheritdoc}
    */
-  public function render(ResultRow $row, $relationship = 'none') {
-    if ($entity = $this->getEntity($row, $relationship)) {
-      $entity_id = $entity->id();
-      $langcode = $this->getLangcode($row, $relationship);
-      return $this->build[$entity_id][$langcode];
-    }
+  public function render(ResultRow $row) {
+    $entity_id = $row->_entity->id();
+    $langcode = $this->getLangcode($row);
+    return $this->build[$entity_id][$langcode];
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getLangcode(ResultRow $row, $relationship = 'none') {
+  public function getLangcode(ResultRow $row) {
     return isset($row->{$this->langcodeAlias}) ? $row->{$this->langcodeAlias} : $this->languageManager->getDefaultLanguage()->getId();
   }
 
diff --git a/core/modules/views/src/Plugin/views/row/EntityRow.php b/core/modules/views/src/Plugin/views/row/EntityRow.php
index d54e03295ca1..f5365296531c 100644
--- a/core/modules/views/src/Plugin/views/row/EntityRow.php
+++ b/core/modules/views/src/Plugin/views/row/EntityRow.php
@@ -166,13 +166,7 @@ public function summaryTitle() {
    */
   public function query() {
     parent::query();
-    if (isset($this->options['relationship'], $this->view->relationship[$this->options['relationship']])) {
-      $relationship = $this->view->relationship[$this->options['relationship']]->alias;
-    }
-    else {
-      $relationship = NULL;
-    }
-    $this->getEntityTranslationRenderer()->query($this->view->getQuery(), $relationship);
+    $this->getEntityTranslationRenderer()->query($this->view->getQuery());
   }
 
   /**
@@ -181,7 +175,7 @@ public function query() {
   public function preRender($result) {
     parent::preRender($result);
     if ($result) {
-      $this->getEntityTranslationRenderer()->preRender($result, isset($this->options['relationship']) ? $this->options['relationship'] : 'none');
+      $this->getEntityTranslationRenderer()->preRender($result);
     }
   }
 
@@ -189,7 +183,7 @@ public function preRender($result) {
    * {@inheritdoc}
    */
   public function render($row) {
-    return $this->getEntityTranslationRenderer()->render($row, isset($this->options['relationship']) ? $this->options['relationship'] : 'none');
+    return $this->getEntityTranslationRenderer()->render($row);
   }
 
   /**
diff --git a/core/modules/views/src/Plugin/views/row/RowPluginBase.php b/core/modules/views/src/Plugin/views/row/RowPluginBase.php
index a60070bf86dc..d1e090e0aac7 100644
--- a/core/modules/views/src/Plugin/views/row/RowPluginBase.php
+++ b/core/modules/views/src/Plugin/views/row/RowPluginBase.php
@@ -90,7 +90,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
         $data = Views::viewsData()->get($relationship['table']);
         $base = $data[$relationship['field']]['relationship']['base'];
         if ($base == $this->base_table) {
-          $relationship_handler->init($executable, $this->displayHandler, $relationship);
+          $relationship_handler->init($executable, $relationship);
           $relationship_options[$relationship['id']] = $relationship_handler->adminLabel();
         }
       }
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row.yml
index c786e20443be..ad9f748197ea 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row.yml
@@ -6,8 +6,8 @@ label: ''
 module: views
 description: ''
 tag: ''
-base_table: entity_test
-base_field: id
+base_table: taxonomy_term_field_data
+base_field: nid
 core: '8'
 display:
   default:
@@ -21,17 +21,10 @@ display:
           offset: 0
         type: none
       row:
-        type: 'entity:entity_test'
+        type: 'entity:taxonomy_term'
         options:
           relationship: none
           view_mode: full
-      relationships:
-        user_id:
-          table: entity_test
-          field: user_id
-          id: user_id
-          relationship: none
-          plugin_id: standard
     display_plugin: default
     display_title: Master
     id: default
diff --git a/core/modules/views/tests/src/Kernel/Plugin/RowEntityTest.php b/core/modules/views/tests/src/Kernel/Plugin/RowEntityTest.php
index 0236b595d6db..8d2d86632a66 100644
--- a/core/modules/views/tests/src/Kernel/Plugin/RowEntityTest.php
+++ b/core/modules/views/tests/src/Kernel/Plugin/RowEntityTest.php
@@ -3,10 +3,10 @@
 namespace Drupal\Tests\views\Kernel\Plugin;
 
 use Drupal\Core\Form\FormState;
-use Drupal\entity_test\Entity\EntityTest;
-use Drupal\user\Entity\User;
 use Drupal\views\Views;
 use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
+use Drupal\taxonomy\Entity\Vocabulary;
+use Drupal\taxonomy\Entity\Term;
 
 /**
  * Tests the generic entity row plugin.
@@ -21,7 +21,7 @@ class RowEntityTest extends ViewsKernelTestBase {
    *
    * @var array
    */
-  public static $modules = ['entity_test', 'field', 'system', 'user'];
+  public static $modules = ['taxonomy', 'text', 'filter', 'field', 'system', 'node', 'user'];
 
   /**
    * Views used by this test.
@@ -34,55 +34,27 @@ class RowEntityTest extends ViewsKernelTestBase {
    * {@inheritdoc}
    */
   protected function setUp($import_test_views = TRUE) {
-    parent::setUp($import_test_views);
+    parent::setUp();
 
-    $this->installEntitySchema('entity_test');
-    $this->installEntitySchema('user');
+    $this->installEntitySchema('taxonomy_term');
+    $this->installConfig(array('taxonomy'));
+    \Drupal::service('router.builder')->rebuild();
   }
 
   /**
    * Tests the entity row handler.
    */
   public function testEntityRow() {
-    $user = User::create([
-      'name' => 'test user',
-    ]);
-    $user->save();
+    $vocab = Vocabulary::create(['name' => $this->randomMachineName(), 'vid' => strtolower($this->randomMachineName())]);
+    $vocab->save();
+    $term = Term::create(['name' => $this->randomMachineName(), 'vid' => $vocab->id() ]);
+    $term->save();
 
-    $entity_test = EntityTest::create([
-      'user_id' => $user->id(),
-      'name' => 'test entity test',
-    ]);
-    $entity_test->save();
-
-    // Ensure entities have different ids.
-    if ($entity_test->id() == $user->id()) {
-      $entity_test->delete();
-      $entity_test = EntityTest::create([
-        'user_id' => $user->id(),
-        'name' => 'test entity test',
-      ]);
-      $entity_test->save();
-    }
-
-    $view = Views::getView('test_entity_row');
-    $build = $view->preview();
-    $this->render($build);
-
-    $this->assertText('test entity test');
-    $this->assertNoText('Member for');
-
-    // Change the view to use a relationship to render the row.
     $view = Views::getView('test_entity_row');
-    $display = &$view->storage->getDisplay('default');
-    $display['display_options']['row']['type'] = 'entity:user';
-    $display['display_options']['row']['options']['relationship'] = 'user_id';
-    $view->setDisplay('default');
     $build = $view->preview();
     $this->render($build);
 
-    $this->assertNoText('test entity test');
-    $this->assertText('Member for');
+    $this->assertText($term->getName(), 'The rendered entity appears as row in the view.');
 
     // Tests the available view mode options.
     $form = array();
-- 
GitLab