Problem/Motivation
In Drupal 11, Webform $form builds seem to be sharing cache between users which is causing issues when users should be able to submit 1 submission per user for each.
Steps to reproduce
1. Spin up a new Drupal 11.1.x test site - (Using 11.2.2)
2. Install Webform 6.3.x - (Using 6.3.0-beta3)
3. Enable the dynamic_page_cache module
4. Create a Webform "Test Webform", can just be a single checkbox field.
5. Under the webforms Settings -> Submissions, set Submission limit settings -> Per user -> Per user submission limit to be 1
6. Under the webforms Settings -> Access, untick "Anonymous User"
So now we've got a Webform that requires you to be logged in, but each user can only submit the webform once.
Now create 2 users, User A and User B.
1. User A logs in and accesses the Webform, at endpoint /form/test-webform
2. After submitting, User A goes back to the page /form/test-webform, and correctly sees the text "No more submissions are permitted."
3. User A logs out
4. User B logs in, and accesses /form/test-webform
5. User B now incorrectly sees the text "No more submissions are permitted.", even though they have never submitted this webform.
6. If you clear the cache with drush cr, User B will be able to see the correct form.
Interestingly, if you then log back in with User A, they'll correctly be blocked again, but then if you go back in with User B, they will now also be blocked from submitting again.
It seems like both users are sharing this same cached version of the form, even though they should see very different $form builds.
If I have some time I will try and write this into a test case. From my testing this doesn't affect Drupal 10.x.
Issue fork webform-3538483
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
thomwilhelm commentedI've committed a test which replicates this issue on my issue fork:
https://git.drupalcode.org/issue/webform-3538483/-/commit/59af043ac82a55...
Comment #3
cilefen commentedWhich Drupal Core commit caused this? It could be #3538294: Regression: All stack middlewares are constructed at the same time even for cached pages or something else.
Comment #4
thomwilhelm commentedMy first thought was the change record in Drupal 11.1 "Forms can be opted in to render caching": https://www.drupal.org/node/3442559
Comment #5
jrockowitz commentedThis feels like a critical and stable release blocking issue
Comment #8
gergo.bodi commentedThe tests that have a status of failed did not fail because of my changes. As I can see, these tests are also failing on other MRs.
Comment #9
mxr576Comment #10
jrockowitz commentedTests are passing. I am going to commit this.
Comment #12
mxr576@jrockowitz Hi, it is really great that this D11 stable major blocker was merged, but I was not sure if the test for anonymous users actually covers the expected behavior, do you know?
https://git.drupalcode.org/project/webform/-/merge_requests/764#note_619905
Comment #13
jrockowitz commentedI don’t know if test coverage was checking this issue. Should we reopen this ticket?
Comment #15
bkosborneI looked into exactly why this broke in D11. It is due to the change introduced in #2578855: Form tokens are now rendered lazily, allow forms to opt in to be cacheable as suggested in #4. Prior to that change, Drupal's
FormBuilderadded a CSRF token to all forms for authenticated users and added a cache max-age=0 to it, which bubbled up and prevented Dynamic Page Cache from caching the form.This is where it gets complicated. After the core change, Drupal only adds this max-age of 0 on the form token if the form array didn't already have any max-age set. Additionally, core sets a max-age=0 to all entity forms. So why is this closed form message (for the user that already submitted the form) being cached by Dynamic Page Cache still?
This happens because
WebformSubmissionForm::form()has this code:A "custom form" (the form closed message) is returned immediately and the parent form method is never invoked (which is where core would force max-age=0 on the form array as it does for all entity forms). Later, when core goes to add the CSRF token to the form, it also doesn't set max-age=0 on it because the form already has max-age=-1 set (this is due to webform adding the Webform entity as a cacheable dependency to the form array and all config entities have a max-age=-1).
In other words, the core change introduced in D11 suddenly started making cacheable the submission forms that don't return the normal form (e.g., they display a closed message instead of the form elements). This wouldn't normally be a problem if the proper cache contexts were also set on the form, but Webform was missing them.
The fix committed in this issue does address the problem presented by adding cache contexts for the user (note there may be other cache contexts missing, that's not clear yet) to the form, but it doesn't do it completely right. It's only adding the needed cache contexts for webform submission forms loaded via the
WebformEntityController::addFormmethod. Forms shown via a block don't use this code path. So the fix should be implemented instead in theWebformSubmissionFormclass instead.Since this issue was marked closed as fixed, I'll create a follow up for it.
Comment #16
bkosborneFollow up #3561066: Set max-age:0 for authenticated users to exclude Webforms from Dynamic page cache created
Comment #17
liam morlandThis was committed in 3f9db01.