Skip to content
Snippets Groups Projects
Commit dc17dbce authored by Kristiaan Van den Eynde's avatar Kristiaan Van den Eynde
Browse files

Issue #3322914 by kristiaanvandeneynde: Copy core's external cache...

Issue #3322914 by kristiaanvandeneynde: Copy core's external cache invalidation strategy for anonymous accounts
parent c74bbe2d
Branches
Tags
1 merge request!231Draft: #3519311 Provide a canonical link to entity using a Views field
......@@ -62,6 +62,11 @@ services:
arguments: ['@config.factory']
tags:
- { name: 'event_subscriber' }
group.anonymous_user_response_subscriber:
class: 'Drupal\group\EventSubscriber\AnonymousUserResponseSubscriber'
arguments: ['@current_user', '@group_permission.calculator']
tags:
- { name: 'event_subscriber' }
group.latest_revision.route_subscriber:
class: 'Drupal\group\Entity\Routing\GroupLatestRevisionRouteSubscriber'
tags:
......
<?php
namespace Drupal\group\EventSubscriber;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\group\Access\GroupPermissionCalculatorInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Response subscriber to handle finished responses for the anonymous user.
*/
class AnonymousUserResponseSubscriber implements EventSubscriberInterface {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The group permission calculator.
*
* @var \Drupal\group\Access\GroupPermissionCalculatorInterface
*/
protected $groupPermissionCalculator;
/**
* Constructs an AnonymousUserResponseSubscriber object.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\group\Access\GroupPermissionCalculatorInterface $permission_calculator
* The group permission calculator.
*/
public function __construct(AccountInterface $current_user, GroupPermissionCalculatorInterface $permission_calculator) {
$this->currentUser = $current_user;
$this->groupPermissionCalculator = $permission_calculator;
}
/**
* Adds a cache tag if the 'user.permissions' cache context is present.
*
* @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
* The event to process.
*/
public function onRespond(ResponseEvent $event) {
if (!$event->isMainRequest()) {
return;
}
if (!$this->currentUser->isAnonymous()) {
return;
}
$response = $event->getResponse();
if (!$response instanceof CacheableResponseInterface) {
return;
}
// The 'user.group_permissions' cache context ensures that if the group
// permissions for a user are modified, users are not served stale render
// cache content. But, when entire responses are cached in reverse proxies,
// the value for the cache context is never calculated, causing the stale
// response to not be invalidated. Therefore, when varying by permissions
// and the current user is the anonymous user, also add the cache tags for
// whatever was used to calculate the 'anonymous' group permissions.
if (in_array('user.group_permissions', $response->getCacheableMetadata()->getCacheContexts())) {
$anonymous_permissions = $this->groupPermissionCalculator->calculateFullPermissions($this->currentUser);
$response->addCacheableDependency($anonymous_permissions);
}
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
public static function getSubscribedEvents() {
// Priority 5, so that it runs before FinishResponseSubscriber, but after
// event subscribers that add the associated cacheability metadata (which
// have priority 10). This one is conditional, so must run after those.
$events[KernelEvents::RESPONSE][] = ['onRespond', 5];
return $events;
}
}
<?php
namespace Drupal\Tests\group\Functional;
use Drupal\group\PermissionScopeInterface;
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\user\RoleInterface;
/**
* tests the page cache in conjunction with Group-specific features.
*
* @group group
*/
class PageCacheTest extends GroupBrowserTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* Group.
*
* @var \Drupal\group\Entity\GroupInterface
*/
protected $group;
/**
* Group type.
*
* @var \Drupal\group\Entity\GroupTypeInterface
*/
protected $groupType;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogout();
$this->groupType = $this->createGroupType(['creator_membership' => FALSE]);
$this->group = $this->createGroup(['type' => $this->groupType->id()]);
$outsider_base = [
'group_type' => $this->groupType->id(),
'scope' => PermissionScopeInterface::OUTSIDER_ID,
'permissions' => ['view group'],
];
$this->createGroupRole(['global_role' => RoleInterface::ANONYMOUS_ID] + $outsider_base);
$this->createGroupRole(['global_role' => RoleInterface::AUTHENTICATED_ID] + $outsider_base);
}
/**
* Tests the automatic presence of the anonymous user's group cache tags.
*
* @see \Drupal\group\EventSubscriber\AnonymousUserResponseSubscriber
*/
public function testPageCacheAnonymousGroupPermissions() {
$config = $this->config('system.performance');
$config->set('cache.page.max_age', 300);
$config->save();
$allowed_url = $this->group->toUrl();
$forbidden_url = $this->group->toUrl('edit-form');
// Get cache tags associated with the anonymous user's group permissions.
/** @var \Drupal\group\Access\GroupPermissionCalculatorInterface $group_permission_calculator */
$group_permission_calculator = \Drupal::service('group_permission.calculator');
$anonymous_permissions = $group_permission_calculator->calculateFullPermissions(\Drupal::currentUser());
$anonymous_cache_tags = $anonymous_permissions->getCacheTags();
// 1. anonymous user, without permission.
$this->drupalGet($forbidden_url);
$this->assertSession()->statusCodeEquals(403);
$this->assertCacheContext('user.group_permissions');
foreach ($anonymous_cache_tags as $anonymous_cache_tag) {
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', $anonymous_cache_tag);
}
// 2. anonymous user, with permission.
$this->drupalGet($allowed_url);
$this->assertSession()->statusCodeEquals(200);
$this->assertCacheContext('user.group_permissions');
foreach ($anonymous_cache_tags as $anonymous_cache_tag) {
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', $anonymous_cache_tag);
}
// Log in as any user.
$auth_user = $this->drupalCreateUser();
$this->drupalLogin($auth_user);
// 3. authenticated user, without permission.
$this->drupalGet($forbidden_url);
$this->assertSession()->statusCodeEquals(403);
$this->assertCacheContext('user.group_permissions');
foreach ($anonymous_cache_tags as $anonymous_cache_tag) {
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', $anonymous_cache_tag);
}
// 4. authenticated user, with permission.
$this->drupalGet($allowed_url);
$this->assertSession()->statusCodeEquals(200);
$this->assertCacheContext('user.group_permissions');
foreach ($anonymous_cache_tags as $anonymous_cache_tag) {
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', $anonymous_cache_tag);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment