Add BaseVoter and Authorization Service Provider

PLUG-112
This commit is contained in:
Benoît Burnichon
2015-09-14 22:49:26 +02:00
parent c808e8a923
commit d9604ae300
6 changed files with 180 additions and 9 deletions

View File

@@ -15,6 +15,7 @@ use Alchemy\Geonames\GeonamesServiceProvider;
use Alchemy\Phrasea\Application\Helper\AclAware;
use Alchemy\Phrasea\Application\Helper\ApplicationBoxAware;
use Alchemy\Phrasea\Application\Helper\AuthenticatorAware;
use Alchemy\Phrasea\Authorization\AuthorizationServiceProvider;
use Alchemy\Phrasea\Cache\Factory;
use Alchemy\Phrasea\Cache\Manager;
use Alchemy\Phrasea\Core\Event\Subscriber\BasketSubscriber;
@@ -191,6 +192,7 @@ class Application extends SilexApplication
$this->register(new ACLServiceProvider());
$this->register(new APIServiceProvider());
$this->register(new AuthenticationManagerServiceProvider());
$this->register(new AuthorizationServiceProvider());
$this->register(new BrowserServiceProvider());
$this->register(new ConvertersServiceProvider());
$this->register(new CSVServiceProvider());

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2015 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Authorization;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
class AuthorizationChecker
{
/** @var AccessDecisionManager */
private $accessDecisionManager;
/** @var TokenInterface */
private $token;
public function __construct(AccessDecisionManager $accessDecisionManager, TokenInterface $token)
{
$this->accessDecisionManager = $accessDecisionManager;
$this->token = $token;
}
public function isGranted($attributes, $object = null)
{
if (!is_array($attributes)) {
$attributes = [$attributes];
}
return $this->accessDecisionManager->decide($this->token, $attributes, $object);
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2015 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Authorization;
use Alchemy\Phrasea\Application as PhraseaApplication;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
class AuthorizationServiceProvider implements ServiceProviderInterface
{
public function register(Application $app)
{
$app['phraseanet.security_token'] = $app->share(function (PhraseaApplication $app) {
$user = $app['authentication']->getUser();
if ($user instanceof \User_Adapter) {
return new PreAuthenticatedToken((string)$user->get_id(), null, 'fake', ['ROLE_USER']);
}
return new AnonymousToken('fake', 'anon.', []);
});
$app['phraseanet.access_manager'] = $app->share(function (PhraseaApplication $app) {
return new AccessDecisionManager($app['phraseanet.voters']);
});
$app['phraseanet.voters'] = $app->share(function () {
return [];
});
$app['phraseanet.authorization_checker'] = $app->share(function (PhraseaApplication $app) {
return new AuthorizationChecker(
$app['phraseanet.access_manager'],
$app['phraseanet.security_token']
);
});
}
public function boot(Application $app)
{
// Nothing to do
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2015 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Authorization;
use Alchemy\Phrasea\Application;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
abstract class BaseVoter implements VoterInterface
{
private $supportedAttributes;
private $supportedClass;
/** @var Application */
private $app;
public function __construct(Application $app, array $attributes, $supportedClass)
{
$this->app = $app;
$this->supportedAttributes = $attributes;
$this->supportedClass = $supportedClass;
if (!is_callable([$this, 'isGranted'])) {
throw new \LogicException('Subclasses should implement a "isGranted" method');
}
}
public function supportsAttribute($attribute)
{
return in_array($attribute, $this->supportedAttributes);
}
public function supportsClass($class)
{
$supportedClass = $this->supportedClass;
if ($class == $supportedClass || is_subclass_of($class, $supportedClass)) {
return true;
}
return false;
}
public function vote(TokenInterface $token, $object, array $attributes)
{
if (!$object || !$this->supportsClass(get_class($object))) {
return self::ACCESS_ABSTAIN;
}
$user = (ctype_digit($token->getUser())) ? new \User_Adapter((int) $token->getUser(), $this->app) : null;
foreach ($attributes as $attribute) {
$attribute = strtolower($attribute);
if ($this->supportsAttribute($attribute)) {
$isGranted = call_user_func([$this, 'isGranted'], $attribute, $object, $user);
return $isGranted ? self::ACCESS_GRANTED : self::ACCESS_DENIED;
}
}
return self::ACCESS_ABSTAIN;
}
/**
* @param string $attribute
* @param object $object
* @param \User_Adapter|null $user
* @return bool
*/
//abstract protected function isGranted($attribute, $object, \User_Adapter $user = null);
}

View File

@@ -161,6 +161,14 @@
{% block content %}
{% set workzone_plugins = [] %}
{% for plugin in app['plugin.workzone'].keys() %}
{% if app['phraseanet.authorization_checker'].isGranted('VIEW', app['plugin.workzone'][plugin]) %}
{% set workzone_plugins = workzone_plugins|merge({(plugin): app['plugin.workzone'][plugin]}) %}
{% endif %}
{% endfor %}
{% set search_datas = module_prod.get_search_datas() %}
<div style="position:absolute; top:0; left:0; right:0; bottom:0; background-color:#1a1a1a; z-index:32766;">
<div id="loader" style="top:200px; margin:0 auto; -webkit-border-radius:5px; -moz-border-radius:5px; border-radius:5px; background-color:#CCCCCC; position:relative; margin:0 auto; text-align:center; width:400px; height:100px; padding:20px; z-index:32767;">
<div style="margin:0 10px 10px; font-family:Helvetica,Arial,sans-serif; font-size:18px; color:#1A1A1A; text-align:left;">Phraseanet</div>
@@ -195,14 +203,9 @@
{% include 'prod/tab_thesaurus.html.twig' with {has_access_to_module: app.getAclForUser(app.getAuthenticatedUser()).has_access_to_module('thesaurus')} %}
{% endif %}
{% endblock %}
{% set workzone_plugins = app['plugin.workzone'] %}
{% set workzone_plugins_class = 'single-plugin' %}
{% if workzone_plugins.keys()|length > 1 %}
{% set workzone_plugins_class = 'multiple-plugin' %}
{% endif %}
<div id="plugins" class="PNB {{ workzone_plugins_class }}" style="top:52px;">
{% for plugin in workzone_plugins.keys() %}
{% include workzone_plugins[plugin].getWorkzoneTemplate() with {'app': app, 'plugin_id': plugin} only %}
<div id="plugins" class="PNB {{ workzone_plugins|length > 1 ? 'multiple-plugin' : 'single-plugin' }}" style="top:52px;">
{% for pluginId, plugin in workzone_plugins %}
{% include plugin.getWorkzoneTemplate() with {'app': app, 'plugin_id': pluginId} only %}
{% endfor %}
</div>
</div>

View File

@@ -16,7 +16,7 @@
</a>
</li>
{% endif %}
{% if app['plugin.workzone'].keys() is not empty %}
{% if workzone_plugins is not empty %}
<li>
<a href="#plugins" class="WZplugins">
<img src="/skins/icons/plugins.png" title="{{ 'phraseanet:: plugin.workzone' | trans }}"/>