Problem/Motivation
All Paragraphs entities are embedded in another entity. When someone manages to directly access the entity, the default paragraphs access check in ParagraphAccessControlHandler will always return AccessResult::allowed().
Example:
When you add a file field to a paragraph type, and set the storage to private, Drupal will automatically see which entities referenced to the file when someone requests it. Drupal will execute a view access check on the referencing entity, and paragraphs will return AccessResult::allowed().
Proposed resolution
We need to have a more advanced access check besides relying on the parent entity to handle access control.
In D7 we always keep track of our host entity, and if we can't find it (not set by widget/formatter), we'll load it.
If we do the same for D8, we are always able to check access on our parent entity.
Remaining tasks
- Track host entity, if not found, try to find it by reverse lookup and load it
- Check host entity for access. Code can be based on D7 version
- Create upgrade path
- Create test for specific problem (file field)
| Comment | File | Size | Author |
|---|---|---|---|
| #83 | paragraphs_2585447_access_83.patch | 7.33 KB | miro_dietiker |
| #74 | interdiff-2585447-56-74.txt | 2.08 KB | johnchque |
| #74 | entity_access_does_not-2585447-74-non-combined.patch | 7.32 KB | johnchque |
| #74 | entity_access_does_not-2585447-74-combined.patch | 24.76 KB | johnchque |
| #63 | not-combined-2585447-63.patch | 7.5 KB | tduong |
Comments
Comment #2
jeroen.b commentedComment #3
jeroen.b commentedHere's a first attempt, it addresses all remaining tasks.
There is a new issue over at Entity Reference Revisions that makes sure we don't load the host entity on rendering for performance reasons #2586771: Add referring entity in the formatter so the entity can use it.
I simpletested this locally with the Entity Reference Revisions patch and without.
Comment #4
jeroen.b commentedComment #5
jeroen.b commentedComment #6
miro_dietikerHmm, this direction feels kinda wrong to me.
Suddenly the target entity needs to know about the embedding context and even persist it.
As a result, you need to fight already against serialization recursion problems..
I thought we should resolve the permission problem on the field level (by the entity reference revisions field / the paragraphs formatter) and suppress items top down. What is the problem that requires the embedded entity itself to be aware of the parent context?
Comment #7
jeroen.b commentedHere is an example of why we need to do this:
I know it feels wrong, but sadly, it's the only proper way to do access control.
If we want to do field level access checking we still need to know about the host entity and the host field so the solution would be kind of the same.
Can't really say I'm fighting it. I'm just removing it as we only use hostEntity as a "cache" and it's not part of the actual entity.
There is currently no issue in the top down approach, it already works fine.
The only issue this takes care of is direct entity access checking on paragraphs entities.
Comment #8
miro_dietikerHm, i think the problem originates in the fact that Drupal does not offer anything to deal with embedding / recursion.
One assumption we introduced was that direct access is not happening ever. To make sure this applies, we did not offer a link template to edit the view and entity directly.
I don't know what we can do with the private file problem using the core methods.
Should we try to report usage against the host entity?
I will ping Berdir for some better advice about this challenging situation.
Comment #9
jeroen.b commentedThanks!
I'm not sure if private files are the only problem, I can image there are other modules doing direct entity access.
Comment #10
jeroen.b commentedOne other approach I thought of:
Copying the file usage service concept to entity reference revisions to create a entity reference revisions usage service. It would still require most of this patch but it's a more general fix.
That might alo be the start of a fix for #2429335: Deletion of referenced entities
Comment #11
berdirHm, don't have much to add.
Usually, we say that it is not your responsibility to check access of your parent, e.g. it is wrong for field level access checks to check the entity too.
But yes, the private file use case is valid, we have to do something about that. The usage approach might work, we've discussed that before, also with drupal-media/media_entity, which has similar requirements.
Reviewed parts of the patch:
the revision id is always the array key of the result. I don't think you really want allRevisions() here, you care about the current revision that's using oyur paragraph, no?
The thing with access result objects is that you should keep using them, not re-create them.
So I think you want something like
(cacheUntilEntityCHanges is deprecated).
Same here, you want to use the existing access object, maybe that varies by user or by permission (or moon phase ;)). Just do return $host_entity->access($check_parent_operation, $account, TRUE)->addCacheableDependency($entity);
I don't think you need a dependency on the host entity, if it depends on a status of that (e.g. published or not), then the host entity access control handler is responsible for adding that.
Comment #12
jeroen.b commentedThanks for the review, fixed in this patch.
I need allRevisions() to make sure we find the reference, even if it is a older revision. Let's say you have revisions enabled on your node. You upload a private file to a pragraph, save the node, remove the file again, save the node again.
Normal users should be denied access to the deleted file, but when the editor looks at the older revision, he should still be able to see the file. This is the only way to find the correct revision right?
How about we get this patch in now and work on the usage approach in a Entity Reference Revisions issue? The change in this module would only be small if we implement that:
Comment #13
jeroen.b commentedCan we talk about this again? I consider this module insecure while this is issue isopen.
Comment #14
miro_dietikerOK, found time to discuss the situation.
We decided that the proper approach for paragraphs is to declare a paragraph as an embedded entity by concept.
This means that the entity host is immutable and due to the field API design it's needed to resolve the parent relationship from the item. And the access check by paragraphs is needed and thus (with knowing that there is only one parent) simple.
We still want to see integrity management delegated as much as possible to entity reference revisions:
ERR introduces the concept of a composite entity. It will support parent_type/parent_id keys in the annotation of the target entity that define the parent entity type and id field name. ERR will then make sure that the IDs are persisted on creation, and also will delete child items.
This means that a paragraph item is NOT reusable (thus has no label, no management metadata). If ever someone wants to do this, he will add a module with a new / altered widget that could still add an operation "make this paragraph item reusable" that leads to creation of a separate (reusable, value cloned) entity with own metadata and switching the paragraphs bundle to a bundle with a reference to the reusable item. Integrity (deleting the reference) is still well done by ERR... And all of the complexity of a mixed case disappears.
Comment #16
miro_dietikerSo let's postpone this issue for proper ERR support #2641824: Maintain composite relationship.
And promote to release critical for the security implications of potential information disclosure.
Comment #17
johnchqueIt seems the Composite relationship will help with this.
Comment #18
johnchqueDiscussed with @Berdir , added host check, the patch includes test coverage. Also I am working on top of the issue #2676190: Make paragraphs use ERR composite relationship since that will make the check easier.
Comment #22
johnchqueSeems to be working locally, maybe the tests will pass after have committed the other issue (?)
Comment #23
johnchqueSorry, I should also have uploaded the non combined patch. Here we go.
Comment #27
miro_dietikerThere is a moment where a paragraph is stored and not yet attached to the parent entity. Thus getParentEntity() will not return the parent.
Your code would fail hard here.
Since this situation is special, it contains risk for many other applications to break things. We should well document it and test cover it.
Actually this situation is initiated by the ERR module and not paragraphs and needs documentation there if not already done.
Comment #28
johnchqueUploaded combined and non combined patches, add a check for the parent and also extended tests for preview mode.
Comment #35
johnchqueLets see why the combined patch is not working.
Comment #39
johnchqueMade small changes, added interdiff against patch of comment 23 to make the review easier.
Comment #46
tduong commentedDebugging it I get
http://d8.dev/system/files/privateImage.jpg, but form raw data I get/system/files/styles/medium/private/privateImage.jpg?itok=<em>something</em>, so maybe you should hardcode it. Not sure if this could work though.(I had a similar problem here #2671046: Paragraphs Type Permission should take the publish/unpublish status into account and I've just hardcoded it).
Comment #47
johnchqueChanged the test, this should work, also again diff against patch #23
Comment #54
johnchqueAdded interdiff against #35
Comment #55
tduong commentedI don't agree that changing that $image_url position could change anything for the test, since you are creating that file URL hardcoding the URI...
Also, please write "\Drupal\paragraphs\Tests\ParagraphsAccessTest"! ;)
Comment #56
johnchqueChanges made based on comment #55 also changed a bit the generation of the url, let's see what testbot thinks about this.
Comment #63
tduong commentedFor the non-combined patch, you haven't defined 'parent_type' and 'parent_id' anywhere, that is why all the tests fail... Added these data, works locally, let's see if there are any other bug for testbot.
Later we will debug deeper the combined patch.
Comment #66
berdirThe non-combined patch isn't meant to work, it's just for reviewing. those fields are added in the other issue.
When you have something like that, I suggest you disable testing for those.
Comment #71
johnchqueThanks for the feedback, after checking the test fail it seems it is not working, it still finds the image, not sure how to proceed.
Comment #72
tduong commentedNeed help for this.
Comment #73
johnchqueDebugging some I think I found the problem, should be in the access check.
Will test more and upload a new patch soon.
Comment #74
johnchqueIt seems there would be a caching problem in core. Added a second image inside the same paragraph, the access check should be fine. Let's see again what test bot thinks about this.
Comment #78
johnchqueAfter so many ideas, changes and patches 'the idea' came to us, it seems testbot is using rc4 of ERR which does not implement composite relationship yet. That is the reason why the tests are failing.
Comment #79
miro_dietikerStill applies, retesting. :-)
Comment #83
miro_dietikerAh, we need the second patch.
Comment #84
miro_dietikerAnother christmas present today. Committing!