mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Add new InheritedPermissionFlusher extension, CacheFlusher service
This commit is contained in:
parent
71c80d3762
commit
eecb9f64d3
9
_config/extensions.yml
Normal file
9
_config/extensions.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
Name: coreextensions
|
||||||
|
---
|
||||||
|
SilverStripe\Security\Member:
|
||||||
|
extensions:
|
||||||
|
- SilverStripe\Security\InheritedPermissionFlusher
|
||||||
|
SilverStripe\Security\Group:
|
||||||
|
extensions:
|
||||||
|
- SilverStripe\Security\InheritedPermissionFlusher
|
16
src/Core/Cache/CacheFlusher.php
Normal file
16
src/Core/Cache/CacheFlusher.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Core\Cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a service that can flush its cache for a list of members
|
||||||
|
* @package SilverStripe\Core\Cache
|
||||||
|
*/
|
||||||
|
interface CacheFlusher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param null $ids
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function flushCache($ids = null);
|
||||||
|
}
|
@ -327,9 +327,16 @@ class Group extends DataObject
|
|||||||
$query->removeFilterOn('Group_Members');
|
$query->removeFilterOn('Group_Members');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now set all children groups as a new foreign key
|
// Now set all children groups as a new foreign key
|
||||||
$groups = Group::get()->byIDs($this->collateFamilyIDs());
|
$familyIDs = $this->collateFamilyIDs();
|
||||||
$result = $result->forForeignID($groups->column('ID'))->where($filter);
|
if (!empty($familyIDs)) {
|
||||||
|
$groups = Group::get()->byIDs($familyIDs);
|
||||||
|
$groupIDs = $groups->column('ID');
|
||||||
|
if (!empty($groupIDs)) {
|
||||||
|
$result = $result->forForeignID($groupIDs)->where($filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
104
src/Security/InheritedPermissionFlusher.php
Normal file
104
src/Security/InheritedPermissionFlusher.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Security;
|
||||||
|
|
||||||
|
use Psr\Log\InvalidArgumentException;
|
||||||
|
use SilverStripe\Core\Flushable;
|
||||||
|
use SilverStripe\ORM\DataExtension;
|
||||||
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\Core\Cache\CacheFlusher;
|
||||||
|
|
||||||
|
class InheritedPermissionFlusher extends DataExtension implements Flushable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var CacheFlusher[]
|
||||||
|
*/
|
||||||
|
protected $services = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush all CacheFlusher services
|
||||||
|
*/
|
||||||
|
public static function flush()
|
||||||
|
{
|
||||||
|
singleton(__CLASS__)->flushCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DataObject $owner
|
||||||
|
*/
|
||||||
|
public function setOwner($owner)
|
||||||
|
{
|
||||||
|
if (!$owner instanceof Member && !$owner instanceof Group) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'%s can only be applied to %s or %s',
|
||||||
|
__CLASS__,
|
||||||
|
Member::class,
|
||||||
|
Group::class
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::setOwner($owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CacheFlusher[]
|
||||||
|
*/
|
||||||
|
public function setServices($services)
|
||||||
|
{
|
||||||
|
foreach ($services as $service) {
|
||||||
|
if (!$service instanceof CacheFlusher) {
|
||||||
|
throw new InvalidArgumentException(sprintf(
|
||||||
|
'%s.services must contain only %s instances. %s provided.',
|
||||||
|
__CLASS__,
|
||||||
|
CacheFlusher::class,
|
||||||
|
get_class($service)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->services = $services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return CacheFlusher[]
|
||||||
|
*/
|
||||||
|
public function getServices()
|
||||||
|
{
|
||||||
|
return $this->services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes all registered CacheFlusher services
|
||||||
|
*/
|
||||||
|
public function flushCache()
|
||||||
|
{
|
||||||
|
$ids = $this->getMemberIDList();
|
||||||
|
foreach ($this->services as $service) {
|
||||||
|
$service->flushCache($ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of member IDs that need their permissions flushed
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function getMemberIDList()
|
||||||
|
{
|
||||||
|
if (!$this->owner) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->owner->exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->owner instanceof Group) {
|
||||||
|
return $this->owner->Members()->exists()
|
||||||
|
? $this->owner->Members()->column('ID')
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$this->owner->ID];
|
||||||
|
}
|
||||||
|
}
|
100
tests/php/Security/InheritedPermissionsFlusherTest.php
Normal file
100
tests/php/Security/InheritedPermissionsFlusherTest.php
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Security\Tests;
|
||||||
|
|
||||||
|
use Psr\SimpleCache\CacheInterface;
|
||||||
|
use SilverStripe\Core\Cache\CacheFactory;
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\Security\InheritedPermissionFlusher;
|
||||||
|
use SilverStripe\Security\Member;
|
||||||
|
use SilverStripe\Security\Group;
|
||||||
|
use SilverStripe\Core\Injector\Injector;
|
||||||
|
use SilverStripe\Security\Tests\InheritedPermissionsFlusherTest\TestCacheFlusher;
|
||||||
|
use SilverStripe\Core\Config\Config;
|
||||||
|
|
||||||
|
class InheritedPermissionsFlusherTest extends SapphireTest
|
||||||
|
{
|
||||||
|
protected static $fixture_file = 'InheritedPermissionsFlusherTest.yml';
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
// Set up a mock cache service
|
||||||
|
Injector::inst()->load([
|
||||||
|
CacheInterface::class . '.TestFlusherCache' => [
|
||||||
|
'factory' => CacheFactory::class,
|
||||||
|
'constructor' => ['namespace' => 'TestFlusherCache']
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMemberFlushesPermissions()
|
||||||
|
{
|
||||||
|
$cache = Injector::inst()->create(CacheInterface::class . '.TestFlusherCache');
|
||||||
|
$flusher = new TestCacheFlusher($cache);
|
||||||
|
$extension = new InheritedPermissionFlusher();
|
||||||
|
$extension->setServices([$flusher]);
|
||||||
|
Injector::inst()->registerService($extension, InheritedPermissionFlusher::class);
|
||||||
|
$editor = $this->objFromFixture(Member::class, 'editor');
|
||||||
|
$admin = $this->objFromFixture(Member::class, 'admin');
|
||||||
|
$editorKey = $flusher->generateCacheKey(TestCacheFlusher::$categories[0], $editor->ID);
|
||||||
|
$adminKey = $flusher->generateCacheKey(TestCacheFlusher::$categories[0], $admin->ID);
|
||||||
|
$cache->set($editorKey, 'uncle');
|
||||||
|
$cache->set($adminKey, 'cheese');
|
||||||
|
$editor->flushCache();
|
||||||
|
|
||||||
|
$this->assertNull($cache->get($editorKey));
|
||||||
|
$this->assertEquals('cheese', $cache->get($adminKey));
|
||||||
|
|
||||||
|
$admin->flushCache();
|
||||||
|
$this->assertNull($cache->get($editorKey));
|
||||||
|
$this->assertNull($cache->get($adminKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGroupFlushesPermissions()
|
||||||
|
{
|
||||||
|
$cache = Injector::inst()->create(CacheInterface::class . '.TestFlusherCache');
|
||||||
|
$flusher = new TestCacheFlusher($cache);
|
||||||
|
$extension = new InheritedPermissionFlusher();
|
||||||
|
$extension->setServices([$flusher]);
|
||||||
|
Injector::inst()->registerService($extension, InheritedPermissionFlusher::class);
|
||||||
|
$editors = $this->objFromFixture(Group::class, 'editors');
|
||||||
|
$admins = $this->objFromFixture(Group::class, 'admins');
|
||||||
|
|
||||||
|
// Populate the cache for all members in each group
|
||||||
|
foreach ($editors->Members() as $editor) {
|
||||||
|
$editorKey = $flusher->generateCacheKey(TestCacheFlusher::$categories[0], $editor->ID);
|
||||||
|
$cache->set($editorKey, 'uncle');
|
||||||
|
}
|
||||||
|
foreach ($admins->Members() as $admin) {
|
||||||
|
$adminKey = $flusher->generateCacheKey(TestCacheFlusher::$categories[0], $admin->ID);
|
||||||
|
$cache->set($adminKey, 'cheese');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the cache for all members in the editors group
|
||||||
|
$editors->flushCache();
|
||||||
|
|
||||||
|
foreach ($editors->Members() as $editor) {
|
||||||
|
$editorKey = $flusher->generateCacheKey(TestCacheFlusher::$categories[0], $editor->ID);
|
||||||
|
$this->assertNull($cache->get($editorKey));
|
||||||
|
}
|
||||||
|
// Admins group should be unaffected
|
||||||
|
foreach ($admins->Members() as $admin) {
|
||||||
|
$adminKey = $flusher->generateCacheKey(TestCacheFlusher::$categories[0], $admin->ID);
|
||||||
|
$this->assertEquals('cheese', $cache->get($adminKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$admins->flushCache();
|
||||||
|
// Admins now affected
|
||||||
|
foreach ($admins->Members() as $admin) {
|
||||||
|
$adminKey = $flusher->generateCacheKey(TestCacheFlusher::$categories[0], $admin->ID);
|
||||||
|
$this->assertNull($cache->get($adminKey));
|
||||||
|
}
|
||||||
|
foreach ($editors->Members() as $editor) {
|
||||||
|
$editorKey = $flusher->generateCacheKey(TestCacheFlusher::$categories[0], $editor->ID);
|
||||||
|
$this->assertNull($cache->get($editorKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
tests/php/Security/InheritedPermissionsFlusherTest.yml
Normal file
35
tests/php/Security/InheritedPermissionsFlusherTest.yml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
SilverStripe\Security\Group:
|
||||||
|
editors:
|
||||||
|
Title: Editors
|
||||||
|
admins:
|
||||||
|
Title: Administrators
|
||||||
|
allsections:
|
||||||
|
Title: All Section Editors
|
||||||
|
securityadmins:
|
||||||
|
Title: Security Admins
|
||||||
|
|
||||||
|
SilverStripe\Security\Permission:
|
||||||
|
admins:
|
||||||
|
Code: ADMIN
|
||||||
|
Group: =>SilverStripe\Security\Group.admins
|
||||||
|
editors:
|
||||||
|
Code: CMS_ACCESS_CMSMain
|
||||||
|
Group: =>SilverStripe\Security\Group.editors
|
||||||
|
testpermission:
|
||||||
|
Code: TEST_NODE_ACCESS
|
||||||
|
Group: =>SilverStripe\Security\Group.editors
|
||||||
|
|
||||||
|
|
||||||
|
SilverStripe\Security\Member:
|
||||||
|
editor:
|
||||||
|
FirstName: Test
|
||||||
|
Surname: Editor
|
||||||
|
Groups: =>SilverStripe\Security\Group.editors
|
||||||
|
admin:
|
||||||
|
FirstName: Test
|
||||||
|
Surname: Administrator
|
||||||
|
Groups: =>SilverStripe\Security\Group.admins
|
||||||
|
allsections:
|
||||||
|
Groups: =>SilverStripe\Security\Group.allsections
|
||||||
|
securityadmin:
|
||||||
|
Groups: =>SilverStripe\Security\Group.securityadmins
|
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\Security\Tests\InheritedPermissionsFlusherTest;
|
||||||
|
|
||||||
|
use Psr\SimpleCache\CacheInterface;
|
||||||
|
use SilverStripe\Core\Cache\CacheFlusher;
|
||||||
|
|
||||||
|
class TestCacheFlusher implements CacheFlusher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $categories = [
|
||||||
|
'apples',
|
||||||
|
'pears',
|
||||||
|
'bananas',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var CacheInterface
|
||||||
|
*/
|
||||||
|
public $cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TestCacheFlusher constructor.
|
||||||
|
* @param CacheInterface $cache
|
||||||
|
*/
|
||||||
|
public function __construct(CacheInterface $cache)
|
||||||
|
{
|
||||||
|
$this->cache = $cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cache for this instance only
|
||||||
|
* @param array $ids A list of member IDs
|
||||||
|
*/
|
||||||
|
public function flushCache($ids = null)
|
||||||
|
{
|
||||||
|
if (!$this->cache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hard flush, e.g. flush=1
|
||||||
|
if (!$ids) {
|
||||||
|
$this->cache->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ids && is_array($ids)) {
|
||||||
|
foreach (self::$categories as $category) {
|
||||||
|
foreach ($ids as $memberID) {
|
||||||
|
$key = $this->generateCacheKey($category, $memberID);
|
||||||
|
$this->cache->delete($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $category
|
||||||
|
* @param $memberID
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generateCacheKey($category, $memberID)
|
||||||
|
{
|
||||||
|
return "{$category}__{$memberID}";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user