Quellcode durchsuchen

add ACL documentation, add ACL check inside template files

Thomas Rabaix vor 14 Jahren
Ursprung
Commit
fb1ef76553

+ 0 - 122
Command/DumpActionRolesCommand.php

@@ -1,122 +0,0 @@
-<?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\Routing\RouteCollection;
-use Symfony\Component\Routing\Route;
-use Symfony\Component\Config\Resource\FileResource;
-
-class DumpActionRolesCommand extends Command
-{
-    public function configure()
-    {
-        $this->setName('sonata:admin:dump-action-roles');
-        $this->setDescription('Dumps a set of access control rules for the classes');
-        $this->addOption('format', null, InputOption::VALUE_OPTIONAL, 'define the output format', 'yaml');
-        $this->addOption('prefix', null, InputOption::VALUE_OPTIONAL, 'define the admin route prefix', '/admin');
-        $this->setHelp(<<<EOF
-Dumps a role hierachy and a set of access control rules using a different role
-for each admin actions.
-EOF
-            );
-    }
-
-    public function execute(InputInterface $input, OutputInterface $output)
-    {
-        $infos = array();
-        foreach ($this->getAdminRoutesCollection($input->getOption('prefix'))->all() as $route) {
-            $compiledRoute = $route->compile();
-
-            $regex = str_replace(array("\n", ' '), '', $compiledRoute->getRegex());
-            if ($pos = strpos($regex, '/$')) {
-                $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2);
-            }
-
-            $defaults = $route->getDefaults();
-
-            $controllerInfos = explode(':', $defaults['_controller']);
-
-            $group = strtoupper(sprintf('ROLE_%s', str_replace(array('.','|'), '_', strtoupper($defaults['_sonata_admin']))));
-            if (!isset($infos[$group])) {
-                $infos[$group] = array();
-            }
-
-            $name = strtoupper(sprintf('ROLE_%s_%s',
-                str_replace(array('.','|'), '_', strtoupper($defaults['_sonata_admin'])),
-                $controllerInfos[2]
-            ));
-
-            $infos[$group][] = array(
-                'path' => substr($regex, 1, -2),
-                'roles' => $name
-            );
-        }
-
-        $this->dumpYaml($output, $infos);
-    }
-
-    public function dumpYaml(OutputInterface $output, array $infos)
-    {
-
-        $output->writeln('security:');
-        $output->writeln('    access_control:');
-        foreach ($infos as $groups) {
-            foreach ($groups as $group) {
-                $output->writeln(sprintf('        - { path: %s, roles: [%s], methods: null }', $group['path'], $group['roles']));
-            }
-        }
-
-        $output->writeln('');
-        $output->writeln('    role_hierarchy:');
-
-        $superAdmin = array();
-        foreach ($infos as $groupName => $groups) {
-            $roles = array();
-            foreach ($groups as $group) {
-                $roles[] = $group['roles'];
-            }
-            $output->writeln(sprintf('        %s: [%s] ', $groupName, implode(', ', $roles)));
-
-            $superAdmin[] = $groupName;
-        }
-
-        $output->writeln(sprintf('        ROLE_SONATA_ADMIN_ROOT: [%s] ', implode(', ', $superAdmin)));
-    }
-
-    public function getAdminRoutesCollection($prefix)
-    {
-        $pool = $this->container->get('sonata.admin.pool');
-        $collection = new RouteCollection;
-
-        foreach ($pool->getAdminServiceIds() as $id) {
-
-            $admin = $pool->getInstance($id);
-
-            foreach ($admin->getRoutes()->getElements() as $code => $route) {
-                $collection->add($route->getDefault('_sonata_name'), $route);
-            }
-
-            $reflection = new \ReflectionObject($admin);
-            $collection->addResource(new FileResource($reflection->getFileName()));
-        }
-
-        $collection->addPrefix($prefix);
-        return $collection;
-    }
-}

+ 0 - 1
Command/ListAdminCommand.php

@@ -29,7 +29,6 @@ class ListAdminCommand extends Command
 
 
     public function execute(InputInterface $input, OutputInterface $output)
     public function execute(InputInterface $input, OutputInterface $output)
     {
     {
-
         $pool = $this->container->get('sonata.admin.pool');
         $pool = $this->container->get('sonata.admin.pool');
 
 
         $output->writeln("<info>Admin services:</info>");
         $output->writeln("<info>Admin services:</info>");

+ 2 - 2
Resources/doc/conf.py

@@ -92,7 +92,7 @@ pygments_style = 'sphinx'
 
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 # a list of builtin themes.
-html_theme = 'default'
+html_theme = 'basic'
 
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # further.  For a list of options available for each theme, see the
@@ -100,7 +100,7 @@ html_theme = 'default'
 #html_theme_options = {}
 #html_theme_options = {}
 
 
 # Add any paths that contain custom themes here, relative to this directory.
 # Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+html_theme_path = ['/Users/thomas/Projects/']
 
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
 # "<project> v<release> documentation".

+ 1 - 0
Resources/doc/index.rst

@@ -24,6 +24,7 @@ Reference Guide
    reference/saving_hooks
    reference/saving_hooks
    reference/routing
    reference/routing
    reference/dashboard
    reference/dashboard
+   reference/security
 
 
 Doctrine ORM
 Doctrine ORM
 ------------
 ------------

+ 1 - 0
Resources/doc/reference/installation.rst

@@ -74,3 +74,4 @@ At this point you can access to the dashboard with the url:
     the above configuration and routing will actually be placed in those
     the above configuration and routing will actually be placed in those
     files, with the correct format (i.e. XML or PHP).
     files, with the correct format (i.e. XML or PHP).
 
 
+The last important step is security, please refer to the dedicated section.

+ 126 - 0
Resources/doc/reference/security.rst

@@ -0,0 +1,126 @@
+Security
+========
+
+The current ``AdminBundle`` implementation uses ACL and ROLES to handle permissions.
+
+If you want an easy way to handle users, please use :
+
+ - https://github.com/FriendsOfSymfony/UserBundle : handle users and group stored from RDMS or MongoDB
+ - https://github.com/sonata-project/UserBundle : integrate the ``FriendsOfSymfony/UserBundle`` with
+   the ``AdminBundle``
+
+The security integration is a work in progress and have some knows issues :
+ - ACL permissions are immutables
+ - Only one PermissionMap can be defined
+
+
+Configuration
+-------------
+
+
+    - The following configuration defines :
+
+        - the ``FriendsOfSymfony/UserBundle`` as a security provider
+        - the login form for authentification
+        - the access control : resources with related required roles, the important part is the admin configuration
+        - the ``acl`` option enable the ACL.
+
+.. code-block:: yaml
+
+    parameters:
+        # ... other parameters
+        security.acl.permission.map.class: Sonata\AdminBundle\Security\Acl\Permission\AdminPermissionMap
+
+    security:
+        providers:
+            fos_userbundle:
+                id: fos_user.user_manager
+
+        firewalls:
+            main:
+                pattern:      .*
+                form-login:
+                    provider:       fos_userbundle
+                    login_path:     /login
+                    use_forward:    false
+                    check_path:     /login_check
+                    failure_path:   null
+                logout:       true
+                anonymous:    true
+
+        access_control:
+            # The WDT has to be allowed to anonymous users to avoid requiring the login with the AJAX request
+            - { path: ^/wdt/, role: IS_AUTHENTICATED_ANONYMOUSLY }
+            - { path: ^/profiler/, role: IS_AUTHENTICATED_ANONYMOUSLY }
+
+            # AsseticBundle paths used when using the controller for assets
+            - { path: ^/js/, role: IS_AUTHENTICATED_ANONYMOUSLY }
+            - { path: ^/css/, role: IS_AUTHENTICATED_ANONYMOUSLY }
+
+            # URL of FOSUserBundle which need to be available to anonymous users
+            - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
+            - { path: ^/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY } # for the case of a failed login
+            - { path: ^/user/new$, role: IS_AUTHENTICATED_ANONYMOUSLY }
+            - { path: ^/user/check-confirmation-email$, role: IS_AUTHENTICATED_ANONYMOUSLY }
+            - { path: ^/user/confirm/, role: IS_AUTHENTICATED_ANONYMOUSLY }
+            - { path: ^/user/confirmed$, role: IS_AUTHENTICATED_ANONYMOUSLY }
+            - { path: ^/user/request-reset-password$, role: IS_AUTHENTICATED_ANONYMOUSLY }
+            - { path: ^/user/send-resetting-email$, role: IS_AUTHENTICATED_ANONYMOUSLY }
+            - { path: ^/user/check-resetting-email$, role: IS_AUTHENTICATED_ANONYMOUSLY }
+            - { path: ^/user/reset-password/, role: IS_AUTHENTICATED_ANONYMOUSLY }
+
+            # Secured part of the site
+            # This config requires being logged for the whole site and having the admin role for the admin part.
+            # Change these rules to adapt them to your needs
+            - { path: ^/admin/, role: ROLE_ADMIN }
+            - { path: ^/.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
+
+
+        role_hierarchy:
+            ROLE_ADMIN:       ROLE_USER
+            ROLE_SUPERADMIN: [ROLE_ADMIN, ROLE_SONATA_ADMIN, ROLE_ALLOWED_TO_SWITCH]
+
+        acl:
+            connection: default
+
+- Install the ACL tables ``php app/console init:acl``
+
+- Create a new user :
+
+.. code-block::
+
+    # php app/console fos:user:create
+    Please choose a username:root
+    Please choose an email:root@domain.com
+    Please choose a password:root
+    Created user root
+
+
+- Promote an user as super admin :
+
+.. code-block::
+
+    # php app/console fos:user:promote root
+    User "root" has been promoted as a super administrator.
+
+If you have Admin classes, you can install the related CRUD ACL rules :
+
+.. code-block::
+
+    # php app/console sonata:admin:setup-acl
+    Starting ACL AdminBundle configuration
+    > install ACL for sonata.media.admin.media
+       - add role: ROLE_SONATA_MEDIA_ADMIN_MEDIA_EDIT, ACL: ["EDIT"]
+       - add role: ROLE_SONATA_MEDIA_ADMIN_MEDIA_LIST, ACL: ["LIST"]
+       - add role: ROLE_SONATA_MEDIA_ADMIN_MEDIA_CREATE, ACL: ["CREATE"]
+       - add role: ROLE_SONATA_MEDIA_ADMIN_MEDIA_DELETE, ACL: ["DELETE"]
+       - add role: ROLE_SONATA_MEDIA_ADMIN_MEDIA_OPERATOR, ACL: ["OPERATOR"]
+    ... skipped ...
+
+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.

+ 6 - 2
Resources/views/CRUD/action.html.twig

@@ -14,8 +14,12 @@ file that was distributed with this source code.
 {% block actions %}
 {% block actions %}
     <div class="sonata-actions">
     <div class="sonata-actions">
         <ul>
         <ul>
-            <li class="sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
-            <li class="sonata-action-element"><a href="{{ admin.generateUrl('list') }}">{% trans from 'SonataAdminBundle' %}link_action_list{% endtrans %}</a></li>
+            {% if admin.isGranted('CREATE')%}
+                <li class="sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
+            {% endif %}
+            {% if admin.isGranted('LIST')%}
+                <li class="sonata-action-element"><a href="{{ admin.generateUrl('list') }}">{% trans from 'SonataAdminBundle' %}link_action_list{% endtrans %}</a></li>
+            {% endif %}
         </ul>
         </ul>
     </div>
     </div>
 {% endblock %}
 {% endblock %}

+ 6 - 2
Resources/views/CRUD/base_edit.html.twig

@@ -14,8 +14,12 @@ file that was distributed with this source code.
 {% block actions %}
 {% block actions %}
     <div class="sonata-actions">
     <div class="sonata-actions">
         <ul>
         <ul>
-            <li class="sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
-            <li class="sonata-action-element"><a href="{{ admin.generateUrl('list') }}">{% trans from 'SonataAdminBundle' %}link_action_list{% endtrans %}</a></li>
+            {% if admin.isGranted('CREATE')%}
+                <li class="sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
+            {% endif %}
+            {% if admin.isGranted('LIST')%}
+                <li class="sonata-action-element"><a href="{{ admin.generateUrl('list') }}">{% trans from 'SonataAdminBundle' %}link_action_list{% endtrans %}</a></li>
+            {% endif %}
         </ul>
         </ul>
     </div>
     </div>
 {% endblock %}
 {% endblock %}

+ 3 - 1
Resources/views/CRUD/base_list.html.twig

@@ -14,7 +14,9 @@ file that was distributed with this source code.
 {% block actions %}
 {% block actions %}
     <div class="sonata-actions">
     <div class="sonata-actions">
         <ul>
         <ul>
-            <li class="sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
+            {% if admin.isGranted('CREATE')%}
+                <li class="sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
+            {% endif %}
         </ul>
         </ul>
     </div>
     </div>
 {% endblock %}
 {% endblock %}

+ 1 - 1
Resources/views/CRUD/base_list_field.html.twig

@@ -10,7 +10,7 @@ file that was distributed with this source code.
 #}
 #}
 
 
 <td class="sonata-ba-list-field sonata-ba-list-field-{{ field_description.type }}" objectId="{{ object.id }}">
 <td class="sonata-ba-list-field sonata-ba-list-field-{{ field_description.type }}" objectId="{{ object.id }}">
-    {% if field_description.options.identifier is defined %}
+    {% if field_description.options.identifier is defined and admin.isGranted('EDIT') %}
         <a href="{{ admin.generateUrl('edit', {'id': object.id}) }}">
         <a href="{{ admin.generateUrl('edit', {'id': object.id}) }}">
             {% block field %}{{ value }}{% endblock %}
             {% block field %}{{ value }}{% endblock %}
         </a>
         </a>

+ 3 - 1
Resources/views/CRUD/edit_orm_many_to_many.html.twig

@@ -21,7 +21,9 @@ file that was distributed with this source code.
             <a
             <a
                 href="{{ field_description.associationadmin.generateUrl('create') }}"
                 href="{{ field_description.associationadmin.generateUrl('create') }}"
                 onclick="start_field_dialog_form_add_{{ field_element.vars.id }}(event)"
                 onclick="start_field_dialog_form_add_{{ field_element.vars.id }}(event)"
-                class="sonata-ba-action">
+                class="sonata-ba-action"
+                style="{% if not field_description.associationadmin.isGranted('CREATE')%}display:none{% endif %}"
+                >
                     <img
                     <img
                         src="{{ asset('bundles/sonataadmin/famfamfam/add.png') }}"
                         src="{{ asset('bundles/sonataadmin/famfamfam/add.png') }}"
                         alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
                         alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"

+ 6 - 2
Resources/views/CRUD/edit_orm_many_to_one.html.twig

@@ -45,7 +45,9 @@ file that was distributed with this source code.
                 {% if field_description.options.edit == 'list' %}
                 {% if field_description.options.edit == 'list' %}
                     <a  href="{{ field_description.associationadmin.generateUrl('list') }}"
                     <a  href="{{ field_description.associationadmin.generateUrl('list') }}"
                         onclick="start_field_dialog_form_list_{{ field_element.vars.id }}(event)"
                         onclick="start_field_dialog_form_list_{{ field_element.vars.id }}(event)"
-                        class="sonata-ba-action">
+                        class="sonata-ba-action"
+                        style="{% if not field_description.associationadmin.isGranted('LIST')%}display:none{% endif %}"
+                        >
                         <img src="{{ asset('bundles/sonataadmin/famfamfam/application_view_list.png') }}"
                         <img src="{{ asset('bundles/sonataadmin/famfamfam/application_view_list.png') }}"
                              alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
                              alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
                         />
                         />
@@ -54,7 +56,9 @@ file that was distributed with this source code.
 
 
                 <a  href="{{ field_description.associationadmin.generateUrl('create') }}"
                 <a  href="{{ field_description.associationadmin.generateUrl('create') }}"
                     onclick="start_field_dialog_form_add_{{ field_element.vars.id }}(event)"
                     onclick="start_field_dialog_form_add_{{ field_element.vars.id }}(event)"
-                    class="sonata-ba-action">
+                    class="sonata-ba-action"
+                    style="{% if not field_description.associationadmin.isGranted('CREATE')%}display:none{% endif %}"
+                    >
                         <img src="{{ asset('bundles/sonataadmin/famfamfam/add.png') }}"
                         <img src="{{ asset('bundles/sonataadmin/famfamfam/add.png') }}"
                             alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
                             alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
                         />
                         />

+ 6 - 2
Resources/views/CRUD/edit_orm_one_to_many.html.twig

@@ -70,7 +70,9 @@ file that was distributed with this source code.
                 <a
                 <a
                     href="{{ field_description.associationadmin.generateUrl('create') }}"
                     href="{{ field_description.associationadmin.generateUrl('create') }}"
                     onclick="start_field_retrieve_{{ field_element.vars.id }}(event)"
                     onclick="start_field_retrieve_{{ field_element.vars.id }}(event)"
-                    class="sonata-ba-action">
+                    class="sonata-ba-action"
+                    style="{% if not field_description.associationadmin.isGranted('CREATE')%}display:none{% endif %}"
+                    >
                     <img
                     <img
                         src="{{ asset('bundles/sonataadmin/famfamfam/add.png') }}"
                         src="{{ asset('bundles/sonataadmin/famfamfam/add.png') }}"
                         alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
                         alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
@@ -121,7 +123,9 @@ file that was distributed with this source code.
                 <a
                 <a
                     href="{{ field_description.associationadmin.generateUrl('create') }}"
                     href="{{ field_description.associationadmin.generateUrl('create') }}"
                     onclick="start_field_dialog_form_add_{{ field_element.vars.id }}(event)"
                     onclick="start_field_dialog_form_add_{{ field_element.vars.id }}(event)"
-                    class="sonata-ba-action">
+                    class="sonata-ba-action"
+                    style="{% if not field_description.associationadmin.isGranted('CREATE')%}display:none{% endif %}"
+                    >
                     <img
                     <img
                         src="{{ asset('bundles/sonataadmin/famfamfam/add.png') }}"
                         src="{{ asset('bundles/sonataadmin/famfamfam/add.png') }}"
                         alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
                         alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"

+ 6 - 2
Resources/views/CRUD/edit_orm_one_to_one.html.twig

@@ -44,7 +44,9 @@ file that was distributed with this source code.
                 {% if field_description.options.edit == 'list' %}
                 {% if field_description.options.edit == 'list' %}
                     <a  href="{{ field_description.associationadmin.generateUrl('list') }}"
                     <a  href="{{ field_description.associationadmin.generateUrl('list') }}"
                         onclick="start_field_dialog_form_list_{{ field_element.vars.id }}(event)"
                         onclick="start_field_dialog_form_list_{{ field_element.vars.id }}(event)"
-                        class="sonata-ba-action">
+                        class="sonata-ba-action"
+                        style="{% if not field_description.associationadmin.isGranted('LIST')%}display:none{% endif %}"
+                        >
                             <img
                             <img
                                 src="{{ asset('bundles/sonataadmin/famfamfam/application_view_list.png') }}"
                                 src="{{ asset('bundles/sonataadmin/famfamfam/application_view_list.png') }}"
                                 alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
                                 alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
@@ -54,7 +56,9 @@ file that was distributed with this source code.
 
 
                 <a  href="{{ field_description.associationadmin.generateUrl('create') }}"
                 <a  href="{{ field_description.associationadmin.generateUrl('create') }}"
                     onclick="start_field_dialog_form_add_{{ field_element.vars.id }}(event)"
                     onclick="start_field_dialog_form_add_{{ field_element.vars.id }}(event)"
-                    class="sonata-ba-action">
+                    class="sonata-ba-action"
+                    style="{% if not field_description.associationadmin.isGranted('CREATE')%}display:none{% endif %}"
+                    >
                         <img
                         <img
                             src="{{ asset('bundles/sonataadmin/famfamfam/add.png') }}"
                             src="{{ asset('bundles/sonataadmin/famfamfam/add.png') }}"
                             alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"
                             alt="{% trans from 'SonataAdminBundle' %}btn_add{% endtrans %}"

+ 5 - 3
Resources/views/CRUD/list__action_delete.html.twig

@@ -1,3 +1,5 @@
-<a href="{{ admin.generateUrl('delete', {'id': object.id}) }}" class="delete_link" title="{% trans from 'SonataAdminBundle' %}action_delete{% endtrans %}">
-    <img src="{{ asset('bundles/sonataadmin/famfamfam/delete.png') }}" alt="{% trans from 'SonataAdminBundle' %}action_delete{% endtrans %}" />
-</a>
+{% if admin.isGranted('DELETE')%}
+    <a href="{{ admin.generateUrl('delete', {'id': object.id}) }}" class="delete_link" title="{% trans from 'SonataAdminBundle' %}action_delete{% endtrans %}">
+        <img src="{{ asset('bundles/sonataadmin/famfamfam/delete.png') }}" alt="{% trans from 'SonataAdminBundle' %}action_delete{% endtrans %}" />
+    </a>
+{% endif %}

+ 5 - 3
Resources/views/CRUD/list__action_edit.html.twig

@@ -1,3 +1,5 @@
-<a href="{{ admin.generateUrl('edit', {'id': object.id}) }}" class="edit_link" title="{% trans from 'SonataAdminBundle' %}action_edit{% endtrans %}">
-    <img src="{{ asset('bundles/sonataadmin/famfamfam/page_white_edit.png') }}" alt="{% trans from 'SonataAdminBundle' %}action_edit{% endtrans %}" />
-</a>
+{% if admin.isGranted('EDIT')%}
+    <a href="{{ admin.generateUrl('edit', {'id': object.id}) }}" class="edit_link" title="{% trans from 'SonataAdminBundle' %}action_edit{% endtrans %}">
+        <img src="{{ asset('bundles/sonataadmin/famfamfam/page_white_edit.png') }}" alt="{% trans from 'SonataAdminBundle' %}action_edit{% endtrans %}" />
+    </a>
+{% endif %}

+ 5 - 1
Resources/views/CRUD/list_orm_many_to_one.html.twig

@@ -13,6 +13,10 @@ file that was distributed with this source code.
 
 
 {% block field%}
 {% block field%}
     {% if value %}
     {% if value %}
-        <a href="{{ field_description.associationadmin.generateUrl('edit', {'id': value.id}) }}">{{ value }}</a>
+        {% if field_description.associationadmin.isGranted('EDIT')%}
+            <a href="{{ field_description.associationadmin.generateUrl('edit', {'id': value.id}) }}">{{ value }}</a>
+        {% else %}
+            {{ value }}
+        {% endif %}
     {% endif %}
     {% endif %}
 {% endblock %}
 {% endblock %}

+ 5 - 1
Resources/views/CRUD/list_orm_one_to_one.html.twig

@@ -14,6 +14,10 @@ file that was distributed with this source code.
 {% block field%}
 {% block field%}
 
 
     {% if value.id %}
     {% if value.id %}
-        <a href="{{ field_description.associationadmin.generateUrl('edit', {'id': value.id}) }}">{{ value }}</a>
+        {% if field_description.associationadmin.isGranted('EDIT')%}
+            <a href="{{ field_description.associationadmin.generateUrl('edit', {'id': value.id}) }}">{{ value }}</a>
+        {% else %}
+            {{ value }}
+        {% endif %}
     {% endif %}
     {% endif %}
 {% endblock %}
 {% endblock %}

+ 93 - 0
Security/Acl/Permission/AdminPermissionMap.php

@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Security\Acl\Permission;
+
+use Symfony\Component\Security\Acl\Permission\PermissionMapInterface;
+
+/**
+ * This is basic permission map complements the masks which have been defined
+ * on the standard implementation of the MaskBuilder.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ * @author Thomas Rabaix <thomas.rabaix@gmail.com>
+ */
+class AdminPermissionMap implements PermissionMapInterface
+{
+    const PERMISSION_VIEW        = 'VIEW';
+    const PERMISSION_EDIT        = 'EDIT';
+    const PERMISSION_CREATE      = 'CREATE';
+    const PERMISSION_DELETE      = 'DELETE';
+    const PERMISSION_UNDELETE    = 'UNDELETE';
+    const PERMISSION_OPERATOR    = 'OPERATOR';
+    const PERMISSION_MASTER      = 'MASTER';
+    const PERMISSION_OWNER       = 'OWNER';
+
+    const PERMISSION_LIST        = 'LIST';
+
+    private $map = array(
+        self::PERMISSION_LIST => array(
+            MaskBuilder::MASK_LIST
+        ),
+
+        self::PERMISSION_VIEW => array(
+            MaskBuilder::MASK_VIEW,
+        ),
+
+        self::PERMISSION_EDIT => array(
+            MaskBuilder::MASK_EDIT,
+        ),
+
+        self::PERMISSION_CREATE => array(
+            MaskBuilder::MASK_CREATE,
+        ),
+
+        self::PERMISSION_DELETE => array(
+            MaskBuilder::MASK_DELETE,
+        ),
+
+        self::PERMISSION_UNDELETE => array(
+            MaskBuilder::MASK_UNDELETE,
+        ),
+
+        self::PERMISSION_OPERATOR => array(
+            MaskBuilder::MASK_OPERATOR,
+        ),
+
+        self::PERMISSION_MASTER => array(
+            MaskBuilder::MASK_MASTER,
+        ),
+
+        self::PERMISSION_OWNER => array(
+            MaskBuilder::MASK_OWNER,
+        ),
+    );
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getMasks($permission, $object)
+    {
+        if (!isset($this->map[$permission])) {
+            return null;
+        }
+
+        return $this->map[$permission];
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function contains($permission)
+    {
+        return isset($this->map[$permission]);
+    }
+}