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');
|
||||
});
|
||||
}
|
||||
|
||||
// Now set all children groups as a new foreign key
|
||||
$groups = Group::get()->byIDs($this->collateFamilyIDs());
|
||||
$result = $result->forForeignID($groups->column('ID'))->where($filter);
|
||||
$familyIDs = $this->collateFamilyIDs();
|
||||
if (!empty($familyIDs)) {
|
||||
$groups = Group::get()->byIDs($familyIDs);
|
||||
$groupIDs = $groups->column('ID');
|
||||
if (!empty($groupIDs)) {
|
||||
$result = $result->forForeignID($groupIDs)->where($filter);
|
||||
}
|
||||
}
|
||||
|
||||
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