NodeForm::preview does $store->set($this->entity->uuid(), $form_state); and subsequently, this is restored in NodePreviewConverter::convert and NodeForm::form as well. Note the only key is the entity uuid.
If two users edit in parallel and they press preview at the same time then one of them will lose their edits because $form_state gets overridden by this restored $form_state in NodeForm::form this is most definitely a data loss issue:
There are a multitude of workflows where concurrents edits of a node are allowed, usually separate revisions are being created. Also: translations. The only key is the entity uuid so if multilingual fields are used then the race can be on.
Whether this is also a security hole is arguable, it feels like to me but it definitely leads to data loss.
| Comment | File | Size | Author |
|---|---|---|---|
| #47 | 2421263-2.47.patch | 84.72 KB | alexpott |
| #44 | tempstore-private.44.patch | 84.71 KB | larowlan |
| #44 | interdiff.txt | 2.83 KB | larowlan |
| #40 | 2421263.40.patch | 84.39 KB | alexpott |
| #40 | 33-40-interdiff.txt | 64.97 KB | alexpott |
Comments
Comment #1
jibranYeah we can see the similar kind of issue here #1959806-56: Provide a generic 'entity_autocomplete' Form API element
Comment #2
berdirAre you sure this is really a security issue? The node preview has an access check that does a check that the user is allowed to update that node object. If you have workflows with that, then you could prevent it there?
Would we validate the uid then if we add it? Or can you still share a preview with someone? Because that is a pretty useful feature, and we have another case on top of that, an external application that allows to create content and preview it in Drupal.
Comment #3
dawehnerTagging the issue.
Comment #4
not_chx commentedhttps://www.drupal.org/project/cps but I am reasonably sure https://www.drupal.org/project/workbench_moderation can also create a situation where you have two people working on two revisions and they don't know much about each other.
Comment #5
not_chx commentedComment #6
andypostThe only problem in current implementation that current user is not able to preview revisions because no vid passed.
The stored form state in tempStore is set and retrieved using factory that adds current user, so each user will get only own stored form state.
Comment #7
not_chx commentedTempStoreFactory does
then TempStore stores this and as the code uses get/set (not the owner versions) so no, each user definitely does not get its own version.
Also: this will happen during translations as well.
Comment #8
catchCPS allows you to do it, but also warns that there's an inherent race condition when either of the revisions eventually gets published (i.e. last published wins). Apart from the warning I personally think it should at least have the option to lock an entity once there's a draft in any changeset. Note that race condition in CPS exists even if the revisions get edited on separate days. I do think you'd run into this bug if you had concurrent edits of the same entity at that same time, so it does exacerbate things.
Don't see how there's any security issue, it's a straight race condition.
Comment #9
andypostI kind of that could at least prevent data loss
Comment #10
andypostA test to show failure
Comment #14
formatC'vt commentedLet's add unique key (based on node
uuidand$form['#build_id']) for each preview cache record and store this key in user session.Comment #16
formatC'vt commentedoops, my bad =)
Comment #18
sidharrell commentedComment #19
formatC'vt commentedNext try (locally test passed)
Comment #20
andypostNo test in last patch!
Suppose to use session here is bad.
build ID addition allows editing node by one user in different tabs, no tests.
nice idea... to abstract key that allows passing preview between sessions/users, great change for separate issue!!!
Comment #21
andypostAre going to hardening the permission for entity-in-preview stored?
Current
I think this change is needed or should be extended if we are not allowing access for entity/form state for different users
Comment #22
formatC'vt commentedroger that, patch coming soon =)
Comment #23
formatC'vt commentedBrand new version of patch. Includes test from
2421263-node-preview-10.patchwith little fix inassertUrl(added option['absolute' => TRUE])I doesn't see a reason to use
getIfOwnerin this implementation, but i provide two version of patch: with getIfOwner and without.Comment #24
larowlanlooks good to me
Comment #25
berdirIs hardcoding the session_id() there really a good idea?
Comment #26
alexpottWe already use session_id() in temp storage
Can't we just use the getIfOwner() and not do any of the session_id() appending in NodeForm. But I guess the problem is that multiple users should be able to preview the at the same time. Maybe we need a mutli-user tempstore here since putting this logic in NodeForm feels wrong.
Comment #27
andypostI'm sure that hardcoding getIfOwner() is not good.
Suppose each submit should generate a hash based on values.
This hash defines a storage preview key and some kind of access logic to generate preview (to allow contrib to configure)
As comment module maintainer I'd like to unify preview logic for comments as well.
Comment #28
alexpott@andypost that's why I was suggesting a multi-user temp store which automatically appends the owner to the key.
Comment #29
formatC'vt commented@andypost when we come back from preview we have no values data and can't calculate hash. No hash - no key. But we can put hash value to form query url.
@alexpott we discussed this in irc with andypost, unfortunately in current implementation owner stored as part of storage value.
O:8:"stdClass":3:{s:5:"owner";s:1:"1";s:4:"data";O:26:"Drupal\Core\Form\FormState"We can modify current TempStore class (for creating unique key names based on user), but i'm not sure this is good, because other modules can use this feature - one user can access to data added by other user with easy to calculate key name (like node uuid).
I think this is should be a different class for this (named something like MultiuserTempStore).
Also we can use per-user collections (
PRIMARY KEY (`collection`,`name`)) instead of unique key names. Or move deeper and add column 'owner' to storage.Comment #30
alexpottHere's a start - I've created FooTempStore for a temp store where the keys are guaranteed to be per user. I think we should rename the existing temp store to
SharedTempStorebut did not do that yet so we can sort out the names here first.MultiuserTempStoredoes not really work for the new class because both temp stores are designed to be used by multiple users it is just the use case is ever so slightly different.I also discovered that quickedit probably has the same problem. The other places that are using tempstores are the multiple delete confirm forms for users and nodes - these don;t have this same problem because they are using the user ID as the key and will never be available to anonymous users. Yes it is rare but anonymous users (if the site is configured to allow it) should be able to create nodes.
Comment #31
alexpottOh and I think there is a security issue here. What if your are changing something on the node that would prevent the other user getting access and they get your tempstore and can see the updates you would have made? In HEAD at the moment it is possible that the user gets the other users data from the tempstore when they preview the node.
Comment #33
alexpottOops.
Comment #34
kim.pepperDoing some manual testing.
Comment #35
kim.pepperValidated #33 fixes this issue.
Steps to manual test:
Comment #36
kim.pepperUser 1 edit:
User 1 preview:
User 2 edit:

User 2 preview:

Comment #37
kim.pepperComment #38
kim.pepperRenamed FooTempStore to UserTempStore which I think makes it clear that its a per-user tempstore?
Happy to call the bikeshed yellow, however. ;-)
Comment #39
larowlanI'd like my bikeshed red
I like PrivateTempStore as the name.
But yeah, fix looks good - names aside
Comment #40
alexpottThe patch attached:
Comment #41
wim leersNote that Quick Edit specifically added test coverage for concurrent editing:
\Drupal\quickedit\Tests\QuickEditLoadingTest::testConcurrentEdit(). Done back in #1901100: Make Edit module work with TempStore, so revisions are not saved on all atomic field edits. EDIT: and discussed further in #2111427: All entity forms should implement (TempStore) locking to protect against concurrent edits, which was a follow-up to that one.Comment #42
alexpottre #41/@Wim Leers - looking at the test - you're testing the save does not work because the timestamps have changed - that's not about the tempstore.
Comment #43
wim leersYeah, it's intra-user concurrency, not inter-user concurrency.
Clarifying that in the title. I hope it also applies to the general issue here, and not just to Quick Edit.
Comment #44
larowlanFound some nits (below) and fixed them
Added draft change notice (https://www.drupal.org/node/2430511)
Updated issue summary
good to go
nitpick > 80
same
same
Comment #45
fabianx commentedRTBC + 1
Nice security improvement!
Comment #47
alexpottNeeded a reroll...
Git took care of it :)
Comment #48
webchickGreat. Committed and pushed to 8.0.x. Thanks! :D
Comment #50
formatC'vt commentedhm, committed, anyway i say why i use
session_id()instead ofgetOwner()=)getOwner()return session id for anonymous and user id for logged user.For example, potential data loss for form preview still exist, but now for single user.
It is easy to reproduce:
1. Open node twice in one browser (tabs), edit -> preview etc. -> data loss.
2. Open node in different browsers on different devices, edit -> preview etc. -> data loss.
I think p.1 impossible on production, but p.2 can be possible and
session_id()prevent p.2In other words i think key for private storage
BwXCiG05F8jwsT3mysaazmB-KOfiC8Ck-oHHah-eK0E:22d2527b-c027-4eff-bb11-1726c3da72b8better than1:22d2527b-c027-4eff-bb11-1726c3da72b8=)Comment #51
alexpott@formatC'vt - The problem with using the session_id would mean that a logged user who opens two tabs in the same browser would experience a different behaviour than a user who logged in using two different browsers. Does that really make sense? Also is it data loss when it is being knowing done by the same user?
Comment #53
xjm