diff --git a/core/modules/file/file.permissions.yml b/core/modules/file/file.permissions.yml
index 8575f20..afff9b5 100644
--- a/core/modules/file/file.permissions.yml
+++ b/core/modules/file/file.permissions.yml
@@ -1,2 +1,6 @@
 access files overview:
   title: 'Access the Files overview page'
+
+administer files:
+  title: 'Administer files'
+  restrict access: true
diff --git a/core/modules/file/src/FileAccessControlHandler.php b/core/modules/file/src/FileAccessControlHandler.php
index a82c460..2193f92 100644
--- a/core/modules/file/src/FileAccessControlHandler.php
+++ b/core/modules/file/src/FileAccessControlHandler.php
@@ -22,8 +22,12 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
     /** @var \Drupal\file\FileInterface $entity */
     if ($operation == 'download' || $operation == 'view') {
       if (\Drupal::service('file_system')->uriScheme($entity->getFileUri()) === 'public') {
-        // Always allow access to file in public file system.
-        return AccessResult::allowed();
+        if ($operation === 'download') {
+          return AccessResult::allowed();
+        }
+        else {
+          return AccessResult::allowedIfHasPermissions($account, ['access content', 'administer files'], 'OR');
+        }
       }
       elseif ($references = $this->getFileReferences($entity)) {
         foreach ($references as $field_name => $entity_map) {
@@ -48,8 +52,9 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
     if ($operation == 'delete' || $operation == 'update') {
       $account = $this->prepareUser($account);
       $file_uid = $entity->get('uid')->getValue();
-      // Only the file owner can delete and update the file entity.
-      if ($account->id() == $file_uid[0]['target_id']) {
+      // Only the file owner or user with 'administer files' permission can
+      // delete and update the file entity.
+      if ($account->id() === $file_uid[0]['target_id'] || $account->hasPermission('administer files')) {
         return AccessResult::allowed();
       }
       return AccessResult::forbidden();
diff --git a/core/modules/file/tests/src/Functional/FileManagedAccessTest.php b/core/modules/file/tests/src/Functional/FileManagedAccessTest.php
index 01419d4..b749ea2 100644
--- a/core/modules/file/tests/src/Functional/FileManagedAccessTest.php
+++ b/core/modules/file/tests/src/Functional/FileManagedAccessTest.php
@@ -12,6 +12,11 @@
 class FileManagedAccessTest extends FileManagedTestBase {
 
   /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['node'];
+
+  /**
    * Tests if public file is always accessible.
    */
   public function testFileAccess() {
@@ -29,7 +34,7 @@ public function testFileAccess() {
     $file->save();
 
     // Create authenticated user to check file access.
-    $account = $this->createUser(['access site reports']);
+    $account = $this->createUser(['access site reports', 'access content']);
 
     $this->assertTrue($file->access('view', $account), 'Public file is viewable to authenticated user');
     $this->assertTrue($file->access('download', $account), 'Public file is downloadable to authenticated user');
@@ -54,7 +59,7 @@ public function testFileAccess() {
     $file->save();
 
     // Create authenticated user to check file access.
-    $account = $this->createUser(['access site reports']);
+    $account = $this->createUser(['access site reports', 'access content']);
 
     $this->assertFalse($file->access('view', $account), 'Private file is not viewable to authenticated user');
     $this->assertFalse($file->access('download', $account), 'Private file is not downloadable to authenticated user');
diff --git a/core/modules/file/tests/src/Kernel/FileItemTest.php b/core/modules/file/tests/src/Kernel/FileItemTest.php
index 8219af1..7a0ddc0 100644
--- a/core/modules/file/tests/src/Kernel/FileItemTest.php
+++ b/core/modules/file/tests/src/Kernel/FileItemTest.php
@@ -10,6 +10,7 @@
 use Drupal\Tests\field\Kernel\FieldKernelTestBase;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\file\Entity\File;
+use Drupal\user\Entity\Role;
 
 /**
  * Tests using entity fields of the file field type.
@@ -42,6 +43,14 @@ class FileItemTest extends FieldKernelTestBase {
   protected function setUp() {
     parent::setUp();
 
+    $this->installEntitySchema('user');
+    $this->installConfig(['user']);
+    // Give anonymous users permission to access content, so that we can view
+    // and download public file.
+    $anonymous_role = Role::load(Role::ANONYMOUS_ID);
+    $anonymous_role->grantPermission('access content');
+    $anonymous_role->save();
+
     $this->installEntitySchema('file');
     $this->installSchema('file', ['file_usage']);
 
diff --git a/core/modules/hal/src/Normalizer/FileEntityNormalizer.php b/core/modules/hal/src/Normalizer/FileEntityNormalizer.php
index ec870e9..56fb38f 100644
--- a/core/modules/hal/src/Normalizer/FileEntityNormalizer.php
+++ b/core/modules/hal/src/Normalizer/FileEntityNormalizer.php
@@ -63,8 +63,11 @@ public function denormalize($data, $class, $format = NULL, array $context = [])
 
     $path = 'temporary://' . drupal_basename($data['uri'][0]['value']);
     $data['uri'] = file_unmanaged_save_data($file_data, $path);
-
-    return $this->entityManager->getStorage('file')->create($data);
+    if (!isset($data['_links']['type'])) {
+      return $this->entityManager->getStorage('file')->create($data);
+    }
+    $data['uri'] = [['value' => $data['uri']]];
+    return parent::denormalize($data, $class, $format, $context);
   }
 
 }
diff --git a/core/modules/hal/tests/src/Functional/EntityResource/File/FileHalJsonAnonTest.php b/core/modules/hal/tests/src/Functional/EntityResource/File/FileHalJsonAnonTest.php
new file mode 100644
index 0000000..9351fef
--- /dev/null
+++ b/core/modules/hal/tests/src/Functional/EntityResource/File/FileHalJsonAnonTest.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Drupal\Tests\hal\Functional\EntityResource\File;
+
+use Drupal\Tests\hal\Functional\EntityResource\HalEntityNormalizationTrait;
+use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
+use Drupal\Tests\rest\Functional\EntityResource\File\FileResourceTestBase;
+
+/**
+ * @group hal
+ */
+class FileHalJsonAnonTest extends FileResourceTestBase {
+
+  use HalEntityNormalizationTrait;
+  use AnonResourceTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['hal'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $format = 'hal_json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $mimeType = 'application/hal+json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedNormalizedEntity() {
+    $default_normalization = parent::getExpectedNormalizedEntity();
+
+    $normalization = $this->applyHalFieldNormalization($default_normalization);
+
+    $url = file_create_url($this->entity->getFileUri());
+    $normalization['uri'][0]['value'] = $url;
+    $uid = $this->author->id();
+
+    return $normalization + [
+      '_embedded' => [
+        $this->baseUrl . '/rest/relation/file/file/uid' => [
+          [
+            '_links' => [
+              'self' => [
+                'href' => $this->baseUrl . "/user/$uid?_format=hal_json",
+              ],
+              'type' => [
+                'href' => $this->baseUrl . '/rest/type/user/user',
+              ],
+            ],
+            'uuid' => [
+              [
+                'value' => $this->author->uuid(),
+              ],
+            ],
+          ],
+        ],
+      ],
+      '_links' => [
+        'self' => [
+          'href' => $url,
+        ],
+        'type' => [
+          'href' => $this->baseUrl . '/rest/type/file/file',
+        ],
+        $this->baseUrl . '/rest/relation/file/file/uid' => [
+          [
+            'href' => $this->baseUrl . "/user/$uid?_format=hal_json",
+          ],
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getNormalizedPostEntity() {
+    return parent::getNormalizedPostEntity() + [
+      '_links' => [
+        'type' => [
+          'href' => $this->baseUrl . '/rest/type/file/file',
+        ],
+      ],
+      'uri' => [
+        [
+          'value' => file_create_url($this->entity->getFileUri()),
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedCacheContexts() {
+    return [
+      'url.site',
+      'user.permissions',
+    ];
+  }
+
+}
diff --git a/core/modules/hal/tests/src/Functional/EntityResource/File/FileHalJsonBasicAuthTest.php b/core/modules/hal/tests/src/Functional/EntityResource/File/FileHalJsonBasicAuthTest.php
new file mode 100644
index 0000000..3b1e3fe
--- /dev/null
+++ b/core/modules/hal/tests/src/Functional/EntityResource/File/FileHalJsonBasicAuthTest.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Tests\hal\Functional\EntityResource\File;
+
+use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
+
+/**
+ * @group hal
+ */
+class FileHalJsonBasicAuthTest extends FileHalJsonAnonTest {
+
+  use BasicAuthResourceTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['basic_auth'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $auth = 'basic_auth';
+
+}
diff --git a/core/modules/hal/tests/src/Functional/EntityResource/File/FileHalJsonCookieTest.php b/core/modules/hal/tests/src/Functional/EntityResource/File/FileHalJsonCookieTest.php
new file mode 100644
index 0000000..8d62372
--- /dev/null
+++ b/core/modules/hal/tests/src/Functional/EntityResource/File/FileHalJsonCookieTest.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\Tests\hal\Functional\EntityResource\File;
+
+use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
+
+/**
+ * @group hal
+ */
+class FileHalJsonCookieTest extends FileHalJsonAnonTest {
+
+  use CookieResourceTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $auth = 'cookie';
+
+}
diff --git a/core/modules/image/tests/src/Kernel/ImageItemTest.php b/core/modules/image/tests/src/Kernel/ImageItemTest.php
index f7a1305..497fa17 100644
--- a/core/modules/image/tests/src/Kernel/ImageItemTest.php
+++ b/core/modules/image/tests/src/Kernel/ImageItemTest.php
@@ -10,6 +10,7 @@
 use Drupal\Tests\field\Kernel\FieldKernelTestBase;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\file\Entity\File;
+use Drupal\user\Entity\Role;
 
 /**
  * Tests using entity fields of the image field type.
@@ -40,6 +41,14 @@ class ImageItemTest extends FieldKernelTestBase {
   protected function setUp() {
     parent::setUp();
 
+    $this->installEntitySchema('user');
+    $this->installConfig(['user']);
+    // Give anonymous users permission to access content, so that we can view
+    // and download public file.
+    $anonymous_role = Role::load(Role::ANONYMOUS_ID);
+    $anonymous_role->grantPermission('access content');
+    $anonymous_role->save();
+
     $this->installEntitySchema('file');
     $this->installSchema('file', ['file_usage']);
 
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/File/FileJsonAnonTest.php b/core/modules/rest/tests/src/Functional/EntityResource/File/FileJsonAnonTest.php
new file mode 100644
index 0000000..b3adf6e
--- /dev/null
+++ b/core/modules/rest/tests/src/Functional/EntityResource/File/FileJsonAnonTest.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Tests\rest\Functional\EntityResource\File;
+
+use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
+
+/**
+ * @group rest
+ */
+class FileJsonAnonTest extends FileResourceTestBase {
+
+  use AnonResourceTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $format = 'json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $mimeType = 'application/json';
+
+}
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/File/FileJsonBasicAuthTest.php b/core/modules/rest/tests/src/Functional/EntityResource/File/FileJsonBasicAuthTest.php
new file mode 100644
index 0000000..971555c
--- /dev/null
+++ b/core/modules/rest/tests/src/Functional/EntityResource/File/FileJsonBasicAuthTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\Tests\rest\Functional\EntityResource\File;
+
+use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
+
+/**
+ * @group rest
+ */
+class FileJsonBasicAuthTest extends FileResourceTestBase {
+
+  use BasicAuthResourceTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['basic_auth'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $format = 'json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $mimeType = 'application/json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $auth = 'basic_auth';
+
+}
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/File/FileJsonCookieTest.php b/core/modules/rest/tests/src/Functional/EntityResource/File/FileJsonCookieTest.php
new file mode 100644
index 0000000..cffd869
--- /dev/null
+++ b/core/modules/rest/tests/src/Functional/EntityResource/File/FileJsonCookieTest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\Tests\rest\Functional\EntityResource\File;
+
+use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
+
+/**
+ * @group rest
+ */
+class FileJsonCookieTest extends FileResourceTestBase {
+
+  use CookieResourceTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $format = 'json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $mimeType = 'application/json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $auth = 'cookie';
+
+}
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/File/FileResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/File/FileResourceTestBase.php
new file mode 100644
index 0000000..ef97646
--- /dev/null
+++ b/core/modules/rest/tests/src/Functional/EntityResource/File/FileResourceTestBase.php
@@ -0,0 +1,193 @@
+<?php
+
+namespace Drupal\Tests\rest\Functional\EntityResource\File;
+
+use Drupal\file\Entity\File;
+use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
+use Drupal\user\Entity\User;
+
+abstract class FileResourceTestBase extends EntityResourceTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['file', 'node', 'user'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $entityTypeId = 'file';
+
+  /**
+   * @var \Drupal\file\FileInterface
+   */
+  protected $entity;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $patchProtectedFieldNames = [
+    'status',
+    'changed',
+  ];
+
+  /**
+   * @var \Drupal\user\UserInterface
+   */
+  protected $author;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpAuthorization($method) {
+    switch ($method) {
+      case 'GET':
+        $this->grantPermissionsToTestedRole(['access content']);
+        break;
+
+      case 'POST':
+      case 'PATCH':
+      case 'DELETE':
+        $this->grantPermissionsToTestedRole(['access content', 'administer files']);
+        break;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function createEntity() {
+    $this->author = User::load(1);
+
+    $file = File::create();
+    $file->setOwnerId($this->author->id());
+    $file->setFilename('drupal.txt');
+    $file->setMimeType('text/plain');
+    $file->setFileUri('public://drupal.txt');
+    $file->set('status', FILE_STATUS_PERMANENT);
+    $file->save();
+
+    file_put_contents($file->getFileUri(), 'Drupal');
+
+    return $file;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedNormalizedEntity() {
+    return [
+      'changed' => [
+        [
+          'value' => $this->entity->getChangedTime(),
+        ],
+      ],
+      'created' => [
+        [
+          'value' => (int) $this->entity->getCreatedTime(),
+        ],
+      ],
+      'fid' => [
+        [
+          'value' => 1,
+        ],
+      ],
+      'filemime' => [
+        [
+          'value' => 'text/plain',
+        ],
+      ],
+      'filename' => [
+        [
+          'value' => 'drupal.txt',
+        ],
+      ],
+      'filesize' => [
+        [
+          'value' => (int) $this->entity->getSize(),
+        ],
+      ],
+      'langcode' => [
+        [
+          'value' => 'en',
+        ],
+      ],
+      'status' => [
+        [
+          'value' => TRUE,
+        ],
+      ],
+      'uid' => [
+        [
+          'target_id' => (int) $this->author->id(),
+          'target_type' => 'user',
+          'target_uuid' => $this->author->uuid(),
+          'url' => base_path() . 'user/' . $this->author->id(),
+        ],
+      ],
+      'uri' => [
+        [
+          'value' => 'public://drupal.txt',
+        ],
+      ],
+      'uuid' => [
+        [
+          'value' => $this->entity->uuid(),
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getNormalizedPostEntity() {
+    return [
+      'uid' => [
+        [
+          'target_id' => $this->author->id(),
+        ],
+      ],
+      'filename' => [
+        [
+          'value' => 'drupal.txt',
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedCacheContexts() {
+    return [
+      'user.permissions',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function testPost() {
+    // @todo https://www.drupal.org/node/1927648
+    $this->markTestSkipped();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedUnauthorizedAccessMessage($method) {
+    if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
+      return parent::getExpectedUnauthorizedAccessMessage($method);
+    }
+
+    if ($method === 'GET') {
+      return "The following permissions are required: 'access content' OR 'administer files'.";
+    }
+    if ($method === 'PATCH') {
+      return 'You are not authorized to update this file entity.';
+    }
+    return parent::getExpectedUnauthorizedAccessMessage($method);
+  }
+
+}
diff --git a/core/modules/text/src/Tests/TextFieldTest.php b/core/modules/text/src/Tests/TextFieldTest.php
index d061c00..af0b73e 100644
--- a/core/modules/text/src/Tests/TextFieldTest.php
+++ b/core/modules/text/src/Tests/TextFieldTest.php
@@ -17,6 +17,11 @@
 class TextFieldTest extends StringFieldTest {
 
   /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['node'];
+
+  /**
    * A user with relevant administrative privileges.
    *
    * @var \Drupal\user\UserInterface
@@ -26,7 +31,7 @@ class TextFieldTest extends StringFieldTest {
   protected function setUp() {
     parent::setUp();
 
-    $this->adminUser = $this->drupalCreateUser(['administer filters']);
+    $this->adminUser = $this->drupalCreateUser(['administer filters', 'access content']);
   }
 
   // Test fields.
