.../Plugin/rest/resource/FileUploadResource.php | 2 +- .../EntityResource/EntityResourceTestBase.php | 8 +- .../EntityResource/Media/MediaResourceTestBase.php | 145 ++++++++++++++++++++- 3 files changed, 151 insertions(+), 4 deletions(-) diff --git a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php index 46e1d04..a7b430a 100644 --- a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php +++ b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php @@ -400,7 +400,7 @@ protected function validateAndLoadFieldDefinition($entity_type_id, $bundle, $fie $entity_access_control_handler = $this->entityTypeManager->getAccessControlHandler($entity_type_id); $bundle = $this->entityTypeManager->getDefinition($entity_type_id)->hasKey('bundle') ? $bundle : NULL; - $access_result = $entity_access_control_handler->createAccess(NULL, NULL, [], TRUE) + $access_result = $entity_access_control_handler->createAccess($bundle, NULL, [], TRUE) ->andIf($entity_access_control_handler->fieldAccess('edit', $field_definition, NULL, NULL, TRUE)); if (!($entity_access_control_handler->createAccess($bundle) && $entity_access_control_handler->fieldAccess('edit', $field_definition))) { throw new AccessDeniedHttpException($access_result->getReason()); diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index 6778f41..e20feb8 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -824,7 +824,13 @@ public function testPost() { // DX: 403 when unauthorized. $response = $this->request('POST', $url, $request_options); - $this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('POST'), $response); + // @todo Remove this if-test in https://www.drupal.org/project/drupal/issues/2820364 + if (static::$entityTypeId === 'media' && !static::$auth) { + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nname: Name: this field cannot hold more than 1 values.\nfield_media_file.0: You do not have access to the referenced entity (file: 3).\n", $response); + } + else { + $this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('POST'), $response); + } $this->setUpAuthorization('POST'); diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php index 414b3eb..8ed41ee 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php @@ -2,12 +2,19 @@ namespace Drupal\Tests\rest\Functional\EntityResource\Media; +use Drupal\Component\Serialization\Json; +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Url; use Drupal\file\Entity\File; use Drupal\media\Entity\Media; use Drupal\media\Entity\MediaType; +use Drupal\rest\RestResourceConfigInterface; use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait; use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase; +use Drupal\user\Entity\Role; use Drupal\user\Entity\User; +use Drupal\user\RoleInterface; +use GuzzleHttp\RequestOptions; abstract class MediaResourceTestBase extends EntityResourceTestBase { @@ -45,7 +52,7 @@ protected function setUpAuthorization($method) { break; case 'POST': - $this->grantPermissionsToTestedRole(['create camelids media']); + $this->grantPermissionsToTestedRole(['create camelids media', 'access content']); break; case 'PATCH': @@ -235,12 +242,26 @@ protected function getNormalizedPostEntity() { 'value' => 'Dramallama', ], ], + 'field_media_file' => [ + [ + 'description' => NULL, + 'display' => NULL, + 'target_id' => 3, + ], + ], ]; } /** * {@inheritdoc} */ + protected function getNormalizedPatchEntity() { + return array_diff_key($this->getNormalizedPostEntity(), ['field_media_file' => TRUE]); + } + + /** + * {@inheritdoc} + */ protected function getExpectedUnauthorizedAccessMessage($method) { if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) { return parent::getExpectedUnauthorizedAccessMessage($method); @@ -250,6 +271,9 @@ protected function getExpectedUnauthorizedAccessMessage($method) { case 'GET'; return "The 'view media' permission is required and the media item must be published."; + case 'POST': + return "The following permissions are required: 'administer media' OR 'create media' OR 'create camelids media'."; + case 'PATCH': return 'You are not authorized to update this media entity of bundle camelids.'; @@ -265,7 +289,124 @@ protected function getExpectedUnauthorizedAccessMessage($method) { * {@inheritdoc} */ public function testPost() { - $this->markTestSkipped('POSTing File Media items is not supported until https://www.drupal.org/node/1927648 is solved.'); + $file_storage = $this->container->get('entity_type.manager')->getStorage('file'); + + // Step 1: upload file, results in File entity marked temporary. + $this->uploadFile(); + $file = $file_storage->loadUnchanged(3); + $this->assertTrue($file->isTemporary()); + $this->assertFalse($file->isPermanent()); + + // Step 1: create Media entity using the File, makes File entity permanent. + parent::testPost(); + $file = $file_storage->loadUnchanged(3); + $this->assertFalse($file->isTemporary()); + $this->assertTrue($file->isPermanent()); + } + + /** + * This duplicates some of the 'file_upload' REST resource plugin test + * coverage, to be able to test it on a concrete use case. + */ + protected function uploadFile() { + // Enable the 'file_upload' REST resource for the current format + auth. + $this->resourceConfigStorage->create([ + 'id' => 'file.upload', + 'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY, + 'configuration' => [ + 'methods' => ['POST'], + 'formats' => [static::$format], + 'authentication' => isset(static::$auth) ? [static::$auth] : [], + ], + 'status' => TRUE, + ])->save(); + $this->refreshTestStateAfterRestConfigChange(); + + // POST to create a File entity. + $url = Url::fromUri('base:file/upload/media/camelids/field_media_file'); + $url->setOption('query', ['_format' => static::$format]); + $request_options = []; + $request_options[RequestOptions::HEADERS] = [ + // Set the required (and only accepted) content type for the request. + 'Content-Type' => 'application/octet-stream', + // Set the required Content-Disposition header for the file name. + 'Content-Disposition' => 'file; filename="drupal rocks 🤘.txt"', + ]; + $request_options[RequestOptions::BODY] = 'Drupal is the best!'; + $request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('POST')); + $response = $this->request('POST', $url, $request_options); + $this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('POST'), $response); + + // Grant necessary permission, retry. + $this->grantPermissionsToTestedRole(['create camelids media']); + $response = $this->request('POST', $url, $request_options); + $this->assertSame(201, $response->getStatusCode()); + $file = File::load(3); + $owner = static::$auth ? $this->account : User::load(0); + $expected_file_entity_normalization = [ + 'fid' => [ + [ + 'value' => 3, + ], + ], + 'uuid' => [ + [ + 'value' => $file->uuid(), + ], + ], + 'langcode' => [ + [ + 'value' => 'en', + ], + ], + 'uid' => [ + [ + 'target_id' => (int) $owner->id(), + 'target_type' => 'user', + 'target_uuid' => $owner->uuid(), + 'url' => base_path() . 'user/' . $owner->id(), + ], + ], + 'filename' => [ + [ + 'value' => 'drupal rocks 🤘.txt', + ], + ], + 'uri' => [ + [ + 'value' => 'public://' . date('Y-m'). '/drupal rocks 🤘.txt', + 'url' => base_path() . $this->siteDirectory . '/files/' . date('Y-m') . '/drupal%20rocks%20%F0%9F%A4%98.txt', + ], + ], + 'filemime' => [ + [ + 'value' => 'text/plain', + ], + ], + 'filesize' => [ + [ + 'value' => 19, + ], + ], + 'status' => [ + [ + 'value' => FALSE, + ], + ], + 'created' => [ + $this->formatExpectedTimestampItemValues($file->getCreatedTime()), + ], + 'changed' => [ + $this->formatExpectedTimestampItemValues($file->getChangedTime()), + ], + ]; + $this->assertSame($expected_file_entity_normalization, Json::decode((string) $response->getBody())); + + // To still run the complete test coverage for POSTing a Media entity, we + // must revoke the additional permissions that we granted. + $role = Role::load(static::$auth ? RoleInterface::AUTHENTICATED_ID : RoleInterface::AUTHENTICATED_ID); + $role->revokePermission('create camelids media'); + $role->trustData()->save(); } /**