浏览代码

start ACL

Thomas Rabaix 14 年之前
父节点
当前提交
8f180e0bd0
共有 4 个文件被更改,包括 153 次插入2 次删除
  1. 47 2
      Admin/Admin.php
  2. 81 0
      Command/SetupAclCommand.php
  3. 21 0
      Controller/CRUDController.php
  4. 4 0
      DependencyInjection/AddDependencyCallsPass.php

+ 47 - 2
Admin/Admin.php

@@ -16,6 +16,8 @@ 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;
 use Sonata\AdminBundle\Datagrid\ListMapper;
 use Sonata\AdminBundle\Datagrid\DatagridMapper;
@@ -31,7 +33,7 @@ use Sonata\AdminBundle\Model\ModelManagerInterface;
 use Knplabs\Bundle\MenuBundle\Menu;
 use Knplabs\Bundle\MenuBundle\MenuItem;
 
-abstract class Admin implements AdminInterface
+abstract class Admin implements AdminInterface, DomainObjectInterface
 {
     /**
      * The class name managed by the admin class
@@ -294,6 +296,7 @@ abstract class Admin implements AdminInterface
      */
     protected $breadcrumbs = array();
 
+    protected $securityContext = null;
 
     /**
      * The configuration pool
@@ -739,7 +742,6 @@ abstract class Admin implements AdminInterface
 
         $collection->add('list');
         $collection->add('create');
-        $collection->add('update');
         $collection->add('batch');
         $collection->add('edit', $this->getRouterIdParameter().'/edit');
         $collection->add('delete', $this->getRouterIdParameter().'/delete');
@@ -1830,4 +1832,47 @@ abstract class Admin implements AdminInterface
     {
         $this->modelManager = $modelManager;
     }
+
+    /**
+     * Returns a unique identifier for this domain object.
+     *
+     * @return string
+     */
+    function getObjectIdentifier()
+    {
+        return $this->getCode();
+    }
+
+    public function getAclInformation()
+    {
+        $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, 'BATCH')     => array('BATCH'),
+            sprintf($baseRole, 'OPERATOR')  => array('OPERATOR'),
+        );
+    }
+
+    public function setSecurityContext($securityContext)
+    {
+        $this->securityContext = $securityContext;
+    }
+
+    public function getSecurityContext()
+    {
+        return $this->securityContext;
+    }
+
+    /**
+     * @param string $name
+     * @return boolean
+     */
+    public function isGranted($name)
+    {
+        return $this->securityContext->isGranted($name, $this);
+    }
 }

+ 81 - 0
Command/SetupAclCommand.php

@@ -0,0 +1,81 @@
+<?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\Command;
+
+use Symfony\Bundle\FrameworkBundle\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Output\Output;
+
+use Symfony\Component\Security\Acl\Model\AclInterface;
+use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
+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;
+
+class SetupAclCommand extends Command
+{
+    public function configure()
+    {
+        $this->setName('sonata:admin:setup-acl');
+        $this->setDescription('Install ACL for Admin Classes');
+    }
+
+    public function execute(InputInterface $input, OutputInterface $output)
+    {
+        $aclProvider = $this->container->get('security.acl.provider');
+
+        $output->writeln('Starting ACL AdminBundle configuration');
+
+        $builder = new MaskBuilder();
+        foreach ($this->container->get('sonata.admin.pool')->getAdminServiceIds() as $id) {
+            $output->writeln(sprintf(' > install ACL for %s', $id));
+
+            try {
+                $admin = $this->container->get($id);
+            } catch (\Exception $e) {
+                $output->writeln('<error>Warning : The admin class cannot be initiated from the command line</error>');
+                $output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
+                continue;
+            }
+
+            $objectIdentity = ObjectIdentity::fromDomainObject($admin);
+            try {
+                $acl = $aclProvider->findAcl($objectIdentity);
+            } catch(AclNotFoundException $e) {
+                $acl = $aclProvider->createAcl($objectIdentity);
+            }
+
+            $this->configureACL($output, $acl, $builder, $admin->getAclInformation());
+
+            $aclProvider->updateAcl($acl);
+        }
+    }
+
+    public function configureACL(OutputInterface $output, AclInterface $acl, MaskBuilder $builder, array $aclInformations = array())
+    {
+        foreach ($aclInformations as $name => $masks) {
+            foreach ($masks as $mask) {
+                $builder->add($mask);
+            }
+
+            $acl->insertClassAce(new RoleSecurityIdentity($name), $builder->get());
+
+            $output->writeln(sprintf('   - add role: %s, ACL: %s', $name, json_encode($masks)));
+
+            $builder->reset();
+        }
+    }
+}

+ 21 - 0
Controller/CRUDController.php

@@ -14,6 +14,7 @@ namespace Sonata\AdminBundle\Controller;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\Security\Core\Exception\AccessDeniedException;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
 
@@ -120,6 +121,10 @@ class CRUDController extends Controller
      */
     public function listAction()
     {
+        if ($this->admin->isGranted('LIST')) {
+            throw new AccessDeniedException();
+        }
+
         return $this->render($this->admin->getListTemplate(), array(
             'action'            => 'list',
             'admin'             => $this->admin,
@@ -135,6 +140,10 @@ class CRUDController extends Controller
      */
     public function batchActionDelete($idx)
     {
+        if ($this->admin->isGranted('DELETE')) {
+            throw new AccessDeniedException();
+        }
+
         $modelManager = $this->admin->getModelManager();
         $modelManager->batchDelete($this->admin->getClass(), $idx);
 
@@ -144,6 +153,10 @@ class CRUDController extends Controller
 
     public function deleteAction($id)
     {
+        if ($this->admin->isGranted('DELETE')) {
+            throw new AccessDeniedException();
+        }
+
         $id = $this->get('request')->get($this->admin->getIdParameter());
         $object = $this->admin->getObject($id);
 
@@ -165,6 +178,10 @@ class CRUDController extends Controller
      */
     public function editAction($id)
     {
+        if ($this->admin->isGranted('EDIT')) {
+            throw new AccessDeniedException();
+        }
+
         $object = $this->admin->getObject($this->get('request')->get($this->admin->getIdParameter()));
 
         if (!$object) {
@@ -261,6 +278,10 @@ class CRUDController extends Controller
      */
     public function createAction()
     {
+        if ($this->admin->isGranted('CREATE')) {
+            throw new AccessDeniedException();
+        }
+
         $object = $this->admin->getNewInstance();
         $form = $this->admin->getForm($object);
 

+ 4 - 0
DependencyInjection/AddDependencyCallsPass.php

@@ -124,6 +124,10 @@ 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('setLabel')) {
             $label = isset($attributes[0]['label']) ? $attributes[0]['label'] : '-';
             $definition->addMethodCall('setLabel', array($label));