diff --git a/core/core.link_relation_types.yml b/core/core.link_relation_types.yml index b2a0955..d4ffce3 100644 --- a/core/core.link_relation_types.yml +++ b/core/core.link_relation_types.yml @@ -3,6 +3,9 @@ add-form: uri: https://drupal.org/link-relations/add-form description: A form where a resource of this type can be created. +add-page: + uri: https://drupal.org/link-relations/add-page + description: A page where a resource of this type and related types can be created. delete-form: uri: https://drupal.org/link-relations/delete-form description: A form where a resource of this type can be deleted. diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Media/MediaHalJsonAnonTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Media/MediaHalJsonAnonTest.php new file mode 100644 index 0000000..afa9465 --- /dev/null +++ b/core/modules/hal/tests/src/Functional/EntityResource/Media/MediaHalJsonAnonTest.php @@ -0,0 +1,176 @@ +applyHalFieldNormalization($default_normalization); + + $file = File::load(1); + $thumbnail = File::load(2); + $author = User::load($this->entity->getOwnerId()); + return $normalization + [ + '_links' => [ + 'self' => [ + 'href' => $this->baseUrl . '/media/1?_format=hal_json', + ], + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/media/camelids', + ], + $this->baseUrl . '/rest/relation/media/camelids/field_media_file_1' => [ + [ + 'href' => $file->url(), + 'lang' => 'en', + ], + ], + $this->baseUrl . '/rest/relation/media/camelids/revision_user' => [ + [ + 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', + ], + ], + $this->baseUrl . '/rest/relation/media/camelids/thumbnail' => [ + [ + 'href' => $thumbnail->url(), + 'lang' => 'en', + ], + ], + $this->baseUrl . '/rest/relation/media/camelids/uid' => [ + [ + 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', + 'lang' => 'en', + ], + ], + ], + '_embedded' => [ + $this->baseUrl . '/rest/relation/media/camelids/field_media_file_1' => [ + [ + '_links' => [ + 'self' => [ + 'href' => $file->url(), + ], + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/file/file', + ], + ], + 'lang' => 'en', + 'uri' => [ + [ + 'value' => $file->url(), + ], + ], + 'uuid' => [ + [ + 'value' => $file->uuid(), + ], + ], + ], + ], + $this->baseUrl . '/rest/relation/media/camelids/revision_user' => [ + [ + '_links' => [ + 'self' => [ + 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', + ], + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/user/user', + ], + ], + 'uuid' => [ + [ + 'value' => $author->uuid(), + ], + ], + ], + ], + $this->baseUrl . '/rest/relation/media/camelids/thumbnail' => [ + [ + '_links' => [ + 'self' => [ + 'href' => $thumbnail->url(), + ], + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/file/file', + ], + ], + 'lang' => 'en', + 'uri' => [ + [ + 'value' => $thumbnail->url(), + ], + ], + 'uuid' => [ + [ + 'value' => $thumbnail->uuid(), + ], + ], + ], + ], + $this->baseUrl . '/rest/relation/media/camelids/uid' => [ + [ + '_links' => [ + 'self' => [ + 'href' => $this->baseUrl . '/user/' . $author->id() . '?_format=hal_json', + ], + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/user/user', + ], + ], + 'uuid' => [ + [ + 'value' => $author->uuid(), + ], + ], + 'lang' => 'en', + ], + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNormalizedPostEntity() { + return parent::getNormalizedPostEntity() + [ + '_links' => [ + 'type' => [ + 'href' => $this->baseUrl . '/rest/type/media/camelids', + ], + ], + ]; + } + +} diff --git a/core/modules/hal/tests/src/Functional/EntityResource/Media/MediaHalJsonBasicAuthTest.php b/core/modules/hal/tests/src/Functional/EntityResource/Media/MediaHalJsonBasicAuthTest.php new file mode 100644 index 0000000..a645cbf --- /dev/null +++ b/core/modules/hal/tests/src/Functional/EntityResource/Media/MediaHalJsonBasicAuthTest.php @@ -0,0 +1,24 @@ +set('uid', $uid); + return $this->set('uid', $uid); } /** diff --git a/core/modules/media/src/MediaAccessControlHandler.php b/core/modules/media/src/MediaAccessControlHandler.php index f753e7f..434abfe 100644 --- a/core/modules/media/src/MediaAccessControlHandler.php +++ b/core/modules/media/src/MediaAccessControlHandler.php @@ -23,9 +23,13 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter $is_owner = ($account->id() && $account->id() === $entity->getOwnerId()); switch ($operation) { case 'view': - return AccessResult::allowedIf($account->hasPermission('view media') && $entity->isPublished()) + $access_result = AccessResult::allowedIf($account->hasPermission('view media') && $entity->isPublished()) ->cachePerPermissions() ->addCacheableDependency($entity); + if (!$access_result->isAllowed()) { + $access_result->setReason("The 'view media' permission is required and the media item must be published."); + } + return $access_result; case 'update': if ($account->hasPermission('update any media')) { diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaJsonAnonTest.php b/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaJsonAnonTest.php new file mode 100644 index 0000000..d9a3550 --- /dev/null +++ b/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaJsonAnonTest.php @@ -0,0 +1,24 @@ +grantPermissionsToTestedRole(['view media']); + break; + + case 'POST': + $this->grantPermissionsToTestedRole(['create media']); + break; + + case 'PATCH': + $this->grantPermissionsToTestedRole(['update any media']); + break; + + case 'DELETE': + $this->grantPermissionsToTestedRole(['delete any media']); + break; + } + } + + /** + * {@inheritdoc} + */ + protected function createEntity() { + if (!MediaType::load('camelids')) { + // Create a "Camelids" media type. + $media_type = MediaType::create([ + 'name' => 'Camelids', + 'id' => 'camelids', + 'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.', + 'source' => 'file', + ]); + $media_type->save(); + // Create the source field. + $source_field = $media_type->getSource()->createSourceField($media_type); + $source_field->getFieldStorageDefinition()->save(); + $source_field->save(); + $media_type + ->set('source_configuration', [ + 'source_field' => $source_field->getName(), + ]) + ->save(); + } + + // Create a file to upload. + $file = File::create([ + 'uri' => 'public://llama.txt', + ]); + $file->setPermanent(); + $file->save(); + + // Create a "Llama" media item. + $media = Media::create([ + 'bundle' => 'camelids', + 'field_media_file_1' => [ + 'target_id' => $file->id(), + ], + // @todo use setName() when https://www.drupal.org/node/2897009 lands. + 'name' => 'Llama', + ]); + $media->setOwnerId(static::$auth ? $this->account->id() : 0) + ->setPublished(TRUE) + ->setCreatedTime(123456789) + ->setRevisionUserId(static::$auth ? $this->account->id() : 0) + ->save(); + + return $media; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedNormalizedEntity() { + $file = File::load(1); + $thumbnail = File::load(2); + $author = User::load($this->entity->getOwnerId()); + return [ + 'mid' => [ + [ + 'value' => 1, + ], + ], + 'uuid' => [ + [ + 'value' => $this->entity->uuid(), + ], + ], + 'vid' => [ + [ + 'value' => 1, + ], + ], + 'langcode' => [ + [ + 'value' => 'en', + ], + ], + 'bundle' => [ + [ + 'target_id' => 'camelids', + 'target_type' => 'media_type', + 'target_uuid' => MediaType::load('camelids')->uuid(), + ], + ], + 'name' => [ + [ + 'value' => 'Llama', + ], + ], + 'field_media_file_1' => [ + [ + 'description' => NULL, + 'display' => NULL, + 'target_id' => (int) $file->id(), + 'target_type' => 'file', + 'target_uuid' => $file->uuid(), + 'url' => $file->url(), + ], + ], + 'thumbnail' => [ + [ + 'alt' => 'Thumbnail', + 'width' => 180, + 'height' => 180, + 'target_id' => (int) $thumbnail->id(), + 'target_type' => 'file', + 'target_uuid' => $thumbnail->uuid(), + 'title' => 'Llama', + 'url' => $thumbnail->url(), + ], + ], + 'status' => [ + [ + 'value' => TRUE, + ], + ], + 'created' => [ + $this->formatExpectedTimestampItemValues(123456789), + ], + 'changed' => [ + $this->formatExpectedTimestampItemValues($this->entity->getChangedTime()), + ], + 'revision_created' => [ + $this->formatExpectedTimestampItemValues((int) $this->entity->getRevisionCreationTime()), + ], + 'default_langcode' => [ + [ + 'value' => TRUE, + ], + ], + 'uid' => [ + [ + 'target_id' => (int) $author->id(), + 'target_type' => 'user', + 'target_uuid' => $author->uuid(), + 'url' => base_path() . 'user/' . $author->id(), + ], + ], + 'revision_user' => [ + [ + 'target_id' => (int) $author->id(), + 'target_type' => 'user', + 'target_uuid' => $author->uuid(), + 'url' => base_path() . 'user/' . $author->id(), + ], + ], + 'revision_log_message' => [], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNormalizedPostEntity() { + return [ + 'bundle' => [ + [ + 'target_id' => 'camelids', + ], + ], + 'name' => [ + [ + 'value' => 'Dramallama', + ], + ], + ]; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedUnauthorizedAccessMessage($method) { + if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) { + return parent::getExpectedUnauthorizedAccessMessage($method); + } + + switch ($method) { + case 'GET'; + return "The 'view media' permission is required and the media item must be published."; + + case 'PATCH': + return 'You are not authorized to update this media entity of bundle camelids.'; + + case 'DELETE': + return 'You are not authorized to delete this media entity of bundle camelids.'; + + default: + return parent::getExpectedUnauthorizedAccessMessage($method); + } + } + + /** + * {@inheritdoc} + */ + public function testPost() { + $this->markTestSkipped('POSTing File Media items is not supported until https://www.drupal.org/node/1927648 is solved.'); + } + +} diff --git a/core/modules/rest/tests/src/Functional/EntityResource/MediaType/MediaTypeJsonAnonTest.php b/core/modules/rest/tests/src/Functional/EntityResource/MediaType/MediaTypeJsonAnonTest.php new file mode 100644 index 0000000..2e4b630 --- /dev/null +++ b/core/modules/rest/tests/src/Functional/EntityResource/MediaType/MediaTypeJsonAnonTest.php @@ -0,0 +1,24 @@ +grantPermissionsToTestedRole(['administer media types']); + } + + /** + * {@inheritdoc} + */ + protected function createEntity() { + // Create a "Camelids" media type. + $camelids = MediaType::create([ + 'name' => 'Camelids', + 'id' => 'camelids', + 'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.', + 'source' => 'file', + ]); + + $camelids->save(); + + return $camelids; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedNormalizedEntity() { + return [ + 'dependencies' => [], + 'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.', + 'field_map' => [], + 'id' => 'camelids', + 'label' => NULL, + 'langcode' => 'en', + 'new_revision' => FALSE, + 'queue_thumbnail_downloads' => FALSE, + 'source' => 'file', + 'source_configuration' => [ + 'source_field' => '', + ], + 'status' => TRUE, + 'uuid' => $this->entity->uuid(), + ]; + } + + /** + * {@inheritdoc} + */ + protected function getNormalizedPostEntity() { + // @todo Update in https://www.drupal.org/node/2300677. + } + +}