Add access restriction

This commit is contained in:
Romain Neutron
2014-02-24 10:42:31 +01:00
parent 4535491e6c
commit 878c96f6d4
11 changed files with 232 additions and 18 deletions

View 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);
}
}
}

View File

@@ -230,6 +230,10 @@ class HostConfiguration implements ConfigurationInterface
$config[$property] = $data;
}
if (isset($subConf['databoxes'])) {
$config['databoxes'] = $subConf['databoxes'];
}
$this->cache = $config;
}
}

View File

@@ -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']);
});
}
/**

View File

@@ -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()));
}
}

View File

@@ -452,7 +452,13 @@ class collection implements cache_cacheableInterface
$key = sprintf('%d_%d', $databox->get_sbas_id(), $coll_id);
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];

View File

@@ -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;

View File

@@ -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]],
];
}
}

View File

@@ -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'
],
];
}

View File

@@ -66,9 +66,4 @@ class FactoryTest extends \PhraseanetTestCase
{
return $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
}
private function createLoggerMock()
{
return $this->getMock('Psr\Log\LoggerInterface');
}
}

View File

@@ -24,11 +24,4 @@ class PhraseanetWampServerTest extends \PhraseanetTestCase
->disableOriginalConstructor()
->getMock();
}
private function createLoggerMock()
{
return $this->getMockBuilder('Psr\Log\LoggerInterface')
->disableOriginalConstructor()
->getMock();
}
}

View File

@@ -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');
}
}