diff --git a/core/modules/file/src/FileAccessControlHandler.php b/core/modules/file/src/FileAccessControlHandler.php index a82c460..a310c5f 100644 --- a/core/modules/file/src/FileAccessControlHandler.php +++ b/core/modules/file/src/FileAccessControlHandler.php @@ -22,8 +22,7 @@ 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(); + return AccessResult::allowedIfHasPermissions($account, ['access content', 'administer files'], 'OR'); } elseif ($references = $this->getFileReferences($entity)) { foreach ($references as $field_name => $entity_map) { @@ -48,8 +47,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..d2064fa 100644 --- a/core/modules/hal/src/Normalizer/FileEntityNormalizer.php +++ b/core/modules/hal/src/Normalizer/FileEntityNormalizer.php @@ -64,7 +64,14 @@ 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); + $entity = $this->entityManager->getStorage('file')->create($data); + + // Pass the names of the fields whose values can be merged. + // @todo https://www.drupal.org/node/2456257 remove this. + unset($data['_links'], $data['_embedded']); + $entity->_restSubmittedFields = array_keys($data); + + return $entity; } } 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 @@ +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 @@ +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 @@ +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.