Selaa lähdekoodia

Fix #132 - Make the dependency to the SecurityBundle and ACLs optionnal

Thomas Rabaix 14 vuotta sitten
vanhempi
commit
f8076a3214

+ 21 - 28
Admin/Admin.php

@@ -15,7 +15,6 @@ use Symfony\Component\Form\FormBuilder;
 use Symfony\Component\Routing\RouterInterface;
 use Symfony\Component\Translation\TranslatorInterface;
 use Symfony\Component\HttpFoundation\Request;
-
 use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
 
 use Sonata\AdminBundle\Form\FormMapper;
@@ -26,7 +25,7 @@ use Sonata\AdminBundle\Admin\Pool;
 use Sonata\AdminBundle\Builder\FormContractorInterface;
 use Sonata\AdminBundle\Builder\ListBuilderInterface;
 use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
-
+use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
 use Sonata\AdminBundle\Route\RouteCollection;
 use Sonata\AdminBundle\Model\ModelManagerInterface;
 
@@ -296,7 +295,7 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
      */
     protected $breadcrumbs = array();
 
-    protected $securityContext = null;
+    protected $securityHandler = null;
 
     /**
      * The configuration pool
@@ -1843,45 +1842,39 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
         return $this->getCode();
     }
 
-    public function getAclInformation()
+    /**
+     * Return the list of security name available for the current admin
+     * This should be used by experimented users
+     *
+     * @return array
+     */
+    public function getSecurityInformation()
     {
-        $baseRole = 'ROLE_'.str_replace('.', '_', strtoupper($this->getCode())).'_%s';
-
         return array(
-            sprintf($baseRole, 'EDIT')      => array('EDIT'),
-            sprintf($baseRole, 'LIST')      => array('LIST'),
-            sprintf($baseRole, 'CREATE')    => array('CREATE'),
-            sprintf($baseRole, 'DELETE')    => array('DELETE'),
-            sprintf($baseRole, 'OPERATOR')  => array('OPERATOR'),
+            'EDIT'      => array('EDIT'),
+            'LIST'      => array('LIST'),
+            'CREATE'    => array('CREATE'),
+            'DELETE'    => array('DELETE'),
+            'OPERATOR'  => array('OPERATOR')
         );
     }
 
-    public function setSecurityContext($securityContext)
+    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
     {
-        $this->securityContext = $securityContext;
+        $this->securityHandler = $securityHandler;
     }
 
-    public function getSecurityContext()
+    public function getSecurityHandler()
     {
-        return $this->securityContext;
+        return $this->securityHandler;
     }
 
     /**
-     * @param string $names
+     * @param string $name
      * @return boolean
      */
-    public function isGranted($names)
+    public function isGranted($name)
     {
-        if (!is_array($names)) {
-            $names = (array) $names;
-        }
-
-        foreach ($names as $name) {
-            if (true === $this->securityContext->isGranted($name, $this)) {
-                return true;
-            }
-        }
-
-        return false;
+        return $this->securityHandler->isGranted($name, $this);
     }
 }

+ 12 - 0
Admin/AdminInterface.php

@@ -125,4 +125,16 @@ interface AdminInterface
      * @return \Symfony\Component\HttpFoundation\Request
      */
     function getRequest();
+
+    /**
+     *
+     * @return string
+     */
+    function getCode();
+
+    /**
+     * @abstract
+     * @return array
+     */
+    function getSecurityInformation();
 }

+ 9 - 1
Command/SetupAclCommand.php

@@ -24,6 +24,7 @@ use Sonata\AdminBundle\Security\Acl\Permission\MaskBuilder;
 use Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException;
 use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
 use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
+use Sonata\AdminBundle\Security\Handler\AclSecurityHandler;
 
 class SetupAclCommand extends Command
 {
@@ -51,6 +52,12 @@ class SetupAclCommand extends Command
                 continue;
             }
 
+            $securityHandler = $admin->getSecurityHandler();
+            if (!$securityHandler instanceof AclSecurityHandler) {
+                $output->writeln('Admin class is not configured to use ACL : <info>ignoring</info>');
+                continue;
+            }
+
             $objectIdentity = ObjectIdentity::fromDomainObject($admin);
             try {
                 $acl = $aclProvider->findAcl($objectIdentity);
@@ -58,7 +65,8 @@ class SetupAclCommand extends Command
                 $acl = $aclProvider->createAcl($objectIdentity);
             }
 
-            $this->configureACL($output, $acl, $builder, $admin->getAclInformation());
+
+            $this->configureACL($output, $acl, $builder, $securityHandler->buildSecurityInformation($admin));
 
             $aclProvider->updateAcl($acl);
         }

+ 34 - 4
DependencyInjection/Compiler/AddDependencyCallsPass.php

@@ -30,6 +30,9 @@ class AddDependencyCallsPass implements CompilerPassInterface
      */
     public function process(ContainerBuilder $container)
     {
+
+        $settings = $this->fixSettings($container);
+
         $groups = $admins = $classes = array();
 
         $pool = $container->getDefinition('sonata.admin.pool');
@@ -48,7 +51,7 @@ class AddDependencyCallsPass implements CompilerPassInterface
                 $definition->replaceArgument(2, 'SonataAdminBundle:CRUD');
             }
 
-            $this->applyDefaults($definition, $attributes);
+            $this->applyDefaults($definition, $attributes, $settings);
 
             $arguments = $definition->getArguments();
             if (preg_match('/%(.*)%/', $arguments[1], $matches)) {
@@ -86,7 +89,7 @@ class AddDependencyCallsPass implements CompilerPassInterface
      * @param array $attributes
      * @return \Symfony\Component\DependencyInjection\Definition
      */
-    public function applyDefaults(Definition $definition, array $attributes = array())
+    public function applyDefaults(Definition $definition, array $attributes = array(), array $settings)
     {
         $definition->setScope(ContainerInterface::SCOPE_PROTOTYPE);
 
@@ -120,8 +123,8 @@ class AddDependencyCallsPass implements CompilerPassInterface
             $definition->addMethodCall('setRouter', array(new Reference('router')));
         }
 
-        if (!$definition->hasMethodCall('setSecurityContext')) {
-            $definition->addMethodCall('setSecurityContext', array(new Reference('security.context')));
+        if (!$definition->hasMethodCall('setSecurityHandler')) {
+            $definition->addMethodCall('setSecurityHandler', array(new Reference($settings['security_handler'])));
         }
 
         if (!$definition->hasMethodCall('setLabel')) {
@@ -133,4 +136,31 @@ class AddDependencyCallsPass implements CompilerPassInterface
 
         return $definition;
     }
+
+    /**
+     * @param ContainerBuilder $container
+     * @return array
+     */
+    public function fixSettings(ContainerBuilder $container)
+    {
+        $pool = $container->getDefinition('sonata.admin.pool');
+
+        // not very clean but don't know how to do that for now
+        $settings = false;
+        $methods  = $pool->getMethodCalls();
+        foreach ($methods as $pos => $calls) {
+            if ($calls[0] == '__hack__') {
+                $settings = $calls[1];
+                break;
+            }
+        }
+
+        if ($settings) {
+            unset($methods[$pos]);
+        }
+
+        $pool->setMethodCalls($methods);
+
+        return $settings;
+    }
 }

+ 1 - 0
DependencyInjection/Configuration.php

@@ -44,6 +44,7 @@ class Configuration implements ConfigurationInterface
     {
         $rootNode
             ->children()
+                ->scalarNode('security_handler')->defaultValue('sonata.admin.security.handler.noop')->end()
                 ->arrayNode('templates')
                     ->children()
                         ->scalarNode('layout')->cannotBeEmpty()->end()

+ 2 - 0
DependencyInjection/SonataAdminExtension.php

@@ -59,6 +59,8 @@ class SonataAdminExtension extends Extension
 
         // setups parameters with values in config.yml, default values from external files used if not
         $this->configSetupTemplates($config, $container);
+
+        $container->getDefinition('sonata.admin.pool')->addMethodCall('__hack__', $config);
     }
 
     protected function configSetupTemplates($config, $container)

+ 6 - 0
Resources/config/core.xml

@@ -18,6 +18,12 @@
             <argument type="service" id="sonata.admin.pool" />
         </service>
 
+        <service id="sonata.admin.security.handler.noop" class="Sonata\AdminBundle\Security\Handler\NoopSecurityHandler" />
+
+        <service id="sonata.admin.security.handler.acl" class="Sonata\AdminBundle\Security\Handler\AclSecurityHandler">
+            <argument type="service" id="security.context" />
+        </service>
+
     </services>
 
 </container>

+ 20 - 5
Resources/doc/reference/security.rst

@@ -1,7 +1,23 @@
 Security
 ========
 
-The current ``AdminBundle`` implementation uses ACL and ROLES to handle permissions.
+The security part is managed by a ``SecurityHandler``, the bundle comes with 2 handlers
+
+  - ``sonata.admin.security.handler.acl`` : ACL and ROLES to handle permissions
+  - ``sonata.admin.security.handler.noop`` : always return true, can be used with the Symfony2 firewall
+
+The default value is ``sonata.admin.security.handler.noop``, if you want to change the default value
+you can set the ``security_handler`` to ``sonata.admin.security.handler.acl``.
+
+.. code-block:: yaml
+
+    sonata_admin:
+        security_handler: sonata.admin.security.handler.acl
+
+The following section explains how to set up ACL with the ``FriendsOfSymfony/UserBundle``.
+
+ACL and FriendsOfSymfony/UserBundle
+-----------------------------------
 
 If you want an easy way to handle users, please use :
 
@@ -15,8 +31,7 @@ The security integration is a work in progress and have some knows issues :
 
 
 Configuration
--------------
-
+~~~~~~~~~~~~~
 
     - The following configuration defines :
 
@@ -78,7 +93,7 @@ Configuration
 
         role_hierarchy:
             ROLE_ADMIN:       ROLE_USER
-            ROLE_SUPERADMIN: [ROLE_ADMIN, ROLE_SONATA_ADMIN, ROLE_ALLOWED_TO_SWITCH]
+            ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_SONATA_ADMIN, ROLE_ALLOWED_TO_SWITCH]
 
         acl:
             connection: default
@@ -120,7 +135,7 @@ If you have Admin classes, you can install the related CRUD ACL rules :
 If you try to access to the admin class you should see the login form, just logon with the ``root`` user.
 
 Usage
------
+~~~~~
 
 Everytime you create a new ``Admin`` class, you should create start the command ``php app/console sonata:admin:setup-acl``
 so the ACL database will be updated with the latest masks and roles informations.

+ 47 - 0
Security/Handler/AclSecurityHandler.php

@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the Sonata project.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+
+namespace Sonata\AdminBundle\Security\Handler;
+
+use Symfony\Component\Security\Core\SecurityContextInterface;
+use Sonata\AdminBundle\Admin\AdminInterface;
+
+class AclSecurityHandler implements SecurityHandlerInterface
+{
+    public function __construct(SecurityContextInterface $securityContext)
+    {
+        $this->securityContext = $securityContext;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    function isGranted($attributes, $object = null)
+    {
+        return $this->securityContext->isGranted($attributes, $this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    function buildSecurityInformation(AdminInterface $admin)
+    {
+        $baseRole = 'ROLE_'.str_replace('.', '_', strtoupper($admin->getCode())).'_%s';
+
+        $results = array();
+        foreach ($admin->getSecurityInformation() as $name => $permissions) {
+            $results[sprintf($baseRole, $name)] = $permissions;
+        }
+
+        return $results;
+    }
+}

+ 35 - 0
Security/Handler/NoopSecurityHandler.php

@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of the Sonata project.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+
+namespace Sonata\AdminBundle\Security\Handler;
+
+use Sonata\AdminBundle\Admin\AdminInterface;
+
+class NoopSecurityHandler implements SecurityHandlerInterface
+{
+
+    /**
+     * {@inheritDoc}
+     */
+    function isGranted($attributes, $object = null)
+    {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    function buildSecurityInformation(AdminInterface $admin)
+    {
+        return array();
+    }
+}

+ 33 - 0
Security/Handler/SecurityHandlerInterface.php

@@ -0,0 +1,33 @@
+<?php
+
+/*
+ * This file is part of the Sonata project.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+
+namespace Sonata\AdminBundle\Security\Handler;
+
+use Sonata\AdminBundle\Admin\AdminInterface;
+
+interface SecurityHandlerInterface
+{
+    /**
+     * @abstract
+     * @param string|array $attributes
+     * @param null $object
+     * @return boolean
+     */
+    function isGranted($attributes, $object = null);
+
+    /**
+     * @abstract
+     * @param array $informations
+     * @return array
+     */
+    function buildSecurityInformation(AdminInterface $admin);
+}

+ 63 - 0
Tests/Security/Handler/AclSecurityHandlerTest.php

@@ -0,0 +1,63 @@
+<?php
+
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Sonata\AdminBundle\Tests\Admin\Security\Acl\Permission;
+
+use Sonata\AdminBundle\Security\Handler\AclSecurityHandler;
+
+class AclSecurityHandlerTest extends \PHPUnit_Framework_TestCase
+{
+    public function testAcl()
+    {
+
+        $securityContext = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
+        $securityContext->expects($this->any())
+            ->method('isGranted')
+            ->will($this->returnValue(true));
+
+        $handler = new AclSecurityHandler($securityContext);
+
+        $this->assertTrue($handler->isGranted(array('TOTO')));
+        $this->assertTrue($handler->isGranted('TOTO'));
+
+        $securityContext = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
+        $securityContext->expects($this->any())
+            ->method('isGranted')
+            ->will($this->returnValue(false));
+
+        $handler = new AclSecurityHandler($securityContext);
+
+        $this->assertFalse($handler->isGranted(array('TOTO')));
+        $this->assertFalse($handler->isGranted('TOTO'));
+    }
+
+  public function testBuildInformation()
+  {
+      $informations = array(
+          'EDIT' => array('EDIT')
+      );
+
+      $securityContext = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
+      $admin = $this->getMock('Sonata\AdminBundle\Admin\AdminInterface');
+      $admin->expects($this->once())
+          ->method('getCode')
+          ->will($this->returnValue('test'));
+
+      $admin->expects($this->once())
+          ->method('getSecurityInformation')
+          ->will($this->returnValue($informations));
+
+      $handler = new AclSecurityHandler($securityContext);
+
+      $results = $handler->buildSecurityInformation($admin);
+
+      $this->assertArrayHasKey('ROLE_TEST_EDIT', $results);
+  }
+}

+ 25 - 0
Tests/Security/Handler/NoopSecurityHandlerTest.php

@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Sonata\AdminBundle\Tests\Admin\Security\Acl\Permission;
+
+use Sonata\AdminBundle\Security\Handler\NoopSecurityHandler;
+
+class NoopSecurityHandlerTest extends \PHPUnit_Framework_TestCase
+{
+    public function testNoop()
+    {
+
+        $handler = new NoopSecurityHandler;
+
+        $this->assertTrue($handler->isGranted(array('TOTO')));
+        $this->assertTrue($handler->isGranted('TOTO'));
+    }
+}