Spin-off from #2064191: Fix the 'target_bundle' field setting - 'target_bundles' are not validated when entity_reference is enabled
Problem/Motivation
The "selection handler" plugins are in charge of defining which entities can be referenced by an e_r field. However, they don't take part in the validation of entity_ref fields :
- ERItem::getConstraints() only hardcodes a check against the list of referenceable bundles, if the field uses the default "selection handler" plugin. That was added as a quickfix in #2064191: Fix the 'target_bundle' field setting - 'target_bundles' are not validated when entity_reference is enabled.
- Other selection handlers are left out of the validation process entirely. That includes ViewsSelector, which lets you define referenceable entities using a View, or even the various NodeSelector, UserSelector, that extend the default "by bundle" SelectorBase by refining the logic a bit (e.g "can't reference an unpublished node")
What is affected :
- REST operations
- The "options" widget are immune by design (they only let you select valid options), but they are seldom used (unusable with 20+ referenceable entities)
- The autocomplete widget is affected : it doesn't call the Selection plugin's validateReferenceableEntities(), because it uses the EntityAutocomplete FAPI element with #validate_reference = FALSE, which is correct for a widget (leave the real data validation to data-level constraints, to avoid doing the work twice), except the ValidReference constraint will do it (which it doesn't atm).
Meaning, the autocompletion list only shows valid options, but the user can manually type "whatever (42)", with 42 being the ID of any entity of the target type, and no error will be reported (other than the 'invalid bundle' violation caught by our hardcoded Bundle constraint). Any other restriction imposed by the Selection plugin is ignored .
- Other (contrib/custom) widgets can be affected as well, depending on their implementation.
Proposed resolution
It should be the selection handler plugins that define what is checked during validation, since they are the ones that already contain the logic for "what is referenceable for this field".
In #2064191: Fix the 'target_bundle' field setting - 'target_bundles' are not validated when entity_reference is enabled, it was considered to repurpose the existing ValidReference constraint to operate on the FieldItemList level (rather than Item-by-Item, which is a perf drag), and have its validator :
- collect the target_ids across each Item, call the *existing* $selector_plugin->validateReferenceableEntities($ids), and flag a violation on each delta whose target_id is deemed invalid
- collect the fresh ("autosaved") entities across each item, call a *new* $selector_plugin->validateNewEntities($entities) method (name TBD), and flag a violation on each delta whose entity is deemed invalid
As a side note : ViewsSelector is inherently incompatible with "autosave". You cannot validate that an $entity that isn't in the database yet will match a given View. How fun :-) That would probably need taking into account for the field config UI in #2412569: Target bundle for auto-created entity references with multiple target bundles. For the validation issue here, I guess ViewsSelector::isEntityValid() would just say "no / not supported" for unsaved entities.
Remaining tasks
- Decide/refine on the new SelectorInterface::validateNewEntities($entities)
- Fix tests
API changes
A new method would be needed on SelectorInterface to validate entities that are not in the db yet.
Comment | File | Size | Author |
---|---|---|---|
#74 | interdiff.txt | 10.88 KB | amateescu |
#74 | 2577963-74.patch | 39.98 KB | amateescu |
| |||
#74 | 2577963-74-test-only.patch | 6.48 KB | amateescu |
| |||
#71 | interdiff.txt | 9.46 KB | amateescu |
#71 | 2577963-71.patch | 33.29 KB | amateescu |
|
Comments
Comment #2
yched CreditAttribution: yched commentedAttached is my 1st attempt at a patch.
As I found out in #2576501: Helper issue for ER validation [#2577963] , relying on Selection::validateReferenceableEntities() and its EntityQuery seems to throw many errors.
- Some of them are caused by the fact that NodeSelector, UserSelector, ... refine their EntityQueries with extra conditions (e.g user.status = 1), thus further restricting the set of valid references. Those tests would likely need to be adjusted (e.g the referenced $user used by the test must have its status = 1)
- Some of them are weird fails in the infamous \Drupal\Core\Entity\Query\Sql\Tables::addField() :-/
Like : CommentNonNodeTest, fails because Tables::addField() can't find the 'status' field on users.
Couldn't get to the bottom of those yet.
Comment #3
yched CreditAttribution: yched commentedWithout the stale update, leftover from the previous issue.
Comment #6
yched CreditAttribution: yched commentedThose weird EntityQuery fails are probably related to this code from EntityReferenceItem :
This means that base e_r fieds that don't bother specifying a 'handler' setting, get a handler for a completely unrelated entity type (user or node, depending on what modules are enabled) ?
That looks potentially very problematic, even outside of this issue here :-)
IMO we should have a method on ERItem / ERItemList to "get the selection handler", and that method would then take care of "if 'default' then 'default:$target_entity_type" ?
Comment #7
yched CreditAttribution: yched commentedSo, "if 'default' then 'default:$target_entity_type" is already taken care of by SelectionPluginManager::getInstance().
Then maybe the default value for 'handler' setting should just be 'default', without trying to guess an arbitrary derivative ?
Comment #11
yched CreditAttribution: yched commentedA couple easy fixes.
Comment #12
yched CreditAttribution: yched commentedOpened a separate issue for #7 and "the default value for 'handler' setting should just be 'default', without trying to guess an arbitrary derivative", because that would be good fixing regardless of this issue here.
#2578249: Some e_r fields get the wrong Selection handler
Comment #14
yched CreditAttribution: yched commentedLooks like FileSelector's $query->condition('status', FILE_STATUS_PERMANENT); messes with a lot of tests doing file uploads. Investigating.
Comment #15
yched CreditAttribution: yched commentedYeah, so the issue is that newly uploaded files remain with status = 0 until the containing node has been saved (not fully sure when that happens exactly).
So they are still at status 0 when we run validation, and then FileSelection::validateReferenceableEntities() doesn't find them in its EntityQuery.
Also of note : this status = 0 condition in the validateReferenceableEntities() EntityQuery is added for file fields by FileSelection, but not for image fields since there is no ImageSelection.
Comment #16
yched CreditAttribution: yched commentedFile status going from 0 to 1 happens dunring FileFieldItemList::postSave() during the save of the parent entity.
So when we validate the entity, newly uploaded files are not "valid references" in the sense of FileSelection::validateReferenceableEntities(). They only become "valid" when the entity gets saved.
Damn. That seems like a serious dent in the 'validation should use the same (efficient) logic than SelectionInterface::validateReferenceableEntities()" approach:-/
Unless validateReferenceableEntities() takes a second param that lets FileSelection be more tolerant with status :-/
Comment #17
yched CreditAttribution: yched commentedGiving a shot at that. Still raw/undocumented, just checking where that brings us with tests.
Comment #18
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedSelection handlers are not tied to the ER field anymore, so this one-line description shouldn't mention it either. The same is true for the issue title for that matter :)
Comment #20
yched CreditAttribution: yched commentedHaven't fully processed #18 :-)
Meanwhile, this might be green.
(some image related tests failed because of the
weirdness mentioned in #2578249-7: Some e_r fields get the wrong Selection handler)
Comment #21
yched CreditAttribution: yched commented@amateescu #18 : ok, but that comment is just being consistent with the existing one for validateReferenceableEntities() ?
Also, this issue is not tying Selection handlers to know about e_r fields, but is making the ValidReferenceConstraint, used by e_r fields, use the Selection handlers assigned o the field.
(Selection handlers don't know about fields, but fields do rely on Selection handlers, right ?)
Comment #22
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedYes :)
And the description of
SelectionInterface::validateReferenceableEntities()
in HEAD is the one that's wrong, not the fault of this patch, sorry :/Comment #26
yched CreditAttribution: yched commentedAnd that one should be green.
The adjustments in Selection plugins (new validateReferenceableNewEntities() method, new $strict param in validateReferenceableEntities()) are still rough, but aiming for a green bot atm :-)
Comment #29
yched CreditAttribution: yched commentedThat's actually an rc deadline, since this necessarily means adding a new method to SelectionInterface
Comment #30
yched CreditAttribution: yched commentedExpanded the IS about why the autocreate widget is affected (accepts any input, only "has correct bundle ?" will ever be checked)
Comment #31
yched CreditAttribution: yched commentedThat "$strict = TRUE/FALSE" additonal param in validateReferenceableEntities() to be able to bypass the 'file.status' = 0 check is really messy - and would de facto mean REST operations become able to reference non-permanent files. Not good.
Instead, what about turning FileSelection::buildEntityQuery() into : "file.status = 1 OR (file.uid = current user AND file.created < now() - 1 or 2 seconds)" ?
That would allow the valdation to run while the newly uploaded file hasn't been made permanent yet by the parent entity being saved ?
Comment #32
yched CreditAttribution: yched commentedAlso, the holes in validation, even on the most common widget (autocreate), still make this at least major IMO.
Comment #33
yched CreditAttribution: yched commentedPatch :
- Removes the awkward $strict param that was added in #17, and instead adjusts the EntityQuery in FileSelection to be tolerant on file uploads (as per #31)
- Differentiates between "entity is not referenceable" and "entity does not exist" in error messages (adds one EntityQuery, but only if there are violations). Reverts a couple tests to the "does not exist" message.
Still @todo :
Adjust the shape of the new validateReferenceableNewEntities() method (current one is not great DX to override in sub classes)
Comment #34
yched CreditAttribution: yched commentedSilly me.
That works for tests, but not IRL of course, because this will pass validation for the ajax upload, but will then fail when the user finally submits the whole entity form (also, my silly code checked for 2 minutes instead of two seconds...)
So the window has to be larger (how long do we allow you to keep your entity form opened before you actually submit it ? a couple hours ? a day ?) or even just "allow temp file created by the current user, no time restriction" :/
Going for the latter (no time restriction on temporary file created by the current user)...
Comment #35
fagoThe change makes totally sense. Had a short look at the patch:
validateReferenceableAutoCreatedEntities() maybe ?
missing space before "cannot"
Comment #36
yched CreditAttribution: yched commentedIMO that here approach would also be the correct fix for #2587957: Arbitrary files can be referenced in file fields even if validation using form API would fail (recent critical)
Comment #37
catchComment #38
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThe patch looks great to me!
I can't decide if
validateReferenceableAutoCreatedEntities()
is more clear than the current method name, I'm ok with either of them.Comment #39
yched CreditAttribution: yched commentedRerolled.
Comment #40
yched CreditAttribution: yched commentedThen starting to apply the rule of "new (autocreate) entities should have the same checks than existing entities" (i.e validateReferenceableNewEntities() must apply the same logic on Entity objects than buildEntityQuery() does for entity queries).
This is where things get funny - the test fails reveal that autocreate widget is broken with nodes :-)
- The autocreate widget creates nodes with just the input string as label and that's it, meaning node.status stays at 0.
- But you're not supposed to reference unpublished nodes, and NodeSelection now checks that on autocreate entities too.
Thus the current test for the autocreate widget, EntityReferenceAutoCreateTest, that is based on creating/referencing nodes, fails.
The underlying bug is that the autocreate widget creates unpublished nodes, which is probably not the desired behavior to begin with.
By adding the checks, the patch here exposes that upfront and makes the autocreate widget useless/broken on nodes (will always fail validation when trying to create a new node and reference it)
Comment #41
yched CreditAttribution: yched commentedGoing on a vacation in 2 days, so I don't think I'll be able to push this forward myself. Just wanted to post what I had so far.
Comment #44
yched CreditAttribution: yched commentedForgot the interdiff for #40.
Comment #45
drnikki CreditAttribution: drnikki as a volunteer commentedPromoting to critical based on #2587957-26: Arbitrary files can be referenced in file fields even if validation using form API would fail
Comment #46
xjmSince this is critical now, it no longer needs to be triaged as an RC target. Thanks!
Comment #47
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThe fix for this should be pretty simple, we just move the responsibility of creating the new entity to the selection handlers, because they already have hardcoded logic about what makes an entity referenceable :)
Comment #48
yched CreditAttribution: yched commented@amateescu : yep, probably. That would also let entity types with no label key support autocreate ?
And would also let us more clearly state that ViewSelection doesn't support autocreate.
Which brings : maybe those two new methods on the Selection (create the fresh entity, check that the new entity is valid) could be a secondary interface (SelectionWithAutocreate), thus removing the API break ?
I'm not where I can work on that myself though ;-)
Comment #49
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedHm.. having a separate interface would also help with hiding the autocreate feature in the UI if the target entity type doesn't support it, but that means we would need to provide a new selection handler for each core / contrib / custom entity type, which is.. not very DX friendly :/
About having two methods, one to create the entity and one to validate it, isn't that a bit superfluous? I mean.. the method that creates the new entity should also know very well if it's valid or not.
I can work on the patch once we reach an agreement on the direction :)
Comment #50
yched CreditAttribution: yched commentedWondered about that too, but the "validate" method is needed for REST and other direct API uses. The "create" method would be used only by the widgets in form/UI flows.
Comment #51
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedRight, two methods it is then. Now we just need to settle on separate interface or not :)
Comment #52
swentel CreditAttribution: swentel commentedtypo: supprt
Comment #53
yched CreditAttribution: yched commented@amateescu: oops, I missed #51 somehow.
Not too easy for me to reason on the inheritance model without being able to look at the codebase. But what if :
- autocreate methods were added to a secondary interface,
- DefaultSelection doesn't implement that interface, but the individual selection derivatives can, helped if needed by a trait providing base implementations ?
Then autocreate becomes opt-in by entity type, that's kind of a behavior break, but it also sort of reflects the current state of affairs (autocreate is mostly supported on taxo terms)
Maybe I'm missing things, other suggestions welcome, as I said I'm not in the best position atm ;-)
Comment #54
amateescu CreditAttribution: amateescu for Drupal Association commentedI think I'm more in favor of keeping autocreate as opt-out by default, for the reasons mentioned in #49.
Here's a patch that fixes the creation of new entities for core entity types by moving the
createNewEntity()
method to the selection handlers. Both new methods are now on a new interface, as discussed above, and DefaultSelection implements this interface in order to keep the current behavior from HEAD.Comment #56
yched CreditAttribution: yched commented#54 : works for me :-)
Still a behavior change though : if your custom selection class doesn't implement the new interface, no autocreate. That's probably not likely to affect a lot of existing code, but we'd need to run that by core committers regarding an RC / minor version strategy.
Also, would be good to hear DER people about this patch in general, I guess ?
Comment #57
amateescu CreditAttribution: amateescu for Drupal Association commentedThe test fail from #54 is caused by a test form element that was supposed to have its validation disabled but it didn't :/
We could write a short change notice for this, which should also mentioned that
\Drupal\Core\Entity\Element\EntityAutocomplete::createNewEntity()
was moved to the new "selection with autocreate" interface, but I'll leave it to core committers to decide if we need it or not.Comment #58
jibranI tried the above patch and DER is failing tests because of this. DER doesn't use
ValidReference
constraint. It has its ownValidDynamicReference
constraint so after this patch DER has to unsetValidReference
constraint inDERFieldItemList
class which we can do in #2405467: Add EntityType and Bundle constraint on entity property of DynamicEntityReferenceItem so no biggie.Other then constraint DER doesn't touch
EntityAutocomplete
orEntityReferenceSelection
just use them as is so moving the methodcreateNewEntity
won't affect DER at all.+1 form DER.
Comment #59
alexpottIf we're going to have a behaviour change then we need a CR
Comment #60
amateescu CreditAttribution: amateescu for Drupal Association commentedHere it is: https://www.drupal.org/node/2606656
Comment #64
klausi#2594565: File extension + max_filesize should be validated using a Constraint on file fields got committed which causes conflicts here, sorry.
Comment #65
amateescu CreditAttribution: amateescu for Drupal Association commentedNo worries, here's a reroll :)
Comment #67
klausientity_manager is deprecated, use one of the new services.
can we use setPublished() here if we know that the entity is of CommentInterface? When we deal with base fields we should never use the weird $entity->{$random_field_name}->value access method but always the dedicated methods on the interface.
same here, setPublished().
can we use setPermanent() here from FileInterface?
can we use isPermanent() and getOwnerId() here form FileInterface?
I'm a bit worried about new files that are set to permanent immediately, but I'm not familiar enough with the ER selection handling code, would have to test that.
Comment #68
klausiWe should probably not call the variable $entity here but rather $comment instead. That way it is immediately clear that we are dealing with comments only here, because the selection handler is only for comments anyway.
Also in all the other selection handler places added by this patch.
Comment #69
klausiHm, we only allow auto creation of new entities on taxonomy term reference fields in core. There is currently no way to auto create a file through an entity reference field.
In order to even get an entity reference field that references files (not a file field!) you need to add a field referencing nodes, then edit it and switch it to referencing files.
I don't see the use case for using auto_create with files since there is no form in Drupal that would allow you creating files. The one and only place to add files is via field fields, file entities are completely dependent on that field, they can only be created there. The additions to FileSelection.php look a bit bogus to me, since that code can never be used in Drupal core. Maybe we should let that open for some contrib module like media that knows more about the use case of file creation/selection? Or maybe I'm just missing something here.
Comment #70
klausishould use $this->getTypedDataManager() instead of \Drupal.
Comment #71
amateescu CreditAttribution: amateescu for Drupal Association commentedThanks for the reviews, @klausi :)
This patch fixes #67, #68, #70 and the current test failures.
That's only true for the UI, the "autocreate" API in HEAD supports all entity types. For example, this piece of code uses the autocreate/autosave functionality:
I don't understand this at all.. can you explain in a bit more detail what you see as not working?
Comment #72
BerdirI think what he meant is that the "File" option under References is a file field and not an entity reference field. But you don't need to select node first and then change, you can also select other and then select file.
But yes, it's definitely not possible to create a working new file through a normal entity reference field, unlike terms or other things, creating the entity isn't enough, you actually need a file behind it. As @yched commented in the other issue, the only possible use case is using that for validation through the inherited behavior/configuration in a file field reference instead of the custom constraint that we added now.
Comment #73
klausiwhat validateReferenceableEntities() method do you mean? It does not exist on this interface? Should you point to another interface that has this method? Or should this interface extend the other interface?
we should use $comment as variable name and $comment->isPublished()
same here, let's use $node and ->isPublished().
same here, let's use $user and ->isActive()
This patch does not add any new test coverage, could you list in the issue summary which test cases cover the new code? I'm having a hard time finding them. Basically there should be a test somewhere that adds an invalid + unsaved entity into a reference field of another valid entity, then calls validate() on that entity and get the violations from the invalid referenced entity.
It seems that EntityAutocomplete::validateEntityAutocomplete() duplicates the work of ValidReferenceConstraintValidator a bit, but since EntityAutocomplete is a form element that must function independently of the entity validation system this should be fine.
Otherwise the changes make sense to me.
Comment #74
amateescu CreditAttribution: amateescu for Drupal Association commentedFixed all the points from #73 and added test coverage. Thanks for reviewing again :)
Comment #75
klausiPlease no random data in tests, see #2571183: Deprecate random() usage in tests to avoid random test failures. Please remove all random*() calls from the lines that you add and use hard-coded strings instead.
The test does not cover the case where multiple entities are referenced in one field. We should at least have one that mixes multiple existing and new entities in one field so that we test that the array_diff/reduce stuff works.
Comment #77
effulgentsia CreditAttribution: effulgentsia at Acquia commentedI discussed this issue with @catch and @xjm, so want to update folks here that RC4 will be tagged within the next couple of hours and will be the final RC. If this doesn't land by then, I think we'll need to do the following:
Comment #78
catchThe randomness in the tests should not block commit, until #2571183: Deprecate random() usage in tests to avoid random test failures lands nothing is deprecated, and we've not agreed to block patches for using those methods yet. I agree with dropping the randomness in general though.
The extra test coverage would be great, but I think this patch gets perilous for 8.x at all once RC4 is tagged, so I would strongly consider doing that in a follow-up.
Or to put it differently:
- if we put it into RC4 as is, and something horrible is picked up before 8.0.0 is tagged via RC4 testing, we can roll it back.
- if we put it in after RC4, we might not pick anything horrible up until after 8.0.0 is tagged.
- if we don't put it in at all, that is horrible.
So the first option, for me at least, is the least horrible.
I don't see this being fixable in 8.1.x as is, I think we'd have to do something like https://www.drupal.org/project/field_validation in core and then have an 'entity reference selection handler validator' which could get messy fast just to me what for me is a natural user expectation of the current interface.
Given all of those things, moving to RTBC.
Comment #79
alexpottI agree with catch's comment in #78 - committing this patch as-is is probably the best way to go. I think we're missing test coverage where multiple new entities are valid and invalid and where we mix new and existing entities.
Committed fbe0991 and pushed to 8.0.x. Thanks!
Removed unused use on commit.
Comment #80
catchOpened #2614408: Add test coverage for multiple invalid + mixed existing/new entity reference validation.
Comment #89
kylebrowning CreditAttribution: kylebrowning as a volunteer and at Acquia commentedper #79.
Comment #90
BerdirThere are at least three contrib modules that used that constraint like this. Payment, Simplenews and Entity Reference Revisions.
The problem is that ValidReferenceConstraintValidator dies with a fatal error if you still have that as it expects a list of field items now and not a single one.
Would have been nice to avoid an error but we need to at least mention this in the change record. Not quite sure what to write, is it enough to just remove this or do those modules need to do anything?
Comment #92
amateescu CreditAttribution: amateescu for Drupal Association commentedAs discussed with @Berdir, I updated the change record to mention the changes needed for the ValidReference constraint: https://www.drupal.org/node/2606656/revisions/view/9103328/9127282