Split off from


Quoting @catch in #606840-39, to capture what still needs to be done:

with contact module I don't think the problem is that flood control is incompatible with page caching, instead it's that flood control needs to be handled in validate/submit, not when viewing forms.


Quoting #606840-26: Enable internal page cache by default, to capture what this patch does and why:

The failures there are because a 403 is expected, but a 200 is given. The 403 is expected because a permission is revoked from the anonymous users role.

The requested URL corresponds to the entity.user.contact_form route. That route's access checking uses the ContactPageAccess access check. The cache tags associated with that route-level access result must be reflected by the cache tags on the eventual response — otherwise the page cache item for that response will not be invalidated when the cache tags associated with the access result are invalidated.
This is a problem we knew we were going to have to solve at this point, and I think it makes sense to solve it here :)

Looking at the other two tests in the Contact module that are failing, similar problems exist. The 4 other failing tests in Contact module are due to flood control not working. That's another bug this issue has uncovered: flood control does *not* work for anonymous users when the page cache is on. The solution is simple: if flood control is active, then don't cache the page. Ideally, we'd communicate this with #cache['max-age'] === 0. But until #2443073: Add #cache[max-age] to disable caching and bubble the max-age lands, we'll have to use the page cache kill switch.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

Issue summary: View changes
Status: Active » Needs review
FileSize
15.59 KB

First: rerolling the original patch, which didn't fix Contact module's problematic flood control implementation.

Wim Leers’s picture

+++ b/core/lib/Drupal/Core/Access/AccessResult.php
@@ -339,7 +339,8 @@ public function setCacheMaxAge($max_age) {
-    $this->addCacheContexts(array('user.roles'));
+    $this->addCacheContexts(['user.roles'])
+      ->addCacheTags(['config:user_role_list']);

This change would not be necessary if we choose to do #2428703: Add a 'user.permissions' cache context (was: "Should cache contexts be able to associate a cache tag?").

Wim Leers’s picture

FileSize
15.6 KB
1013 bytes

The last submitted patch, 1: 2453341-1.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 3: 2453341-3.patch, failed testing.

Wim Leers’s picture

Next, do #606840-39: Enable internal page cache by default, as described in the IS.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
18.24 KB
4.52 KB

Fixed the test failures.

And removed the hunk in CommentForm that snuck in, @moshe pointed that out in chat.

Now I'll do what I said in #6.

effulgentsia’s picture

Status: Needs review » Needs work

"Needs work" per last sentence in #7.

Wim Leers’s picture

Title: Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view » [PP-1] Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view
Wim Leers’s picture

Status: Needs work » Needs review
FileSize
25.75 KB
9.04 KB

Did #6: flood control is now performed when validating the message form, instead of when viewing the form.

dawehner’s picture

Looks pretty reasonable.

Status: Needs review » Needs work

The last submitted patch, 10: 2453341-10.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
25.55 KB

Chasing HEAD.

#11: Thanks for the review!

Wim Leers’s picture

Assigned: Unassigned » Wim Leers

I'll continue with this in the morning.

Wim Leers’s picture

Title: [PP-1] Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view » Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view
Assigned: Wim Leers » Unassigned
FileSize
29.46 KB
10.49 KB

Per #2428703-11: Add a 'user.permissions' cache context (was: "Should cache contexts be able to associate a cache tag?"), looks like that's going to be stuck on discussion for a while, we should try to continue here in the direction that catch prefers: make AccessResult responsible for adding the necessary role cache tags. Then if we still do #2428703: Add a 'user.permissions' cache context (was: "Should cache contexts be able to associate a cache tag?"), it can be cleaned up there.

Status: Needs review » Needs work

The last submitted patch, 15: 2453341-15.patch, failed testing.

Wim Leers’s picture

FileSize
36.88 KB
7.53 KB

Fixed those failures. Had to introduce a mocked container in 4 more PHPUnit tests. Ugly, but the only way to do it until #2428703: Add a 'user.permissions' cache context (was: "Should cache contexts be able to associate a cache tag?") lands.

Wim Leers’s picture

I just moved the route access result hunks from this patch into #2458349: Route's access result's cacheability not applied to the response's cacheability, so we can get that in separately.

Wim Leers’s picture

Status: Needs work » Needs review
larowlan’s picture

  1. +++ b/core/lib/Drupal/Core/Access/AccessResult.php
    @@ -339,7 +339,14 @@ public function setCacheMaxAge($max_age) {
    +      ->addCacheTags(Cache::buildTags('config:user.role', \Drupal::currentUser()->getRoles(), '.'));
    

    What happens if the AccessResult doesn't relate to the current logged in user? Isn't there places where we can formulate an AccessResult based on another user? e.g. anywhere we do an access check we can pass an optional account, with default falling back to the logged in user. If so, that would mean that a) this isn't correct and b) the user should be passed along to the access result and we don't need the call out to the container? Happy to be wrong on this.

  2. +++ b/core/lib/Drupal/Core/Routing/AccessAwareRouterInterface.php
    @@ -16,6 +16,11 @@
    +   * Attribute name of the access result for the request..
    

    nit: two .

  3. +++ b/core/tests/Drupal/Tests/Core/Entity/EntityAccessCheckTest.php
    @@ -22,6 +23,27 @@
    +  protected function setUp() {
    
    +++ b/core/tests/Drupal/Tests/Core/Route/RoleAccessCheckTest.php
    @@ -22,6 +23,27 @@
    +  protected function setUp() {
    

    Could we move the duplicated code to a trait and then we only need to add a use statement, with the only change to the ::setUp being a call to the trait method? Saves the duplication.

Wim Leers’s picture

Title: Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view » [PP-1] Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view
Status: Needs review » Postponed

Thank you very much for the review!

  1. Magnificent catch. You're absolutely right. Very very glad that this will allow me to remove the container dependency.
  2. Will fix.
  3. Point 1 will mean that this can go away.

Sadly, point 1 means that the signature of ::cachePerRole() would have to change. That means changing all ::cachePerRole() callers.

But #2428703: Add a 'user.permissions' cache context (was: "Should cache contexts be able to associate a cache tag?") is moving forward again, in a promising direction, since #16. The changes there would completely overlap with this. And the changes there would actually remove the need for many changes in this issue. Therefore, sadly, postponing this issue on that one.

Wim Leers’s picture

Title: [PP-1] Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view » Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view
Status: Postponed » Needs work

#2428703: Add a 'user.permissions' cache context (was: "Should cache contexts be able to associate a cache tag?") landed, but went in a different direction. Yet it still paved the way for this issue :)

Wim Leers’s picture

FileSize
20.67 KB
14.8 KB

I said in #21:

[…] the changes there would actually remove the need for many changes in this issue.

And my prediction was right. I don't need to fix #20.1 and #20.3, because those hunks are now completely gone :)

This patch is now basically half the size of the previous patch. Note that the interdiff doesn't show everything because some changes were actually part of the rebase conflict.

Wim Leers’s picture

Status: Needs work » Needs review

Such noob.

Wim Leers’s picture

Title: Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view » [PP-1] Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view
Status: Needs review » Postponed

Besides, this is actually blocked on #2458349: Route's access result's cacheability not applied to the response's cacheability, which handles about half of this patch.

Wim Leers’s picture

FileSize
13.85 KB

Rerolled on top of #2458349: Route's access result's cacheability not applied to the response's cacheability.

Please keep this postponed until the other issue lands; after it lands, we can mark this issue as NR and it'll be tested.

andypost’s picture

+++ b/core/modules/contact/src/Controller/ContactController.php
@@ -74,10 +33,6 @@ public static function create(ContainerInterface $container) {
   public function contactSitePage(ContactFormInterface $contact_form = NULL) {
-    // Check if flood control has been activated for sending emails.
-    if (!$this->currentUser()->hasPermission('administer contact forms')) {
-      $this->contactFloodControl();

@@ -141,24 +92,8 @@ public function contactPersonalPage(UserInterface $user) {
-  protected function contactFloodControl() {
...
-    if (!$this->flood->isAllowed('contact', $limit, $interval)) {
...
-      throw new AccessDeniedHttpException();

So maybe better to move that check to access handler, we should not show the form and try to serve POST requests for "flood"

Wim Leers’s picture

#27: catch told me to it this way:

with contact module I don't think the problem is that flood control is incompatible with page caching, instead it's that flood control needs to be handled in validate/submit, not when viewing forms.

See #606840-39: Enable internal page cache by default.

Fabianx’s picture

I agree with #28, the validation message comes when trying to post more not when going to the site.

The code changes for the contact flood changes look good to me.

Wim Leers’s picture

Status: Postponed » Needs review

Status: Needs review » Needs work

The last submitted patch, 26: contact_forms_cacheability_and_flood-2453341-26.patch, failed testing.

Wim Leers’s picture

Title: [PP-1] Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view » Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view
Status: Needs work » Needs review
FileSize
13.66 KB

The PHP7 String changes that were committed lately caused conflicts. Rerolled.

larowlan’s picture

+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -167,7 +167,7 @@ public function onRespond(FilterResponseEvent $event) {
+    if (strlen($response->headers->get('X-Drupal-Cache-Tags')) > 0) {

nitpick: any reason why strlen and not != ''?
Apart from that (or even including that) rtbc

Berdir’s picture

+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -167,7 +167,7 @@ public function onRespond(FilterResponseEvent $event) {
     $cache_tags = $cacheable_access_result->getCacheTags();
-    if ($response->headers->has('X-Drupal-Cache-Tags')) {
+    if (strlen($response->headers->get('X-Drupal-Cache-Tags')) > 0) {

That, or just switch has() to get(). That's exactly the difference between the two, has is isset(), if (get()) is basically an !empty(), it will return an empty string, that will be cast to FALSE and it won't go inside the if.

Looks fine to me as well. For the record, I'm pretty sure it would have worked if you just changed to submit in the tests because the form post would run through the uncached controller and should have triggered validation there, but this is cleaner and more reliable if we ever switch to using a separate route or so for form submissions.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
13.63 KB
1.53 KB

Applied the #34 suggested fix. RTBC per #33 & #34.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

Needs a reroll..

+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -167,7 +167,7 @@ public function onRespond(FilterResponseEvent $event) {
-    if ($response->headers->has('X-Drupal-Cache-Tags')) {
+    if ($response->headers->get('X-Drupal-Cache-Tags')) {

@@ -175,7 +175,7 @@ protected function updateDrupalCacheHeaders(Response $response, CacheableInterfa
-    if ($response->headers->has('X-Drupal-Cache-Contexts')) {
+    if ($response->headers->get('X-Drupal-Cache-Contexts')) {

I'm not particularly happy happy about the concept of http headers with no value. Something feels really off. Discussed with @Wim Leers in IRC. We decided that we'll open a followup to discuss how to prevent this.

Wim Leers’s picture

Follow-up filed: #2463697: X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags headers should not indicate emptiness by the empty string as header value.

Rerolled.

Thanks to #2453059: Set default render cache contexts: 'theme' + 'languages:' . LanguageInterface::TYPE_INTERFACE having landed in the mean time, in core there's never going to be the case that either of those headers are empty (at minimum, X-Drupal-Cache-Tags equals 'rendered', and X-Drupal-Cache-Contexts equals 'languages:language_interface theme').

So, we can omit those changes now, and discuss all of that in #2463697: X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags headers should not indicate emptiness by the empty string as header value.

Wim Leers’s picture

Oops, the rebase was wrong. The rebase interdiff even shows that.

Fabianx’s picture

RTBC + 1 to #38

Fabianx’s picture

Status: Reviewed & tested by the community » Needs review

I take that back, I don't think the rebase was wrong.

I think #38 misses parts of #35 now.

E.g. the flood and dateFormatter are with #35 removed from the ContactController, but in #38 those remain.

I am confused ...

The last submitted patch, 37: contact_forms_cacheability_and_flood-2453341-37.patch, failed testing.

Wim Leers’s picture

Well-spotted. #35 indeed removed far more, and the rebase in #37 lost many deletions. I got confused by what I saw, I saw the constructor still getting some interfaces, but those interfaces not being imported, so I "fixed" that in #38. So now all the intended deletions were no longer there. Now they're back :)

Fabianx’s picture

Status: Needs review » Reviewed & tested by the community

RTBC, this now looks great!

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

I gave some thought to the change of making flood control on the validate since this is a change to how users experience the site - i.e.they'll only get informed on submit instead of just visiting the page. I think this an acceptable price for a cacheable site so +1.

This issue addresses a major bug and is allowed per https://www.drupal.org/core/beta-changes. Committed eebf493 and pushed to 8.0.x. Thanks!

  • alexpott committed eebf493 on 8.0.x
    Issue #2453341 by Wim Leers: Contact forms don't have necessary cache...
Fabianx’s picture

Do we need a change record / doc update of some kind to inform other authors to ensure they use flood correctly?

effulgentsia’s picture

Title: Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view » [needs change record] Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view
Status: Fixed » Needs work
Issue tags: +Needs change record

Yes, I think so.

Wim Leers’s picture

Basically to warn/inform existing contrib modules about the correct usage of Flood?

effulgentsia’s picture

Right, to:
- inform site builders about the change to site behavior with respect to Contact module
- inform contrib modules that create their own flood-controlled forms about the new best practice
- inform modules that extend/alter Contact and/or other forms and might care about this (e.g., Mollom?)

Wim Leers’s picture

Title: [needs change record] Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view » Contact forms don't have necessary cache contexts & tags; flood control should work on validate, not on view
Issue tags: -Needs change record
andypost’s picture

Status: Needs work » Fixed

CR looks great

cosmicdreams’s picture

Does this need to be ported to Drupal 7?

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.