Problem/Motivation
Quoting @dawehner from #2263569-244: Bypass form caching by default for forms using #ajax.:
To be clear, using the master request is almost always semantically the wrong thing to do
We currently use the master request to populate $form['#action'] in \Drupal\Core\Form\FormBuilder::buildFormAction().
Using getCurrentRequest() caused test failures in \Drupal\system\Tests\Form\RedirectTest, but continuing to use the master request with <current> caused failures in \Drupal\system\Tests\System\AccessDeniedTest
This is due to conflicting use cases covered by RedirectTest and AccessDeniedTest. The latter asserts that people are sent to the original destination after a login while the former tests that it is possible for a form to set a redirect destination (even on exception pages).
The problem is that in HEAD the destination query parameter is used to pass the original location into the subrequest where the exception is rendered. However, the destination parameter always overrides any redirect set during form building. Usage of the master request in the FormBuilder does not really resolve that conflict but actually hides it.
Proposed resolution
Do not use the destination parameter in order to pass the original location to the subrequest but rather a deticated one (_exception_location).
Remaining tasks
Review.
User interface changes
N/A
API changes
TBD
Beta phase evaluation
| Issue category | Bug |
|---|---|
| Issue priority | Normal |
| Comment | File | Size | Author |
|---|
Issue fork drupal-2505339
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 #1
dawehnerIt's especially funny because both tests do something related to forms on access denied pages, weird.
Comment #2
effulgentsia commented#2263569: Bypass form caching by default for forms using #ajax. is in.
Comment #3
znerol commentedI very much agree that
getMasterRequest()is not correct there. Let's see what breaks.Comment #5
znerol commentedThe problem is that 403/404 is rendered in a subrequest and the URI includes the path of the main request in the
destinationquery parameter (why?). If that ends up in the form action, then that will win over the redirect set byRedirectBlockForm::submitForm().Comment #7
znerol commentedSpecial case exception subscriber subrequest. Maybe this needs to be configurable in some way? The destination is useful for login forms on 403 pages. I feel it is bad for most other things, like, e.g. a search field on the 404 one.
Comment #9
znerol commentedThe redirection behavior after form submissions from exception pages should be left up to the implementing form. The login form already special cases that case in
HEAD, presumably in order to fix problems when a 403 is rendered as a result of aPOSTrequest. In that case thedestinationparameter is missing from the URL (its added to POST parameters by the exception handler).In this patch:
destinationin the html exception handler but instead_exception_locationin order to prevent an automatic redirect.Comment #10
dawehnerIt would be great to get rid of that hack indeed! Thank you znerol
Can we add a comment somewhere explaining why setting destination itself doesn't work?
It would be nice if we could introduce a constant for _exception_location
Comment #12
znerol commentedComment #13
znerol commentedAddresses #10.
Comment #14
znerol commentedAlso fix semantics in ExternalFormUrlTest.
Comment #17
znerol commenteduhm.
Comment #19
znerol commentedComment #20
znerol commentedReroll.
Comment #21
znerol commentedComment #22
znerol commentedComment #23
kentr commentedPatch in #20 failed for me on
8.0.2.Reject files are attached (renamed to
*_.rej_.txt).Comment #24
kentr commentedRerolled the patch from #20.
Comment #25
wim leersPatch is looking good overall.
s/URL query/Request/
s/successfull/successful/
This definitely makes more sense.
s/uri/URI/
Comment #29
berdirTrying to reroll this, would be useful for better cache performance of the login block (#2867703: Change #action in UserLoginBlock to a #lazy_builder to avoid the route cache context , #2847972: Missing context on UserLoginBlock causes render-cache poisoning of the "destination").
As far as I see, #2595695: 4xx handling using subrequests: no longer able to vary by URL basically already solved a lot of what we are trying todo here, except it kept the default destination argument, and just added the _exception_statuscode.
Trying to go back to a minimal patch, then we can see from there if we still need to do something more.
Also combining it with the patch from #2867703: Change #action in UserLoginBlock to a #lazy_builder to avoid the route cache context to see if that works too.
Comment #31
berdirSo this is passing (yay), but it doesn't fix the user login form test.
Checked that test and the problem is that we explicitly need to force the destination even on non 403 pages, so this won't help with that. That means we definitely need a custom lazy builder there, fair enough.
But it also means that this seems to be good to go like this?
Comment #32
berdirIt also didn't fix it because the user login form patch was completely bogus and didn't do what it should have done. But it wouldn't have done it even if it would have been correct.
Comment #36
amateescu commentedSince #2867703: Change #action in UserLoginBlock to a #lazy_builder to avoid the route cache context is in, the non-combined patch from #29 should be all that's left to do here. Let's try it.
Comment #37
amateescu commentedNice, this should be good to go based on #31.
Comment #38
andypostI bet it needs test coverage
Comment #39
amateescu commentedI'm not sure we need any additional test coverage, #29/#31 implies that we don't.
Comment #40
wim leersIf this broke something, we'd have a zillion tests failing. 😄
I agree with this not needing test coverage.
Comment #41
andypostI think this is better fix cos
- it documents what actually expected
- this method mostly empty but using external service dependency
Actually this method require only request object to but actually supposed to hide implementation details:
- build URI from request removing "ajax implementation nits" - which has follow-up to be removed #2504709: Prevent _wrapper_format and ajax_form parameters from bleeding through to generated URLs
- checking for
//(guessing the request stack can contain weird request object) - maybe it should be cleaned in follow-upAs method protected it needs unit test coverage and probably could be removed (BC?)
https://www.drupal.org/core/d8-bc-policy#protected-methods allows to change argument
Comment #42
andypostHere's a test
Comment #44
amateescu commentedThis approach looks fine as well, thanks for the test :)
Comment #45
alexpottI don't see the benefit of the changes to Formbuilder in #41 over #36. Changing the method signature is not necessary to do the fix and is unnecessarily disruptive. If there are compelling reasons down the line to make it the way it is in #41 then we should do this then. +1 for the new test though.
Comment #46
alexpottI also think that this change has caused some disruption to core forms in the past we should have a CR and only commit to the development branch and have some idea if major contrib is affected.
Comment #47
andypost@alexpott why chamging protected method you find disruptive?
Comment #48
alexpott@andypost because if someone has extended the FormBuilder class this change might break them. And whilst this is not public API we should still seek to minimise breaking change unless it is truly necessary. I don't think it is in this issue. Maybe a future issue it will be but we should justify that on that issue and not second guess ourselves here.
Comment #49
jibranIn which case someone would extend
FormBuilderclass? Can we have an example, please?Comment #50
amateescu commentedLet's not make perfect the enemy of good, it's really not worth it for such a small @todo cleanup :)
This patch contains the fix from #36 with the test from #42.
Comment #51
alexpottOkay. So now looking at the history of this issue - what's the chance of this breaking contrib in the same way the core has been broken over the years. Like what happens if contrib has something where it would need to do the corollary of #2867703: Change #action in UserLoginBlock to a #lazy_builder to avoid the route cache context . At the very least we need a change record that details the implications for contrib - it would also be good to know what might break - especially if popular contrib is affected.
Comment #52
dawehnerI'm a bit dubious about this test coverage. For me this doesn't really prove anything, beside the code being implemented as it is. Is there a way to test more something which is described in the issue summary?
Comment #55
andypostComment #59
heddnRan into this today. We've got inbound/outbound path rewriting going on and Forms set the action to the wrong URL. I believe that using the
<current>route would fix things for us.I think the URL should be generated the same way it is generated in
RedirectDestination::get(). i.e.Comment #60
heddnNo interdiff as the tests didn't change and the approach used in the form builder is entirely different.
Comment #61
heddnNo interdiff as the tests didn't change and the approach used in the form builder is entirely different.
Comment #64
andypostComment #66
andypostBtw #50 patch looks enough
Comment #67
ravi.shankar commentedAdded reroll of patch #50 on Drupal core 9.5.x. as patch #50 was not applying. I think it can be reviewed if It passes the test as per comment 366.
Comment #70
andypoststill needs CR and MR for 11.x
Comment #72
andypostNeeds only CR now
Comment #73
andypostAdded CR draft which needs better wording
Comment #74
smustgrave commentedSmall change requests
Comment #76
andypostfixed
Comment #77
bramdriesenCode wise this looks good! All tests are passing 👍
Also updated the readability of the CR a little bit. Not really sure if the CR needs more info or not.
I think we can set this to RTBC now.
Comment #78
longwaveI still don't think @alexpott or @dawehner's comments from #51 and #52 have been addressed. The CR doesn't really explain why you might be affected by this nor what you should do if you are, and the test coverage is very specific to the fix; it doesn't really prove anything about the original problem.
Comment #79
joachim commentedThis is blocking #3566881: Add a submitForm() method to HttpKernelUiHelperTrait, because in a kernel test, the main request is the request from PHPUnit whose path is always '/'.
Should this be escalated to a bug in light of this?