Problem/Motivation

When you upload a media item it is by default available at /media/{id} eg. media/1. Any user with "view media" will be able to access it. People can enumerate URLs to discover yet to be published content. For example, if you have content_moderation enabled and you upload media to a draft content entity the media will be automatically published to /media/{id}. If the content is a timed press release this could cause a lot of problems.

Regardless of the access issue, I think it is surprising that uploading media to a content entity makes the media available at another URL. This does not feel like the 80% use-case and it makes the access problems described by #2904842: Make private file access handling respect the full entity reference chain worse.

Proposed resolution

  • Make the edit-form the canonical route (and revision) route for media entities. (This is the same as BlockContent entities)
  • To retain BC: Add a tick box to media settings to restore removed routes

Remaining tasks

None.

User interface changes

  • /media/{id} is not available to users with the permission "view media" by default.
  • A new option on /admin/config/media/media-settings to ensure access to media items at /media/{id}

API changes

The default canonical route for media items is the edit-form and access to it is the same as edit access to media unless the media.settings:security.standalone_url is set to true

Data model changes

None

Release notes snippet

Media items are no longer available at /media/{id} by default. This can be enabled on /admin/config/media/media-settings.

Comments

alexpott created an issue. See original summary.

chr.fritsch’s picture

Status: Active » Needs review
StatusFileSize
new7.2 KB

Here is a first patch. There are a couple of things that should be adjusted:

* We need an update path to set the checkbox for existing sites.
* Changing the setting, doesn't clear the entity type cache. Not sure about this.
* Some documentation in the settings form why it is risky to expose the canonical URL.

Finally, let's see how much tests are failing.

chr.fritsch’s picture

wim leers’s picture

Priority: Normal » Major
Issue summary: View changes
Issue tags: +Security improvements
  1. +++ b/core/modules/media/config/schema/media.schema.yml
    @@ -11,6 +11,9 @@ media.settings:
    +    expose_canonical:
    

    Let's prefix this with bc_, just like we did in #2751325: All serialized values are strings, should be integers/booleans when appropriate.

    @chr.fritsch pointed out that this is not actually solely for BC, some sites may have an explicit need for this.

  2. +++ b/core/modules/media/media.links.task.yml
    @@ -1,18 +1,6 @@
    -entity.media.edit_form:
    -  title: Edit
    -  route_name: entity.media.edit_form
    -  base_route: entity.media.canonical
    -
    -entity.media.delete_form:
    
    +++ b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
    @@ -0,0 +1,79 @@
    +    $this->derivatives["entity.media.edit_form"] = [
    +      'route_name' => "entity.media.edit_form",
    +      'title' => $this->t('Edit'),
    +      'base_route' => $parent,
    +    ] + $base_plugin_definition;
    +
    +    $this->derivatives["entity.media.delete_form"] = [
    

    Why did we move these?

  3. +++ b/core/modules/media/media.links.task.yml
    @@ -1,18 +1,6 @@
    -  weight: 10
    
    +++ b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
    @@ -0,0 +1,79 @@
    +      'route_name' => "entity.media.delete_form",
    +      'title' => $this->t('Delete'),
    +      'base_route' => $parent,
    +    ] + $base_plugin_definition;
    

    We lost the weight here.

  4. +++ b/core/modules/media/media.module
    @@ -347,3 +347,16 @@ function _media_get_add_url($allowed_bundles) {
    +  if (\Drupal::config('media.settings')->get('expose_canonical')) {
    

    This means that a config change needs to invalidate entity type definitions, i.e. the entity_types cache tag.

    It also means routes need to be rebuilt, so you'll want to invalidate the routes cache tag too AFAICT.

    See \Drupal\rest\EventSubscriber\RestConfigSubscriber and \Drupal\system\EventSubscriber\ConfigCacheTag::onSave() an example.

  5. +++ b/core/modules/media/src/Entity/Media.php
    @@ -531,4 +529,19 @@ public static function getRequestTime() {
    +    if (in_array($rel, ['canonical', 'revision']) && !$this->hasLinkTemplate($rel)) {
    

    Let's put in an @see to the configuration here, and explain why we even need this.

  6. +++ b/core/modules/media/src/Form/MediaSettingsForm.php
    @@ -91,6 +91,12 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +      '#title' => $this->t('Expose canonical media URL'),
    +      '#default_value' => $this->config('media.settings')->get('expose_canonical'),
    

    I suggest s/expose_canonical/expose_canonical_routes/ and "Make media items accessible via their own pages"

  7. +++ b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
    @@ -0,0 +1,79 @@
    +   * Creates an DynamicLocalTasks object.
    

    Übernit: s/an/a/

wim leers’s picture

+++ b/core/modules/media/src/Form/MediaSettingsForm.php
@@ -91,6 +91,12 @@ public function buildForm(array $form, FormStateInterface $form_state) {
+      '#description' => $this->t('We NEED a description'),

Suggestion: By default, published !media-entities are only accessible to end users if they're shown (referenced) by other entities. On most sites, access to individual !media-entities (via guessable URLs) is unexpected. If it makes sense for your site to have all !media-entities be individually accessible, turn this setting on.

(For !media-entities, use label_plural from the entity type definition.)

That's quite verbose and perhaps a bit scary, but I think that's warranted in this case.

Status: Needs review » Needs work

The last submitted patch, 2: 3017935-2.patch, failed testing. View results

chr.fritsch’s picture

Status: Needs work » Needs review
StatusFileSize
new12.44 KB
new9.62 KB

Regarding #4

2. Because we have to set a different parent
3. Fixed
4. We have still have the problem that media_entity_type_alter is called after DefaultHtmlRouteProvider::getRoutes()
5. Fixed
6. Fixed
7. Fixed

Also added the suggestion from #5

Status: Needs review » Needs work

The last submitted patch, 7: 3017935-7.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

wim leers’s picture

  1. +++ b/core/modules/media/media.links.task.yml
    @@ -1,18 +1,6 @@
    +  weight: 100
    

    Why this weight? Let's document the reason.

  2. +++ b/core/modules/media/media.services.yml
    @@ -19,3 +19,8 @@ services:
    +    tags:
    +    - { name: event_subscriber }
    

    Übernit: the last line needs an extra 2 spaces of leading indentation.

  3. +++ b/core/modules/media/src/Entity/Media.php
    @@ -531,4 +529,22 @@ public static function getRequestTime() {
    +    // Canonical and revision URL's should always point to the edit form or
    

    Nit: s/URL's/URLs/

  4. +++ b/core/modules/media/src/Entity/Media.php
    @@ -531,4 +529,22 @@ public static function getRequestTime() {
    +      if ($this->access('edit')) {
    +        $rel = 'edit-form';
    +      }
    +      else {
    +        $rel = 'collection';
    +      }
    

    🤔 Hm, this could be tricky. This means that the URL that is returned depends on the user (which permissions do they have?) and the configuration (which permissions does each role have?).

    I'd say that we should merge the cacheability of the access result with the resulting Url object, but … the \Drupal\Core\Url object does not carry cacheability.

    \Drupal\Core\GeneratedUrl does … but that's not what this method returns.

    Do we really need to conditionally return different routes?

  5. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -0,0 +1,68 @@
    +   * The router builder.
    ...
    +  protected $routerBuilder;
    ...
    +   *   The router builder service.
    ...
    +   * Informs the router builder a rebuild is needed when necessary.
    

    s/router/route/

    (And yes, \Drupal\rest\EventSubscriber\RestConfigSubscriber got this wrong too 😅)

  6. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -0,0 +1,68 @@
    +   * Constructs the RestConfigSubscriber.
    

    C/P leftover :)

  7. +++ b/core/modules/media/src/Form/MediaSettingsForm.php
    @@ -22,6 +23,13 @@ class MediaSettingsForm extends ConfigFormBase {
    +   * The entity manager service.
    
    @@ -29,10 +37,13 @@ class MediaSettingsForm extends ConfigFormBase {
    +   *   The entity manager service.
    

    Nit: entity type manager.

chr.fritsch’s picture

StatusFileSize
new13.51 KB
new6.05 KB

This patch fixes the caching issue we had when changing the setting.

It also adds a super-simple update path to set the setting for existing sites.

I will now start to address #9

wim leers’s picture

  1. +++ b/core/modules/media/media.post_update.php
    @@ -18,3 +18,13 @@ function media_post_update_collection_route() {
    + * Enable canonical routes for the Media entity type.
    

    Keeps canonical routes for Media entities enabled on existing sites.

  2. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -27,6 +28,13 @@ class MediaConfigSubscriber implements EventSubscriberInterface {
    +   * The entity manager service.
    
    @@ -34,10 +42,13 @@ class MediaConfigSubscriber implements EventSubscriberInterface {
    +   *   The entity manager service.
    

    entity type manager

  3. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -50,9 +61,9 @@ public function __construct(RouteBuilderInterface $router_builder, CacheTagsInva
    +      $this->cacheTagsInvalidator->invalidateTags(['rendered']);
    

    ✅ Needs a comment. Added that. Explanation is similar to #2241275: DateFormat cache tag: don't set the cache tag, but invalidate the entire render cache?.

  4. +++ b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
    @@ -56,6 +56,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
    +        'weight' => 1,
    
    @@ -65,6 +66,7 @@ public function getDerivativeDefinitions($base_plugin_definition) {
    +      'weight' => 2,
    

    These would guarantee the correct order, but why was this not necessary before, but is now?

chr.fritsch’s picture

StatusFileSize
new15.23 KB
new5.22 KB

This fixes #9 and #11

wim leers’s picture

All HAL REST GET tests are failing (Testing Drupal\Tests\media\Functional\Hal\MediaHalJsonAnonTest and friends), even after adding something like

  public function testGet() {
    \Drupal::configFactory()
      ->getEditable('media.settings')
      ->set('expose_canonical_routes', TRUE)
      ->save(TRUE);

    $this->rebuildAll();
    \Drupal::service('router.builder')->rebuild();
    $this->resourceConfigStorage->resetCache();

    return parent::testGet();
  }

It's then failing like this:

1) Drupal\Tests\media\Functional\Hal\MediaHalJsonAnonTest::testGet
Drupal\Core\Config\ConfigDuplicateUUIDException: Attempt to save a configuration entity 'entity.media' with UUID '3d1b92f9-010b-425f-8f04-44b5a30e9ad4' when this entity already exists with UUID 'e7ffc9c7-1e9c-4e0d-b227-2f8dd6b16e28'

That's triggered by this line:

$this->resourceConfigStorage->load(static::$resourceConfigId)->disable()->save();

Manually stepping through this confirms this seemingly impossible thing, that loading something by ID succeeds and simply calling disable()->save() somehow causes the loaded config entity to have a UUID that differs from the original config entity. Calling $this->resourceConfigStorage->resetCache([static::$resourceConfigId]); doesn't help.

If this is not scary enough, what's worse is that this happens only for HAL tests. Why? How? I have no clue. I already spent about 45 minutes debugging this. I'm moving on to something else.

chr.fritsch’s picture

Status: Needs work » Needs review
StatusFileSize
new21.13 KB
new9.43 KB

I changed the implementation to stay with the canonical link template. Now we are setting the canonical URL to the edit URL.

Let's see how many tests will fail now.

Status: Needs review » Needs work

The last submitted patch, 14: 3017935-14.patch, failed testing. View results

chr.fritsch’s picture

Status: Needs work » Needs review
StatusFileSize
new22.02 KB
new3.41 KB

So let's start fixing tests.

BTW: I don't think that the related issue is affecting us, because our route has no optional arguments.

Status: Needs review » Needs work

The last submitted patch, 17: 3017935-17.patch, failed testing. View results

wim leers’s picture

+++ b/core/modules/media/tests/src/Functional/Rest/MediaResourceTestBase.php
@@ -41,6 +41,24 @@
+    $this->serializer = $this->container->get('serializer');
+    $this->resourceConfigStorage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');

🎉😭🙈We spent so much time debugging this! Great find :)

chr.fritsch’s picture

Status: Needs work » Needs review
StatusFileSize
new26.23 KB
new4.76 KB

This should fix the remaining test fails

berdir’s picture

> We spent so much time debugging this! Great find :)

That's exactly why I think that using $this->container and properties with services is a bad idea in tests, should either just use \Drupal::() or alternatively methods. See #2066993: Use magic methods to sync container property to \Drupal::getContainer in functional tests.

tim.plunkett’s picture

+++ b/core/modules/media/src/Entity/Media.php
@@ -75,7 +75,7 @@
- *     "canonical" = "/media/{media}",
+ *     "canonical" = "/media/{media}/edit",

After reading the title, I came here to suggest this (the same as BlockContent) instead of actually removing it. +1!

alexpott’s picture

  1. +++ b/core/modules/media/config/schema/media.schema.yml
    @@ -11,6 +11,9 @@ media.settings:
    +    expose_canonical_routes:
    +      type: boolean
    +      label: 'Make media items accessible via their own pages'
    
    +++ b/core/modules/media/src/Form/MediaSettingsForm.php
    @@ -91,6 +103,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +    $form['security']['expose_canonical_routes'] = [
    +      '#prefix' => '<hr>',
    +      '#type' => 'checkbox',
    +      '#title' => $this->t('Make media items accessible via their own pages'),
    +      '#default_value' => $this->config('media.settings')->get('expose_canonical_routes'),
    +      '#description' => $this->t("By default, published @media-entities are only accessible to end users if they're shown (referenced) by other entities. On most sites, access to individual @media-entities (via guessable URLs) is unexpected. If it makes sense for your site to have all @media-entities be individually accessible, turn this setting on.", ['@media-entities' => $this->entityTypeManager->getDefinition('media')->getPluralLabel()]),
    +    ];
    

    This setting name needs work. It's super tricky because you don't want to expose too much of the internals. But I think this setting should be about using the edit-form as the canonical route instead of the "view" (nice overloaded word in Drupal). The current description also has a lot of assumptions baked in. The concept of "end-users" might include editors who would have access to edit the media entity.

  2. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -0,0 +1,94 @@
    +  public function onSave(ConfigCrudEvent $event) {
    +    $saved_config = $event->getConfig();
    +    if ($saved_config->getName() === 'media.settings' && $event->isChanged('expose_canonical_routes')) {
    +      $this->cacheTagsInvalidator->invalidateTags([
    +        // The configuration change triggers entity type definition changes,
    +        // which in turn triggers routes to appear or disappear.
    +        // @see media_entity_type_alter()
    +        'entity_types',
    +        // The 'rendered' cache tag needs to be explicitly invalidated to ensure
    +        // that all links to Media entities are re-rendered. Ideally, this would
    +        // not be necessary; invalidating the 'entity_types' cache tag should be
    +        // sufficient. But that cache tag would then need to be on nearly
    +        // everything, resulting in excessive complexity. We prefer pragmatism.
    +        'rendered',
    +      ]);
    +      // @todo Remove this when invalidating the 'entity_types' cache tag is
    +      // respected by the entity type plugin manager. See
    +      // https://www.drupal.org/project/drupal/issues/3001284 and
    +      // https://www.drupal.org/project/drupal/issues/3013659.
    +      $this->entityTypeManager->clearCachedDefinitions();
    +      $this->routerBuilder->setRebuildNeeded();
    +
    +    }
    +  }
    
    +++ b/core/modules/media/tests/src/Functional/FieldFormatter/OEmbedFormatterTest.php
    @@ -33,6 +33,13 @@ class OEmbedFormatterTest extends MediaFunctionalTestBase {
    +    \Drupal::configFactory()
    +      ->getEditable('media.settings')
    +      ->set('expose_canonical_routes', TRUE)
    +      ->save(TRUE);
    +
    +    $this->rebuildAll();
    

    The rebuildAll() makes me wonder if we've got this right. Does it work if we only force a route rebuild in tests?

  3. +++ b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
    @@ -0,0 +1,82 @@
    +    $this->derivatives["entity.media.canonical"] = [
    +      'route_name' => "entity.media.canonical",
    +      'title' => $this->t('Edit'),
    +      'base_route' => "entity.media.canonical",
    +      'weight' => 1,
    +    ] + $base_plugin_definition;
    +
    +    if (\Drupal::config('media.settings')->get('expose_canonical_routes')) {
    +      $this->derivatives["entity.media.canonical"]['title'] = $this->t('View');
    +
    +      $this->derivatives["entity.media.edit_form"] = [
    +        'route_name' => "entity.media.edit_form",
    +        'title' => $this->t('Edit'),
    +        'base_route' => 'entity.media.canonical',
    +        'weight' => 2,
    +      ] + $base_plugin_definition;
    +    }
    

    I think this is a slightly confusing construction. @todo...

  4. +++ b/core/modules/media/tests/src/Functional/Rest/MediaResourceTestBase.php
    @@ -41,6 +41,24 @@
    +    $this->rebuildAll();
    +    \Drupal::entityTypeManager()->clearCachedDefinitions();
    
    +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaDisplayTest.php
    @@ -30,6 +30,15 @@ protected function setUp() {
    +    $this->rebuildAll();
    +    \Drupal::entityTypeManager()->clearCachedDefinitions();
    
    +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceTestBase.php
    @@ -11,6 +11,23 @@
    +    $this->rebuildAll();
    +    \Drupal::entityTypeManager()->clearCachedDefinitions();
    

    Is the clearCachedDefinitions() needed after a rebuildAll()?

chr.fritsch’s picture

StatusFileSize
new26.86 KB
new14.14 KB

1. I talked to @alexpott and @Wim Leers and we decided to rename the setting to standalone_media_url
2. Nice catch, $this->container->get('router.builder')->rebuild(); is enough for us to work
3. Added a comment
4. Actually it's not needed.

phenaproxima’s picture

Status: Needs review » Needs work

Awesome work. I think this patch is an excellent fix for a pretty serious WTF in the media system. One request, though:

+++ b/core/modules/media/config/schema/media.schema.yml
@@ -11,6 +11,9 @@ media.settings:
+    standalone_media_url:
+      type: boolean
+      label: 'Standalone Media URL'

Having the word "media" in this setting name is redundant. Can we just call this standalone_url? And can the description be changed to "Whether media items can be accessed at standalone URLs"?

alexpott’s picture

  1. +++ b/core/modules/media/config/schema/media.schema.yml
    @@ -11,6 +11,9 @@ media.settings:
    +      label: 'Standalone Media URL'
    

    Allow media entities to be viewed at media/ID
    Let's explain it here.

  2. +++ b/core/modules/media/media.post_update.php
    @@ -18,3 +18,13 @@ function media_post_update_collection_route() {
    +function media_post_update_enable_canonical_routes() {
    

    This needs a rename. enable_standalone_url?

  3. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -0,0 +1,94 @@
    + * A subscriber triggering a route rebuild when certain configuration changes.
    

    I think this can be improved. Maybe
    Listens to the config save event for media.settings.

  4. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -0,0 +1,94 @@
    +   * Informs the router builder a rebuild is needed when necessary.
    

    Does more than this.

  5. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -0,0 +1,94 @@
    +   *   The Event to process.
    

    Either event or ConfigCrudEvent

  6. +++ b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
    @@ -0,0 +1,97 @@
    +    // The canonical has to be always there as a base task. Without a standalone
    +    // media URL this task is labeled 'Edit'.
    

    I think the second sentence is unnecessary.

  7. +++ b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
    @@ -0,0 +1,97 @@
    +    // With a standalone media URL the canonical task has to be re-labeled to
    +    // 'Title' and a dedicated edit task has to be added.
    

    Provide an edit_form task if standalone media URLs are enabled.

  8. +++ b/core/modules/media/src/Routing/MediaRouteProvider.php
    @@ -0,0 +1,25 @@
    +    if (\Drupal::config('media.settings')->get('standalone_media_url')) {
    

    The config factory can be injected. Override __construct and createInstance

  9. +++ b/core/modules/media/tests/src/Functional/FieldFormatter/OEmbedFormatterTest.php
    @@ -33,6 +33,13 @@ class OEmbedFormatterTest extends MediaFunctionalTestBase {
    +    $this->rebuildAll();
    
    +++ b/core/modules/media/tests/src/Functional/MediaAccessTest.php
    @@ -171,4 +161,48 @@ public function testMediaAccess() {
    +    $this->rebuildAll();
    

    And these... one

  10. +++ b/core/modules/media/tests/src/Functional/MediaCacheTagsTest.php
    @@ -24,6 +24,18 @@ class MediaCacheTagsTest extends EntityWithUriCacheTagsTestBase {
    +    $this->rebuildAll();
    
    +++ b/core/modules/media/tests/src/Functional/MediaContextualLinksTest.php
    @@ -22,6 +22,13 @@ class MediaContextualLinksTest extends MediaFunctionalTestBase {
    +    $this->rebuildAll();
    

    Can these be replaced with a router rebuild too?

chr.fritsch’s picture

Status: Needs work » Needs review
StatusFileSize
new27.76 KB
new13.66 KB

I addressed #25 and #26

alexpott’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/media/media.post_update.php
    @@ -18,3 +18,13 @@ function media_post_update_collection_route() {
    + * Keeps the standalone URL for Media entities enabled on existing sites.
    

    The standard for update functions is different since this is text used in the UI. See https://www.drupal.org/docs/develop/standards/api-documentation-and-comm... (that text needs to be updated for post updates) - so something like Keep media entities viewable at media/{id}. seems right.

  2. +++ b/core/modules/media/media.post_update.php
    @@ -18,3 +18,13 @@ function media_post_update_collection_route() {
    +function media_post_update_enable_standalone_url() {
    

    This needs a test.

  3. +++ b/core/modules/media/tests/src/Functional/Rest/MediaResourceTestBase.php
    @@ -41,6 +41,23 @@
    +    $this->serializer = $this->container->get('serializer');
    +    $this->resourceConfigStorage = $this->container->get('entity_type.manager')->getStorage('rest_resource_config');
    

    I wonder if this is necessary now?

chr.fritsch’s picture

Status: Needs work » Needs review
StatusFileSize
new29.13 KB
new2.73 KB

Addressing #28

1. Changed
2. Added an update test.
3. I removed it. Let the testbot answer that question :)

chr.fritsch’s picture

StatusFileSize
new38.96 KB
new9.83 KB

This patch needed some test changes after #2882473: Hide the media name basefield from the entity form by default landed.

alexpott’s picture

+++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
@@ -72,7 +72,7 @@ public function testMediaWithOnlyOneMediaType() {
-    $assert_session->titleEquals($media_name . ' | Drupal');
+    $assert_session->statusCodeEquals(404);

@@ -89,7 +89,7 @@ public function testMediaWithOnlyOneMediaType() {
-    $assert_session->titleEquals($media_name2 . ' | Drupal');
+    $assert_session->statusCodeEquals(404);

@@ -116,7 +116,7 @@ public function testMediaWithOnlyOneMediaType() {
-    $assert_session->titleEquals($media_name . ' | Drupal');
+    $assert_session->statusCodeEquals(404);

@@ -176,14 +176,11 @@ public function testMediaWithMultipleMediaTypes() {
     // Go to first media item.
     $this->drupalGet('media/' . $first_media_item->id());
-    $assert_session->statusCodeEquals(200);
-    $assert_session->pageTextContains($first_media_item->getName());
-    $assert_session->elementsCount('css', '.media--view-mode-full', 1);
+    $assert_session->statusCodeEquals(404);
 
     // Go to second media item.
     $this->drupalGet('media/' . $second_media_item->id());
-    $assert_session->statusCodeEquals(200);
-    $assert_session->pageTextContains($second_media_item->getName());
+    $assert_session->statusCodeEquals(404);

These changes are a little odd. Maybe we should remove the drupalGets? After all they are a little pointless now and we have other assertions checking that the media has been uploaded / changed etc...

chr.fritsch’s picture

StatusFileSize
new39.82 KB
new2.42 KB

Let's kick them.

alexpott’s picture

Issue tags: +Needs Caga tió

Discussion at the Barcelona sprint has resulted in the decision that this needs Caga tió.

chr.fritsch’s picture

👍🏻

wim leers’s picture

👍

seanb’s picture

#33 +1

phenaproxima’s picture

Agreed, +1!

chr.fritsch’s picture

Issue tags: -Needs Caga tió
StatusFileSize
new528.82 KB

Ok, here is the Caga Tio. Let's proceed  😁

alexpott’s picture

Issue summary: View changes
alexpott’s picture

Title: Make media entity canonical route optional » Media items should not be available at /media/{id} to users with 'view media' permission by default

Added a change record - https://www.drupal.org/node/3018742

And fixed the issue title.

alexpott’s picture

Issue summary: View changes
wim leers’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new9.41 KB
new14.33 KB

Only found nits (see below), and fixed them all. Let's get this done.

  1. +++ b/core/modules/media/config/schema/media.schema.yml
    @@ -11,6 +11,9 @@ media.settings:
    +      label: 'Allow media entities to be viewed at media/ID'
    

    Allow media items to be viewed standalone at /media/{id}.

  2. +++ b/core/modules/media/media.post_update.php
    @@ -18,3 +18,13 @@ function media_post_update_collection_route() {
    + * Keep media entities viewable at media/{id}.
    

    Nit: Missing leading slash.
    s/entities/items/

  3. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -0,0 +1,97 @@
    +   * The router builder.
    

    s/router/route/

  4. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -0,0 +1,97 @@
    +    $this->entityTypeManager = $entity_type_manager;
    +
    +  }
    

    Nit.

  5. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -0,0 +1,97 @@
    +   * This method is executed on all config save events.
    +   *
    +   * Informs the router builder a rebuild is needed and clears the entity types
    +   * cache when necessary.
    

    The first line is kinda pointless. The second is great. Let's make it fit on a single line.

  6. +++ b/core/modules/media/src/EventSubscriber/MediaConfigSubscriber.php
    @@ -0,0 +1,97 @@
    +      $this->routerBuilder->setRebuildNeeded();
    +
    +    }
    

    Nit.

  7. +++ b/core/modules/media/src/Form/MediaSettingsForm.php
    @@ -22,6 +23,13 @@ class MediaSettingsForm extends ConfigFormBase {
    +   * The entity type manager service.
    
    @@ -29,10 +37,13 @@ class MediaSettingsForm extends ConfigFormBase {
    +   *   The entity type manager service.
    

    s/service//

  8. +++ b/core/modules/media/src/Form/MediaSettingsForm.php
    @@ -91,6 +103,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    +      '#description' => $this->t("Allow users to access @media-entities on media/{id}.", ['@media-entities' => $this->entityTypeManager->getDefinition('media')->getPluralLabel()]),
    

    s/on/at/

    s/media//media/

  9. +++ b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
    @@ -0,0 +1,94 @@
    + * Generates media local tasks.
    

    s/media/media-related/

  10. +++ b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
    @@ -0,0 +1,94 @@
    +   * The entity manager service.
    ...
    +   *   The entity manager service.
    ...
    +   *   The config factory service.
    

    s/service//

  11. +++ b/core/modules/media/tests/src/Functional/Update/MediaUpdateTest.php
    @@ -80,4 +84,34 @@ public function testOwnerEntityKey() {
    +    $mediaType = $this->createMediaType('test');
    

    Should not use camelCase.

  12. … and many more nits, all fixed.
wim leers’s picture

StatusFileSize
new39.72 KB

Ugh, right interdiff, wrong patch.

The last submitted patch, 42: 3017935-11.patch, failed testing. View results

wim leers’s picture

CR reviewed; added some small clarifications.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed a6e4eaf and pushed to 8.7.x. Thanks!

diff --git a/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
index 9faa339640..70af3fcb8d 100644
--- a/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
+++ b/core/modules/media/src/Plugin/Derivative/DynamicLocalTasks.php
@@ -18,7 +18,7 @@ class DynamicLocalTasks extends DeriverBase implements ContainerDeriverInterface
   use StringTranslationTrait;
 
   /**
-   * The entity manager.
+   * The entity type manager.
    *
    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    */

Small fix on commit.

  • alexpott committed a6e4eaf on 8.7.x
    Issue #3017935 by chr.fritsch, Wim Leers, alexpott, phenaproxima: Media...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

berdir’s picture

+++ b/core/modules/media/media.post_update.php
@@ -18,3 +18,13 @@ function media_post_update_collection_route() {
+/**
+ * Keep media items viewable at /media/{id}.
+ */
+function media_post_update_enable_standalone_url() {
+  \Drupal::configFactory()
+    ->getEditable('media.settings')
+    ->set('standalone_url', TRUE)
+    ->save(TRUE);

Why was this done a as a post update, this is just a regular plain config save?

It being a post update makes it impossible for me to also set that to FALSE in existing installations of my installation profile.

IMHO, we should either change it to a regular update function or check it against NULL, so that it is only set if NULL. Then I can do my own update or post update function and the execution order doesn't matter, mine will win.

berdir’s picture

berdir’s picture

Note: This breaks linkit media integration as that no longer exposes a matcher plugin for media entities. Created an issue there #3040749: Drupal 8.7 no longer has a dedicated canonical route for media entities, breaks entity matcher deriver.

whikloj’s picture

Is it a longer term plan to remove all ability to access the fields and content of media outside of nodes? We have been building a semantic data repository on Drupal 8 and testing the move from 8.6.10 to 8.7.x had me worried. I see the canonical route for media entities can be re-enabled. But I am just wondering what the longer term plan for media entities is?

berdir’s picture

I don't think there are plans beyond this issue. Most sites don't need/want them to be exposed, but you can still chose to do so if that makes sense for you.

whikloj’s picture

Thanks for your quick response, I also noticed that the media REST endpoint seems to have been changed as well from the old media/{id} to media/{id}/edit. I don't think it is from this ticket. but I might have missed that change or is that a completely separate ticket?

berdir’s picture

That was most likely caused by this and probably not something that we expected to happen. Not seeing any test changes related to that, so I think our test coverage is a bit too generic to catch that :-/. I'd suggest you open an new issue (bug?) about that. A bit tricky now, because changing it again could be seen as yet another change.

wim leers’s picture

Wow, great catch, @whikloj! And yes, @Berdir is right, this is a consequence of our REST test coverage being generic. OTOH it's pretty exceptional to have foo/{…}/edit as a canonical route, so I'm not too concerned about the REST test coverage.

The fix should be easy, could you file that issue that @Berdir asked for, @whikloj? 🙏

whikloj’s picture

@Berdir @Wim Leers... cancel that. It was a PEBKAC error, I thought I had cleared my cache but apparently I had not.

**edit**
To clarify the REST route does change to media/{id}/edit due to this change, but if you check the checkbox in Media Settings it reverts to media/{id}.

chris matthews’s picture

It took me a minute to discover this checkbox was at /admin/config/media/media-settings as I didn't really think of it as a "security setting". I know this issue is fixed, but does anyone else think it might have a better home on the edit page for each media type (i.e. at /admin/structure/media/manage/audio > Publishing options) so there is more fine grained control per media type?

andrewmacpherson’s picture

Re. #58 - can you file a new issue to propose this? I think it's a good idea, though it'll need discussion. It sounds similar to what the Rabbit Hole contrib module does.

chris matthews’s picture

wim leers’s picture