Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve CanView handling for SiteTree records #2

Merged
merged 2 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions _config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ SilverStripe\Control\Controller:
extensions:
- 'NSWDPC\Utilities\Cache\CacheStateModificationExtension'
---
Name: nswdpc-cache-headers-contentcontroller
---
SilverStripe\CMS\Controllers\ContentController:
extensions:
- 'NSWDPC\Utilities\Cache\ContentControllerExtension'
---
Name: nswdpc-cache-headers
---
NSWDPC\Utilities\Cache\CacheHeaderConfiguration:
Expand All @@ -30,6 +36,7 @@ NSWDPC\Utilities\Cache\CacheHeaderConfiguration:
- 'SilverStripe\Dev\DevConfigController'
- 'SilverStripe\Dev\SapphireInfo'
- 'SilverStripe\Dev\InstallerTest'
- 'SilverStripe\GraphQL\Controller'
- 'SilverStripe\ORM\DatabaseAdmin'
- 'SilverStripe\Security\Security'
- 'SilverStripe\ShareDraftContent\Controllers\ShareDraftController'
Expand Down
58 changes: 58 additions & 0 deletions src/Extensions/ContentControllerExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
namespace NSWDPC\Utilities\Cache;

use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Core\Extension;
use SilverStripe\Control\Middleware\HTTPCacheControlMiddleware;
use SilverStripe\Security\InheritedPermissions;
use SilverStripe\Versioned\Versioned;

/**
* Extension applied to ContentController
*/
class ContentControllerExtension extends Extension {

/**
* Handle actions after controller init
*/
public function onAfterInit() {
// When on the live stage, determine if the page has restrictions
$stage = Versioned::get_stage();
if($stage == Versioned::LIVE) {
$record = $this->owner->data();
if($record && ($record instanceof SiteTree) && !$this->hasAnyoneViewPermission($record)) {
// If the page can't be viewed, disable cache
HTTPCacheControlMiddleware::singleton()->disableCache(true)->useAppliedState();
}
}
}

/**
* Return whether a SiteTree record has CanViewType of ANYONE
* @return bool
*/
private function hasAnyoneViewPermission(SiteTree $record) : bool {

$siteConfig = $record->getSiteConfig();
if($siteConfig->CanViewType !== InheritedPermissions::ANYONE) {
return false;
}

if($record->CanViewType === InheritedPermissions::ANYONE) {
// this record sets permissions
return true;
} else if ($record->CanViewType === InheritedPermissions::INHERIT) {
// inherit permissions from parent
$parent = $record->Parent();
if(!empty($parent->ID)) {
// find permissions from parent
return $this->hasAnyoneViewPermission($parent);
} else if($siteConfig->CanViewType === InheritedPermissions::ANYONE) {
// SiteConfig sets 'Anyone' permission
return true;
}
}
return false;
}

}
115 changes: 114 additions & 1 deletion tests/CacheHeaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use SilverStripe\Dev\TestOnly;
use SilverStripe\Versioned\Versioned;
use SilverStripe\View\SSViewer;
use SilverStripe\SiteConfig\SiteConfig;
use SilverStripe\Security\InheritedPermissions;
use Page;

class CacheHeaderTest extends FunctionalTest {
Expand Down Expand Up @@ -55,11 +57,22 @@ protected function setUp()
CacheHeaderConfiguration::config()->set('s_max_age', 7401);
CacheHeaderConfiguration::config()->set('must_revalidate', true);

// initiall request without session
$this->setSiteConfigCanViewType( InheritedPermissions::ANYONE );

// intial request without session
$this->logOut();

}

/**
* Set site config CanViewType
*/
private function setSiteConfigCanViewType(string $type) {
$siteConfig = SiteConfig::current_site_config();
$siteConfig->CanViewType = $type;
$siteConfig->write();
}

public function testCacheHeaders() {

// page has form, this should disableCache
Expand Down Expand Up @@ -146,6 +159,106 @@ public function testCacheHeadersWithFormCacheLoggedIn() {

}

/**
* Tests based on CanViewType
*/
public function testCanViewRootPage() {
// Inherit (root page)
$response = $this->get("/header-test/?noform=1");
$headers = $response->getHeaders();
$this->assertTrue(!empty($headers['cache-control']), "Page 1 - no cache-control header in response");
$parts = $this->getCacheControlParts($headers['cache-control']);
$this->assertTrue( $this->hasCacheDirective($parts, "public"), "Page 1 - Header {$headers['cache-control']} missing public" );

}

public function testCanViewSubPageAnyone() {
// Anyone page under root should be public
$response = $this->get("/header-test/sub-page-header-test/?noform=1");
$headers = $response->getHeaders();
$this->assertTrue(!empty($headers['cache-control']), "Page 2 - no cache-control header in response");
$parts = $this->getCacheControlParts($headers['cache-control']);
$this->assertTrue( $this->hasCacheDirective($parts, "public"), "Page 2 - Header {$headers['cache-control']} missing public" );
}

public function testCanViewSubPageOnlyTheseUsers() {
// OnlyTheseUsers should not be public
$response = $this->get("/header-test/restricted-page-header-test/?noform=1");
$headers = $response->getHeaders();
$this->assertTrue(!empty($headers['cache-control']), "Page 3 - no cache-control header in response");
$parts = $this->getCacheControlParts($headers['cache-control']);
$this->assertTrue( $this->hasCacheDirective($parts, "no-cache"), "Page 3 - Header {$headers['cache-control']} missing no-cache" );
$this->assertTrue( $this->hasCacheDirective($parts, "no-store"), "Page 3 - Header {$headers['cache-control']} missing no-store" );
$this->assertTrue( $this->hasCacheDirective($parts, "must-revalidate"), "Page 3 - Header {$headers['cache-control']} missing must-revalidate" );
}

public function testCanViewSubPageLoggedInUsers() {
// LoggedInUsers page should not be public
$response = $this->get("/header-test/loggedin-page-header-test/?noform=1");
$headers = $response->getHeaders();
$this->assertTrue(!empty($headers['cache-control']), "Page 4 - no cache-control header in response");
$parts = $this->getCacheControlParts($headers['cache-control']);
$this->assertTrue( $this->hasCacheDirective($parts, "no-cache"), "Page 4 - Header {$headers['cache-control']} missing no-cache" );
$this->assertTrue( $this->hasCacheDirective($parts, "no-store"), "Page 4 - Header {$headers['cache-control']} missing no-store" );
$this->assertTrue( $this->hasCacheDirective($parts, "must-revalidate"), "Page 4 - Header {$headers['cache-control']} missing must-revalidate" );
}

public function testCanViewSubPageInheritedAnyone() {
// Inherited from parent page with Anyone - should bge public
$response = $this->get("/header-test/sub-page-header-test/inherited-anyone-page-header-test/?noform=1");
$headers = $response->getHeaders();
$this->assertTrue(!empty($headers['cache-control']), "Page 5 - no cache-control header in response");
$parts = $this->getCacheControlParts($headers['cache-control']);
$this->assertTrue( $this->hasCacheDirective($parts, "public"), "Page 5 - Header {$headers['cache-control']} missing public" );
}

public function testCanViewSubPageInheritedOnlyTheseUsers() {
// Inherited from parent page with OnlyTheseUsers set - not public
$response = $this->get("/header-test/restricted-page-header-test/inherited-restricted-page-header-test/?noform=1");
$headers = $response->getHeaders();
$this->assertTrue(!empty($headers['cache-control']), "Page 6 - no cache-control header in response");
$parts = $this->getCacheControlParts($headers['cache-control']);
$this->assertTrue( $this->hasCacheDirective($parts, "no-cache"), "Page 6 - Header {$headers['cache-control']} missing no-cache" );
$this->assertTrue( $this->hasCacheDirective($parts, "no-store"), "Page 6 - Header {$headers['cache-control']} missing no-store" );
$this->assertTrue( $this->hasCacheDirective($parts, "must-revalidate"), "Page 6 - Header {$headers['cache-control']} missing must-revalidate" );
}

/**
* Restrict SiteConfig to Logged In Users and test root page (Inherit)
*/
public function testCanViewRestrictedRootPage() {
$this->setSiteConfigCanViewType( InheritedPermissions::LOGGED_IN_USERS );
$response = $this->get("/parentless-test/?noform=1");
$headers = $response->getHeaders();
$this->assertTrue(!empty($headers['cache-control']), "Page 7 - no cache-control header in response");
$parts = $this->getCacheControlParts($headers['cache-control']);
$this->assertTrue( $this->hasCacheDirective($parts, "no-cache"), "Page 7 - Header {$headers['cache-control']} missing no-cache" );
$this->assertTrue( $this->hasCacheDirective($parts, "no-store"), "Page 7 - Header {$headers['cache-control']} missing no-store" );
$this->assertTrue( $this->hasCacheDirective($parts, "must-revalidate"), "Page 7 - Header {$headers['cache-control']} missing must-revalidate" );
$this->setSiteConfigCanViewType( InheritedPermissions::ANYONE );
}

/**
* Sub Page anyone, but restricted site config - not public
*/
public function testCanViewRestrictedSubPageAnyone() {
$this->setSiteConfigCanViewType( InheritedPermissions::LOGGED_IN_USERS );
$response = $this->get("/header-test/sub-page-header-test/?noform=1");
$headers = $response->getHeaders();
$this->assertTrue(!empty($headers['cache-control']), "Page 2 - no cache-control header in response");
$parts = $this->getCacheControlParts($headers['cache-control']);
$this->assertTrue( $this->hasCacheDirective($parts, "no-cache"), "Page 2 - Header {$headers['cache-control']} missing no-cache" );
$this->assertTrue( $this->hasCacheDirective($parts, "no-store"), "Page 2 - Header {$headers['cache-control']} missing no-store" );
$this->assertTrue( $this->hasCacheDirective($parts, "must-revalidate"), "Page 2 - Header {$headers['cache-control']} missing must-revalidate" );
$this->setSiteConfigCanViewType( InheritedPermissions::ANYONE );
}

private function getCacheControlParts($header) {
$parts = explode("," , $header);
$parts = array_map("trim", $parts);
return $parts;
}

private function hasCachingState(array $header, $state) {
return array_search($state, $header) !== false;
}
Expand Down
39 changes: 39 additions & 0 deletions tests/CacheHeaderTest.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,43 @@
NSWDPC\Utilities\Cache\Tests\CacheHeaderTestPage:
root_page:
ID: 1
Title: 'Header Test'
URLSegment: 'header-test'
CanViewType: 'Inherit'
ParentID: 0
sub_page:
ID: 2
Title: 'Sub page header test'
URLSegment: 'sub-page-header-test'
CanViewType: 'Anyone'
ParentID: 1
restricted_sub_page:
ID: 3
Title: 'Restricted page header test'
URLSegment: 'restricted-page-header-test'
CanViewType: 'OnlyTheseUsers'
ParentID: 1
loggedin_sub_page:
ID: 4
Title: 'Logged in page header test'
URLSegment: 'loggedin-page-header-test'
CanViewType: 'LoggedInUsers'
ParentID: 1
inherit_anyone_sub_page:
ID: 5
Title: 'Inherited anyone in page header test'
URLSegment: 'inherited-anyone-page-header-test'
CanViewType: 'Inherit'
ParentID: 2
inherit_restricted_sub_page:
ID: 6
Title: 'Inherited restricted in page header test'
URLSegment: 'inherited-restricted-page-header-test'
CanViewType: 'Inherit'
ParentID: 3
parentless_page:
ID: 7
Title: 'Parentless Test'
URLSegment: 'parentless-test'
CanViewType: 'Inherit'
ParentID: 0