I have a Symfony2 controller as follows:
/**
* @Security("is_granted('my_permission')")
*/
class MyController extends Controller
{
/**
* @Security("is_granted('another_permission')")
*/
public function myAction()
{
// ...
}
}
It appears the @Security
annotation on the myAction()
method overrides/ignores the parent @Security
annotation on the MyController
class. Is there any way to make these stack, to avoid having to do:
/**
* @Security("is_granted('my_permission') and is_granted('another_permission')")
*/
public function myAction()
{
// ...
}
on every action method in the controller?
It appears the @Security annotation on the myAction method overrides/ignores the parent @Security annotation on the MyController class.
Indeed, Sensio\Bundle\FrameworkExtraBundle\Configuration\Security
annotation doesn't allows nested configuration (see allowArray()
method). So method configuration overrides class configuration for @Security
annotation.
Is there any way to make these stack...
Not in a simple way, you need create three class and one trick to not reimplement the whole parent code:
Security.php
namespace AppBundle\Configuration;
/**
* @Annotation
*/
class Security extends \Sensio\Bundle\FrameworkExtraBundle\Configuration\Security
{
public function getAliasName()
{
return 'app_security';
}
public function allowArray()
{
// allow nested configuration (class/method).
return true;
}
}
SecurityConfiguration.php
This class allow you compound the final security expression through all security configurations (class/method).
namespace AppBundle\Configuration;
class SecurityConfiguration
{
/**
* @var Security[]
*/
private $configurations;
public function __construct(array $configurations)
{
$this->configurations = $configurations;
}
public function getExpression()
{
$expressions = [];
foreach ($this->configurations as $configuration) {
$expressions[] = $configuration->getExpression();
}
return implode(' and ', $expressions);
}
}
SecurityListener.php
namespace AppBundle\EventListener;
use AppBundle\Configuration\SecurityConfiguration;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class SecurityListener extends \Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener
{
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
if (!$configuration = $request->attributes->get('_app_security')) {
return;
}
// trick to simulate one security configuration (all in one class/method).
$request->attributes->set('_security', new SecurityConfiguration($configuration));
parent::onKernelController($event);
}
public static function getSubscribedEvents()
{
// this listener must be called after Sensio\Bundle\FrameworkExtraBundle\EventListener\ControllerListener.
return array(KernelEvents::CONTROLLER => array('onKernelController', -1));
}
}
services.yml
services:
app.security.listener:
class: AppBundle\EventListener\SecurityListener
parent: sensio_framework_extra.security.listener
tags:
- { name: kernel.event_subscriber }
Finally, just use your @AppBundle\Configuration\Security
annotation instead the standard one.
Here's my try:
Using, in app/config/security.yml
, this role hierarchy:
role_hierarchy:
ROLE_CLASS: ROLE_CLASS
ROLE_METHOD: [ROLE_CLASS, ROLE_METHOD]
And if I have two users: user1
with ROLE_CLASS
, and user2
with ROLE_METHOD
(which means this user has both roles), then the first user can see all the pages created inside the controller, except the ones that have additional restrictions.
Controller example:
/**
* @Security("is_granted('ROLE_CLASS')")
*/
class SomeController extends Controller
{
/**
* @Route("/page1", name="page1")
* @Security("is_granted('ROLE_METHOD')")
*/
public function page1()
{
return $this->render('default/page1.html.twig');
}
/**
* @Route("/page2", name="page2")
*/
public function page2()
{
return $this->render('default/page2.html.twig');
}
}
So because user1
has ROLE_CLASS
, he is able to see just /page2
, but not /page1
, as he will receive a 403 Expression "is_granted('ROLE_METHOD')" denied access.
error (for dev obviously).
On the other hand, user2
, having ROLE_METHOD
(and ROLE_CLASS
), he is able to see both pages.