mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-18 15:33:15 +00:00
Add access restriction
This commit is contained in:
138
lib/Alchemy/Phrasea/Core/Configuration/AccessRestriction.php
Normal file
138
lib/Alchemy/Phrasea/Core/Configuration/AccessRestriction.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2014 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Core\Configuration;
|
||||
|
||||
use Alchemy\Phrasea\Cache\ArrayCache;
|
||||
use Alchemy\Phrasea\Cache\Cache;
|
||||
use Alchemy\Phrasea\Model\Entities\Collection;
|
||||
use Alchemy\Phrasea\Model\Entities\Databox;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class AccessRestriction
|
||||
{
|
||||
private $conf;
|
||||
private $appbox;
|
||||
private $logger;
|
||||
private $cache;
|
||||
|
||||
public function __construct(Cache $cache, PropertyAccess $conf, \appbox $appbox, LoggerInterface $logger)
|
||||
{
|
||||
$this->conf = $conf;
|
||||
$this->appbox = $appbox;
|
||||
$this->logger = $logger;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a configuration is set.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isRestricted()
|
||||
{
|
||||
$this->load();
|
||||
|
||||
return $this->cache->fetch('restricted');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a databox is available given a configuration.
|
||||
*
|
||||
* @param \databox $databox
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isDataboxAvailable(\databox $databox)
|
||||
{
|
||||
if (!$this->isRestricted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($databox->get_sbas_id(), $this->cache->fetch('available_databoxes'), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a collection is available given a configuration.
|
||||
*
|
||||
* @param \collection $collection
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isCollectionAvailable(\collection $collection)
|
||||
{
|
||||
if (!$this->isRestricted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$availableCollections = $this->cache->fetch('available_collections_'.$collection->get_databox()->get_sbas_id()) ?: [];
|
||||
|
||||
return in_array($collection->get_base_id(), $availableCollections, true);
|
||||
}
|
||||
|
||||
private function load()
|
||||
{
|
||||
if ($this->cache->fetch('loaded')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cache->save('loaded', true);
|
||||
|
||||
$allowedDataboxIds = array_map(function ($dbConf) {
|
||||
return $dbConf['id'];
|
||||
}, $this->conf->get('databoxes', []));
|
||||
|
||||
if (count($allowedDataboxIds) === 0) {
|
||||
$this->cache->save('restricted', false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cache->save('restricted', true);
|
||||
|
||||
$databoxIds = array_map(function (\databox $databox) { return $databox->get_sbas_id(); }, $this->appbox->get_databoxes());
|
||||
$errors = array_diff($allowedDataboxIds, $databoxIds);
|
||||
|
||||
if (count($errors) > 0) {
|
||||
$this->logger->error(sprintf('Misconfiguration for allowed databoxes : ids %s do not exist', implode(', ', $errors)));
|
||||
}
|
||||
|
||||
$allowedDataboxIds = array_intersect($allowedDataboxIds, $databoxIds);
|
||||
$this->cache->save('available_databoxes', $allowedDataboxIds);
|
||||
|
||||
$this->loadCollections();
|
||||
}
|
||||
|
||||
private function loadCollections()
|
||||
{
|
||||
$allowedDataboxIds = $this->cache->fetch('available_databoxes');
|
||||
|
||||
foreach ($this->conf->get('databoxes') as $databox) {
|
||||
if (!in_array($databox['id'], $allowedDataboxIds, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$collections = isset($databox['collections']) ? (is_array($databox['collections']) ? $databox['collections'] : [$databox['collections']]) : [];
|
||||
|
||||
$availableBaseIds = array_map(function (\collection $collection) { return $collection->get_base_id(); }, $this->appbox->get_databox($databox['id'])->get_collections());
|
||||
$errors = array_diff($collections, $availableBaseIds);
|
||||
|
||||
if (count($errors) > 0) {
|
||||
$this->logger->error(sprintf('Misconfiguration for allowed collections : ids %s do not exist', implode(', ', $errors)));
|
||||
}
|
||||
|
||||
$collections = array_intersect($collections, $availableBaseIds);
|
||||
|
||||
$this->cache->save('available_collections_'.$databox['id'], $collections);
|
||||
}
|
||||
}
|
||||
}
|
@@ -230,6 +230,10 @@ class HostConfiguration implements ConfigurationInterface
|
||||
$config[$property] = $data;
|
||||
}
|
||||
|
||||
if (isset($subConf['databoxes'])) {
|
||||
$config['databoxes'] = $subConf['databoxes'];
|
||||
}
|
||||
|
||||
$this->cache = $config;
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace Alchemy\Phrasea\Core\Provider;
|
||||
|
||||
use Alchemy\Phrasea\Core\Configuration\AccessRestriction;
|
||||
use Alchemy\Phrasea\Core\Configuration\Configuration;
|
||||
use Alchemy\Phrasea\Core\Configuration\DisplaySettingService;
|
||||
use Alchemy\Phrasea\Core\Configuration\HostConfiguration;
|
||||
@@ -62,6 +63,10 @@ class ConfigurationServiceProvider implements ServiceProviderInterface
|
||||
$app['settings'] = $app->share(function (SilexApplication $app) {
|
||||
return new DisplaySettingService($app['conf']);
|
||||
});
|
||||
|
||||
$app['conf.restrictions'] = $app->share(function (SilexApplication $app) {
|
||||
return new AccessRestriction($app['cache'], $app['conf'], $app['phraseanet.appbox'], $app['monolog']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -424,9 +424,13 @@ class appbox extends base
|
||||
$ret = [];
|
||||
foreach ($this->retrieve_sbas_ids() as $sbas_id) {
|
||||
try {
|
||||
$ret[$sbas_id] = new \databox($this->app, $sbas_id);
|
||||
$databox = new \databox($this->app, $sbas_id);
|
||||
if (!$this->app['conf.restrictions']->isDataboxAvailable($databox)) {
|
||||
continue;
|
||||
}
|
||||
$ret[$sbas_id] = $databox;
|
||||
} catch (NotFoundHttpException $e) {
|
||||
|
||||
$this->app['monolog']->error(sprintf('Databox %s is not reliable.', $databox->getId()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -451,8 +451,14 @@ class collection implements cache_cacheableInterface
|
||||
assert(is_int($coll_id));
|
||||
|
||||
$key = sprintf('%d_%d', $databox->get_sbas_id(), $coll_id);
|
||||
if ( ! isset(self::$_collections[$key])) {
|
||||
self::$_collections[$key] = new self($app, $coll_id, $databox);
|
||||
if (!isset(self::$_collections[$key])) {
|
||||
$collection = new self($app, $coll_id, $databox);
|
||||
|
||||
if (!$app['conf.restrictions']->isCollectionAvailable($collection)) {
|
||||
throw new Exception_Databox_CollectionNotFound('Collection `' . $collection->get_base_id() . '` is not available here.');
|
||||
}
|
||||
|
||||
self::$_collections[$key] = $collection;
|
||||
}
|
||||
|
||||
return self::$_collections[$key];
|
||||
|
@@ -36,11 +36,12 @@ class XSendFileMappingGeneratorTest extends \PhraseanetTestCase
|
||||
->getMock();
|
||||
$conf->expects($this->any())
|
||||
->method('get')
|
||||
->will($this->returnCallback(function ($property) use ($originalConf) {
|
||||
->will($this->returnCallback(function ($property, $defaultValue = null) use ($originalConf) {
|
||||
switch ($property) {
|
||||
case ['main', 'database']:
|
||||
return $originalConf->get($property);
|
||||
break;
|
||||
default:
|
||||
return $defaultValue;
|
||||
}
|
||||
}));
|
||||
self::$DI['cli']['conf'] = $conf;
|
||||
|
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\Core\Configuration;
|
||||
|
||||
use Alchemy\Phrasea\Cache\ArrayCache;
|
||||
use Alchemy\Phrasea\Core\Configuration\AccessRestriction;
|
||||
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
|
||||
use Alchemy\Tests\Phrasea\MockArrayConf;
|
||||
|
||||
class AccessRestrictionTest extends \PhraseanetTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideVariousConfiguration
|
||||
*/
|
||||
public function testRestrictionConfigurations($conf, $restricted, array $collAccess, array $collNoAccess, array $databoxAccess, array $databoxNoAccess)
|
||||
{
|
||||
$conf = new MockArrayConf($conf);
|
||||
$logger = $this->createLoggerMock();
|
||||
|
||||
$restriction = new AccessRestriction(new ArrayCache(), new PropertyAccess($conf), self::$DI['app']['phraseanet.appbox'], $logger);
|
||||
$this->assertEquals($restricted, $restriction->isRestricted());
|
||||
|
||||
foreach ($collAccess as $coll) {
|
||||
$this->assertTrue($restriction->isCollectionAvailable($coll));
|
||||
}
|
||||
foreach ($collNoAccess as $coll) {
|
||||
$this->assertFalse($restriction->isCollectionAvailable($coll));
|
||||
}
|
||||
|
||||
foreach ($databoxAccess as $databox) {
|
||||
$this->assertTrue($restriction->isDataboxAvailable($databox));
|
||||
}
|
||||
foreach ($databoxNoAccess as $databox) {
|
||||
$this->assertFalse($restriction->isDataboxAvailable($databox));
|
||||
}
|
||||
}
|
||||
|
||||
public function provideVariousConfiguration()
|
||||
{
|
||||
$app = $this->loadApp();
|
||||
|
||||
$databoxes = $app['phraseanet.appbox']->get_databoxes();
|
||||
$databox = current($databoxes);
|
||||
$collections = $databox->get_collections();
|
||||
$collection = current($collections);
|
||||
$conf1 = [];
|
||||
$conf2 = ['databoxes' => []];
|
||||
$conf3 = ['databoxes' => [['id' => $databox->get_sbas_id(), 'collections' => 244]]];
|
||||
$conf4 = ['databoxes' => [['id' => 25, 'collections' => 244]]];
|
||||
|
||||
return [
|
||||
[$conf1, false, [$collection], [], [$databox], []],
|
||||
[$conf2, false, [$collection], [], [$databox], []],
|
||||
[$conf3, true, [], [$collection], [$databox], []],
|
||||
[$conf4, true, [], [$collection], [], [$databox]],
|
||||
];
|
||||
}
|
||||
}
|
@@ -34,6 +34,11 @@ class ConfigurationServiceProviderTest extends ServiceProviderTestCase
|
||||
'registry.manipulator',
|
||||
'Alchemy\\Phrasea\\Core\\Configuration\\RegistryManipulator'
|
||||
],
|
||||
[
|
||||
'Alchemy\Phrasea\Core\Provider\ConfigurationServiceProvider',
|
||||
'conf.restrictions',
|
||||
'Alchemy\Phrasea\Core\Configuration\AccessRestriction'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -66,9 +66,4 @@ class FactoryTest extends \PhraseanetTestCase
|
||||
{
|
||||
return $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
|
||||
}
|
||||
|
||||
private function createLoggerMock()
|
||||
{
|
||||
return $this->getMock('Psr\Log\LoggerInterface');
|
||||
}
|
||||
}
|
||||
|
@@ -24,11 +24,4 @@ class PhraseanetWampServerTest extends \PhraseanetTestCase
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
|
||||
private function createLoggerMock()
|
||||
{
|
||||
return $this->getMockBuilder('Psr\Log\LoggerInterface')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
}
|
||||
|
@@ -627,4 +627,9 @@ abstract class PhraseanetTestCase extends WebTestCase
|
||||
$app['EM']->remove($user);
|
||||
$app['EM']->flush();
|
||||
}
|
||||
|
||||
protected function createLoggerMock()
|
||||
{
|
||||
return $this->getMock('Psr\Log\LoggerInterface');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user