Problem/Motivation

#2958554: Allow creation of file entities from binary data via JSON API requests allows to upload a file to file field on an entity. However, when I create a 'file' base field on an entity, it fails at the below line:

assert(is_null($entity) || $field_definition->getTargetEntityTypeId() === $entity->getEntityTypeId() && $field_definition->getTargetBundle() === $entity->bundle());

and here is my base field definition in an entity:

    $fields['user_document'] = BaseFieldDefinition::create('file')
      ->setLabel(t('Document'))
      ->setRequired(FALSE)
      ->setTranslatable(FALSE)
      ->setSetting('uri_scheme', 'private')
      ->setSetting('target_type', 'file')
      ->setCardinality(1)
      ->setDisplayOptions('form', [
        'label' => 'above',
        'type' => 'file_generic',
        'weight' => 15,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayOptions('view', [
        'label' => 'hidden',
        'type' => 'file_default',
        'weight' => 15,
      ])
      ->setDisplayConfigurable('view', TRUE);

Proposed resolution

Turns out the issue is with $field_definition->getTargetBundle() as there is no bundle associated. This is handled after for other access check operation, but not in/before `assert()`

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Comments

vijaycs85 created an issue. See original summary.

vijaycs85’s picture

Status: Active » Needs review
StatusFileSize
new1.25 KB
alexpott’s picture

StatusFileSize
new1.79 KB

This is happening because the file field is a base field. The docs for \Drupal\Core\Field\FieldDefinitionInterface::getTargetBundle() are clear.

   *   The bundle the field is defined for, or NULL if it is a base field; i.e.,
   *   it is not bundle-specific.

So the question because how can we check create access for files being attached to base fields. I think is we can't determine the bundle then checking the generic create access is all we can do.

Whilst we're at it the docs seem to be wrong here too

   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   (optional) The entity to which the file is to be uploaded, if it exists.
   *   If the entity does not exist and it is not given, create access to the
   *   file will be checked.

It's not create access to the file will be checked. - it's create access to the entity to which the file field is attached.

So I think we can improve the assertion.

alexpott’s picture

Version: 8.9.x-dev » 9.1.x-dev
StatusFileSize
new7.68 KB
new9.46 KB

Here's a test. While this whole class is supposed to be removed at some point I think test coverage of this specific method seems like a good idea.

Because we're adding a test with a setup we'll need 9.x and 8.x versions of this patch.

The last submitted patch, 4: 3154962-4.test-only.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 4: 3154962-4.patch, failed testing. View results

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new681 bytes
new7.67 KB
new66.93 KB

Whoops test in the wrong namespace.

The last submitted patch, 7: 3154962-7.test-only.patch, failed testing. View results

alexpott’s picture

StatusFileSize
new9.46 KB

#7's patch had more than I bargained for in it. Needed to rebase properly.

vijaycs85’s picture

Thanks for the patches @alexpott. definitely better solution than

  1. +++ b/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php
    @@ -255,13 +255,17 @@ public function validateAndParseContentDispositionHeader(Request $request) {
    +      // Base fields do not have target bundles.
    

    nice comment to say why we do is_null

  2. +++ b/core/modules/jsonapi/tests/src/Kernel/Controller/TemporaryJsonapiFileFieldUploaderTest.php
    @@ -0,0 +1,206 @@
    +    // The no access user has no access at all.
    

    Minor: probably The 'no access' user has no access at all?

alexpott’s picture

StatusFileSize
new4.21 KB
new8.88 KB

Tidied up the test a bit and addressed #10.2

wim leers’s picture

#3: nice research! 👏

  1. +++ b/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php
    @@ -255,13 +255,17 @@ public function validateAndParseContentDispositionHeader(Request $request) {
    +      // Base fields do not have target bundles.
    +      (is_null($field_definition->getTargetBundle()) || $field_definition->getTargetBundle() === $entity->bundle())
    

    This is the change, and this comment is critical.

    Note that this logic mimics logic from elsewhere in Drupal core because the is no standardized FileFieldUploader in core yet.

    Perhaps those other places also need to be updated to account for this?

    (I'm glad we were testing our assumptions! It took over a year for somebody to set up a scenario that proved those assumptions wrong 😳 So while this assertion was perhaps annoying to update and to test for, that seems to have been valuable nonetheless.)

  2. +++ b/core/modules/jsonapi/tests/src/Kernel/Controller/TemporaryJsonapiFileFieldUploaderTest.php
    @@ -0,0 +1,176 @@
    +    // While the method is only used to check file fields it should work without
    +    // error for any field whether it is a base field or a bundle field.
    +    $base_field_definition = $this->container->get('entity_field.manager')->getBaseFieldDefinitions('node')['title'];
    +    $bundle_field_definition = $this->container->get('entity_field.manager')->getFieldDefinitions('node', 'article')['field_relationships'];
    

    🤔 So … this is using non-file fields to test this particular logic.

    Fascinating for sure.

    It's probably okay. But the larger question is the one I raised in my previous point.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bbrala’s picture

Added probable related issue.

alexpott’s picture

Re #12.1 No I don't the other instances of similar code have the same problem - for example \Drupal\file\Plugin\rest\resource\FileUploadResource::validateAndLoadFieldDefinition() copes fine when the entity doesn't have bundles and it's a base field.

Re #12.2 It seemed simpler to use available fields.

For sure #2940383: [META] Unify file upload logic of REST and JSON:API will help reduce the bugs in this area by sharing the logic amongst all three similar bits of code.

berdir’s picture

  1. +++ b/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php
    @@ -255,13 +255,17 @@ public function validateAndParseContentDispositionHeader(Request $request) {
    -    assert(is_null($entity) || $field_definition->getTargetEntityTypeId() === $entity->getEntityTypeId() && $field_definition->getTargetBundle() === $entity->bundle());
    +    assert(is_null($entity) ||
    +      $field_definition->getTargetEntityTypeId() === $entity->getEntityTypeId() &&
    +      // Base fields do not have target bundles.
    +      (is_null($field_definition->getTargetBundle()) || $field_definition->getTargetBundle() === $entity->bundle())
    +    );
         $entity_type_manager = \Drupal::entityTypeManager();
    

    I'm honestly unsure what exactly the purpose of this assert is. It can't be an actual access check, because assert is not enabled on production.

    What if we turn it around and just do $entity->hasField($field_definition->getName())? that works for all kinds of fields.

  2. +++ b/core/modules/jsonapi/src/Controller/TemporaryJsonapiFileFieldUploader.php
    @@ -255,13 +255,17 @@ public function validateAndParseContentDispositionHeader(Request $request) {
         $bundle = $entity_type_manager->getDefinition($field_definition->getTargetEntityTypeId())->hasKey('bundle') ? $field_definition->getTargetBundle() : NULL;
    

    this doesn't seem right.

    That means $bundle will be NULL for a node base field. It should use $entity->bundle() if the entity type supports bundles.

berdir’s picture

Oh wait, that doesn't work because that code is only used when there is no entity. Not sure how we would get it to check on the right bundle then.

kim.pepper’s picture

Can we split out methods for entity vs no entity? Would make it easier to read the logic.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bbrala’s picture

Status: Needs review » Reviewed & tested by the community

Can we split out methods for entity vs no entity? Would make it easier to read the logic.

This is not the place for that imo, we could do that in another issue. But this would also mean extra methods in this temporary class. I would like it if we don't touch this too much.

Other than that, reading this thread it seems everything has been adressed, we are expanding the tests to test the TemporaryJsonapiFileFieldUploader code and fixing the assertion error.

I'm setting this to RTBC.

bbrala’s picture

Adding credits for reviewers.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work

Fails on 9.4

Can we also get 10.x version?

Thanks

alexpott’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new1.05 KB
new8.98 KB

Patch applies to all branches and passes on all with minor fix to test data... so setting back to rtbc.

bbrala’s picture

Thanks @alexpott!

  • larowlan committed dce52d1 on 10.0.x
    Issue #3154962 by alexpott, vijaycs85, bbrala, Berdir, Wim Leers:...

  • larowlan committed dba429a on 9.3.x
    Issue #3154962 by alexpott, vijaycs85, bbrala, Berdir, Wim Leers:...
  • larowlan committed c6d0179 on 9.4.x
    Issue #3154962 by alexpott, vijaycs85, bbrala, Berdir, Wim Leers:...
larowlan’s picture

Version: 9.4.x-dev » 9.3.x-dev
Status: Reviewed & tested by the community » Fixed
Issue tags: +Bug Smash Initiative
+++ b/core/modules/jsonapi/tests/src/Kernel/Controller/TemporaryJsonapiFileFieldUploaderTest.php
@@ -0,0 +1,179 @@
+    // While the method is only used to check file fields it should work without
+    // error for any field whether it is a base field or a bundle field.
+    $base_field_definition = $this->container->get('entity_field.manager')->getBaseFieldDefinitions('node')['title'];

sneaky, I like that we commented here because it makes it obvious why we're using title to check a file field access

Committed dce52d1 and pushed to 10.0.x. Thanks!

Backported to 9.4.x and 9.3.x as we have a green run and the risk of disruption here is low.

Status: Fixed » Closed (fixed)

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