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;
|
$config[$property] = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($subConf['databoxes'])) {
|
||||||
|
$config['databoxes'] = $subConf['databoxes'];
|
||||||
|
}
|
||||||
|
|
||||||
$this->cache = $config;
|
$this->cache = $config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Alchemy\Phrasea\Core\Provider;
|
namespace Alchemy\Phrasea\Core\Provider;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\Core\Configuration\AccessRestriction;
|
||||||
use Alchemy\Phrasea\Core\Configuration\Configuration;
|
use Alchemy\Phrasea\Core\Configuration\Configuration;
|
||||||
use Alchemy\Phrasea\Core\Configuration\DisplaySettingService;
|
use Alchemy\Phrasea\Core\Configuration\DisplaySettingService;
|
||||||
use Alchemy\Phrasea\Core\Configuration\HostConfiguration;
|
use Alchemy\Phrasea\Core\Configuration\HostConfiguration;
|
||||||
@@ -62,6 +63,10 @@ class ConfigurationServiceProvider implements ServiceProviderInterface
|
|||||||
$app['settings'] = $app->share(function (SilexApplication $app) {
|
$app['settings'] = $app->share(function (SilexApplication $app) {
|
||||||
return new DisplaySettingService($app['conf']);
|
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 = [];
|
$ret = [];
|
||||||
foreach ($this->retrieve_sbas_ids() as $sbas_id) {
|
foreach ($this->retrieve_sbas_ids() as $sbas_id) {
|
||||||
try {
|
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) {
|
} 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));
|
assert(is_int($coll_id));
|
||||||
|
|
||||||
$key = sprintf('%d_%d', $databox->get_sbas_id(), $coll_id);
|
$key = sprintf('%d_%d', $databox->get_sbas_id(), $coll_id);
|
||||||
if ( ! isset(self::$_collections[$key])) {
|
if (!isset(self::$_collections[$key])) {
|
||||||
self::$_collections[$key] = new self($app, $coll_id, $databox);
|
$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];
|
return self::$_collections[$key];
|
||||||
|
@@ -36,11 +36,12 @@ class XSendFileMappingGeneratorTest extends \PhraseanetTestCase
|
|||||||
->getMock();
|
->getMock();
|
||||||
$conf->expects($this->any())
|
$conf->expects($this->any())
|
||||||
->method('get')
|
->method('get')
|
||||||
->will($this->returnCallback(function ($property) use ($originalConf) {
|
->will($this->returnCallback(function ($property, $defaultValue = null) use ($originalConf) {
|
||||||
switch ($property) {
|
switch ($property) {
|
||||||
case ['main', 'database']:
|
case ['main', 'database']:
|
||||||
return $originalConf->get($property);
|
return $originalConf->get($property);
|
||||||
break;
|
default:
|
||||||
|
return $defaultValue;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
self::$DI['cli']['conf'] = $conf;
|
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',
|
'registry.manipulator',
|
||||||
'Alchemy\\Phrasea\\Core\\Configuration\\RegistryManipulator'
|
'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');
|
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()
|
->disableOriginalConstructor()
|
||||||
->getMock();
|
->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']->remove($user);
|
||||||
$app['EM']->flush();
|
$app['EM']->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function createLoggerMock()
|
||||||
|
{
|
||||||
|
return $this->getMock('Psr\Log\LoggerInterface');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user