Browse Source

Merge pull request #2257 from pulzarraider/controller_csrf_test

Improved CRUDController tests and doc blocks
Thomas 10 years ago
parent
commit
d5b8fa79ad
2 changed files with 312 additions and 79 deletions
  1. 73 60
      Controller/CRUDController.php
  2. 239 19
      Tests/Controller/CRUDControllerTest.php

+ 73 - 60
Controller/CRUDController.php

@@ -23,17 +23,20 @@ use Symfony\Component\HttpFoundation\Request;
 use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
 use Sonata\AdminBundle\Admin\BaseFieldDescription;
 use Sonata\AdminBundle\Util\AdminObjectAclData;
+use Sonata\AdminBundle\Admin\AdminInterface;
 
 class CRUDController extends Controller
 {
     /**
      * The related Admin class
      *
-     * @var \Sonata\AdminBundle\Admin\AdminInterface
+     * @var AdminInterface
      */
     protected $admin;
 
     /**
+     * Render JSON
+     *
      * @param mixed   $data
      * @param integer $status
      * @param array   $headers
@@ -56,8 +59,9 @@ class CRUDController extends Controller
     }
 
     /**
+     * Returns true if the request is a XMLHttpRequest.
      *
-     * @return boolean true if the request is done by an ajax like query
+     * @return bool True if the request is an XMLHttpRequest, false otherwise
      */
     protected function isXmlHttpRequest()
     {
@@ -96,7 +100,6 @@ class CRUDController extends Controller
      * Contextualize the admin class depends on the current request
      *
      * @throws \RuntimeException
-     * @return void
      */
     protected function configure()
     {
@@ -129,9 +132,9 @@ class CRUDController extends Controller
     }
 
     /**
-     * return the base template name
+     * Returns the base template name
      *
-     * @return string the template name
+     * @return string The template name
      */
     protected function getBaseTemplate()
     {
@@ -143,11 +146,7 @@ class CRUDController extends Controller
     }
 
     /**
-     * @param string   $view
-     * @param array    $parameters
-     * @param Response $response
-     *
-     * @return Response
+     * {@inheritdoc}
      */
     public function render($view, array $parameters = array(), Response $response = null)
     {
@@ -159,11 +158,11 @@ class CRUDController extends Controller
     }
 
     /**
-     * return the Response object associated to the list action
-     *
-     * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
+     * List action
      *
      * @return Response
+     *
+     * @throws AccessDeniedException If access is not granted
      */
     public function listAction()
     {
@@ -186,13 +185,13 @@ class CRUDController extends Controller
     }
 
     /**
-     * execute a batch delete
+     * Execute a batch delete
      *
-     * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
+     * @param ProxyQueryInterface $query
      *
-     * @param \Sonata\AdminBundle\Datagrid\ProxyQueryInterface $query
+     * @return RedirectResponse
      *
-     * @return \Symfony\Component\HttpFoundation\RedirectResponse
+     * @throws AccessDeniedException If access is not granted
      */
     public function batchActionDelete(ProxyQueryInterface $query)
     {
@@ -212,11 +211,14 @@ class CRUDController extends Controller
     }
 
     /**
-     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException|\Symfony\Component\Security\Core\Exception\AccessDeniedException
+     * Delete action
      *
-     * @param mixed $id
+     * @param int|string|null $id
      *
      * @return Response|RedirectResponse
+     *
+     * @throws NotFoundHttpException If the object does not exist
+     * @throws AccessDeniedException If access is not granted
      */
     public function deleteAction($id)
     {
@@ -278,15 +280,14 @@ class CRUDController extends Controller
     }
 
     /**
-     * return the Response object associated to the edit action
-     *
+     * Edit action
      *
-     * @param mixed $id
+     * @param int|string|null $id
      *
-     * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
-     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+     * @return Response|RedirectResponse
      *
-     * @return Response
+     * @throws NotFoundHttpException If the object does not exist
+     * @throws AccessDeniedException If access is not granted
      */
     public function editAction($id = null)
     {
@@ -357,11 +358,11 @@ class CRUDController extends Controller
     }
 
     /**
-     * redirect the user depend on this choice
+     * Redirect the user depend on this choice
      *
      * @param object $object
      *
-     * @return Response
+     * @return RedirectResponse
      */
     protected function redirectTo($object)
     {
@@ -394,10 +395,12 @@ class CRUDController extends Controller
     }
 
     /**
-     * return the Response object associated to the batch action
+     * Batch action
      *
-     * @throws \RuntimeException
-     * @return Response
+     * @return Response|RedirectResponse
+     *
+     * @throws NotFoundHttpException If the HTTP method is not POST
+     * @throws \RuntimeException     If the batch action is not defined
      */
     public function batchAction()
     {
@@ -496,10 +499,11 @@ class CRUDController extends Controller
     }
 
     /**
-     * return the Response object associated to the create action
+     * Create action
      *
-     * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
      * @return Response
+     *
+     * @throws AccessDeniedException If access is not granted
      */
     public function createAction()
     {
@@ -572,7 +576,7 @@ class CRUDController extends Controller
     /**
      * Returns true if the preview is requested to be shown
      *
-     * @return boolean
+     * @return bool
      */
     protected function isPreviewRequested()
     {
@@ -582,7 +586,7 @@ class CRUDController extends Controller
     /**
      * Returns true if the preview has been approved
      *
-     * @return boolean
+     * @return bool
      */
     protected function isPreviewApproved()
     {
@@ -595,7 +599,7 @@ class CRUDController extends Controller
      * That means either a preview is requested or the preview has already been shown
      * and it got approved/declined.
      *
-     * @return boolean
+     * @return bool
      */
     protected function isInPreviewMode()
     {
@@ -608,7 +612,7 @@ class CRUDController extends Controller
     /**
      * Returns true if the preview has been declined
      *
-     * @return boolean
+     * @return bool
      */
     protected function isPreviewDeclined()
     {
@@ -616,14 +620,14 @@ class CRUDController extends Controller
     }
 
     /**
-     * return the Response object associated to the view action
-     *
-     * @param null $id
+     * Show action
      *
-     * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
-     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+     * @param int|string|null $id
      *
      * @return Response
+     *
+     * @throws NotFoundHttpException If the object does not exist
+     * @throws AccessDeniedException If access is not granted
      */
     public function showAction($id = null)
     {
@@ -649,11 +653,14 @@ class CRUDController extends Controller
     }
 
     /**
-     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException|\Symfony\Component\Security\Core\Exception\AccessDeniedException
+     * Show history revisions for object
      *
-     * @param mixed $id
+     * @param int|string|null $id
      *
      * @return Response
+     *
+     * @throws AccessDeniedException If access is not granted
+     * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
      */
     public function historyAction($id = null)
     {
@@ -687,13 +694,15 @@ class CRUDController extends Controller
     }
 
     /**
-     * @param null   $id
-     * @param string $revision
+     * View history revision of object
      *
-     * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
-     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+     * @param int|string|null $id
+     * @param string|null     $revision
      *
      * @return Response
+     *
+     * @throws AccessDeniedException If access is not granted
+     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
      */
     public function historyViewRevisionAction($id = null, $revision = null)
     {
@@ -734,12 +743,14 @@ class CRUDController extends Controller
     }
 
     /**
-     * @param Request $request
+     * Export data to specified format
      *
-     * @throws \RuntimeException
-     * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
+     * @param Request $request
      *
      * @return Response
+     *
+     * @throws AccessDeniedException If access is not granted
+     * @throws \RuntimeException     If the export format is invalid
      */
     public function exportAction(Request $request)
     {
@@ -787,14 +798,14 @@ class CRUDController extends Controller
     }
 
     /**
-     * return the Response object associated to the acl action
+     * Returns the Response object associated to the acl action
      *
-     * @param null $id
+     * @param int|string|null $id
      *
-     * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
-     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+     * @return Response|RedirectResponse
      *
-     * @return Response
+     * @throws AccessDeniedException If access is not granted.
+     * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
      */
     public function aclAction($id = null)
     {
@@ -863,11 +874,11 @@ class CRUDController extends Controller
     }
 
     /**
-     * Validate CSRF token for action with out form
+     * Validate CSRF token for action without form
      *
      * @param string $intention
      *
-     * @throws \RuntimeException
+     * @throws HttpException
      */
     protected function validateCsrfToken($intention)
     {
@@ -876,14 +887,16 @@ class CRUDController extends Controller
         }
 
         if (!$this->container->get('form.csrf_provider')->isCsrfTokenValid($intention, $this->get('request')->request->get('_sonata_csrf_token', false))) {
-            throw new HttpException(400, "The csrf token is not valid, CSRF attack ?");
+            throw new HttpException(400, 'The csrf token is not valid, CSRF attack?');
         }
     }
 
     /**
-     * @param $intention
+     * Get CSRF token
+     *
+     * @param string $intention
      *
-     * @return string
+     * @return string|false
      */
     protected function getCsrfToken($intention)
     {

+ 239 - 19
Tests/Controller/CRUDControllerTest.php

@@ -26,6 +26,9 @@ use Sonata\AdminBundle\Util\AdminObjectAclManipulator;
 use Sonata\AdminBundle\Admin\FieldDescriptionCollection;
 use Sonata\AdminBundle\Tests\Fixtures\Controller\BatchAdminController;
 use Symfony\Component\HttpKernel\Kernel;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
+use Sonata\AdminBundle\Admin\AdminInterface;
 
 /**
  * Test for CRUDController
@@ -45,7 +48,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
     private $request;
 
     /**
-     * @var Sonata\AdminBundle\Admin\AdminInterface
+     * @var AdminInterface
      */
     private $admin;
 
@@ -89,6 +92,11 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
      */
     private $protectedTestedMethods;
 
+    /**
+     * @var CsrfProviderInterface
+     */
+    private $csrfProvider;
+
     /**
      * {@inheritDoc}
      */
@@ -141,7 +149,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $twig->expects($this->any())
             ->method('getExtension')
-            ->will($this->returnCallback(function($name) use ($formExtension) {
+            ->will($this->returnCallback(function ($name) use ($formExtension) {
                 switch ($name) {
                     case 'form':
                         return $formExtension;
@@ -168,6 +176,28 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $auditManager = $this->auditManager;
         $adminObjectAclManipulator = $this->adminObjectAclManipulator;
 
+        $this->csrfProvider = $this->getMockBuilder('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface')
+            ->getMock();
+
+        $this->csrfProvider->expects($this->any())
+            ->method('generateCsrfToken')
+            ->will($this->returnCallback(function ($intention) {
+                return 'csrf-token-123_'.$intention;
+            }));
+
+        $this->csrfProvider->expects($this->any())
+            ->method('isCsrfTokenValid')
+            ->will($this->returnCallback(function ($intention, $token) {
+                if ($token == 'csrf-token-123_'.$intention) {
+                    return true;
+                }
+
+                return false;
+            }));
+
+        // php 5.3 BC
+        $csrfProvider = $this->csrfProvider;
+
         $requestStack = null;
         if (Kernel::MINOR_VERSION > 3) {
             $requestStack = new \Symfony\Component\HttpFoundation\RequestStack();
@@ -176,7 +206,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->container->expects($this->any())
             ->method('get')
-            ->will($this->returnCallback(function($id) use ($pool, $request, $admin, $templating, $twig, $session, $exporter, $auditManager, $adminObjectAclManipulator, $requestStack) {
+            ->will($this->returnCallback(function ($id) use ($pool, $request, $admin, $templating, $twig, $session, $exporter, $auditManager, $adminObjectAclManipulator, $requestStack, $csrfProvider) {
                 switch ($id) {
                     case 'sonata.admin.pool':
                         return $pool;
@@ -198,14 +228,29 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
                         return $auditManager;
                     case 'sonata.admin.object.manipulator.acl.admin':
                         return $adminObjectAclManipulator;
+                    case 'form.csrf_provider':
+                        return $csrfProvider;
                 }
 
                 return null;
             }));
 
+        // php 5.3
+        $tthis = $this;
+
+        $this->container->expects($this->any())
+            ->method('has')
+            ->will($this->returnCallback(function ($id) use ($tthis) {
+                if ($id == 'form.csrf_provider' && $tthis->getCsrfProvider()!==null) {
+                    return true;
+                }
+
+                return false;
+            }));
+
         $this->admin->expects($this->any())
             ->method('getTemplate')
-            ->will($this->returnCallback(function($name) {
+            ->will($this->returnCallback(function ($name) {
                 switch ($name) {
                     case 'ajax':
                         return 'SonataAdminBundle::ajax_layout.html.twig';
@@ -331,7 +376,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->admin->expects($this->once())
             ->method('setUniqid')
-            ->will($this->returnCallback(function($uniqid) use (&$uniqueId) {
+            ->will($this->returnCallback(function ($uniqid) use (&$uniqueId) {
                 $uniqueId = $uniqid;
             }));
 
@@ -348,7 +393,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->admin->expects($this->once())
             ->method('setUniqid')
-            ->will($this->returnCallback(function($uniqid) use (&$uniqueId) {
+            ->will($this->returnCallback(function ($uniqid) use (&$uniqueId) {
                 $uniqueId = $uniqid;
             }));
 
@@ -493,7 +538,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals('list', $this->parameters['action']);
         $this->assertInstanceOf('Symfony\Component\Form\FormView', $this->parameters['form']);
         $this->assertInstanceOf('Sonata\AdminBundle\Datagrid\DatagridInterface', $this->parameters['datagrid']);
-        $this->assertEquals('', $this->parameters['csrf_token']);
+        $this->assertEquals('csrf-token-123_sonata.batch', $this->parameters['csrf_token']);
         $this->assertEquals(array(), $this->session->getFlashBag()->all());
         $this->assertEquals('SonataAdminBundle:CRUD:list.html.twig', $this->template);
     }
@@ -540,7 +585,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $modelManager->expects($this->once())
             ->method('batchDelete')
-            ->will($this->returnCallback(function() {
+            ->will($this->returnCallback(function () {
                     throw new ModelManagerException();
                 }));
 
@@ -702,6 +747,35 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals('SonataAdminBundle::standard_layout.html.twig', $this->parameters['base_template']);
         $this->assertEquals($this->pool, $this->parameters['admin_pool']);
 
+        $this->assertEquals('delete', $this->parameters['action']);
+        $this->assertEquals($object, $this->parameters['object']);
+        $this->assertEquals('csrf-token-123_sonata.delete', $this->parameters['csrf_token']);
+
+        $this->assertEquals(array(), $this->session->getFlashBag()->all());
+        $this->assertEquals('SonataAdminBundle:CRUD:delete.html.twig', $this->template);
+    }
+
+    public function testDeleteActionNoCsrfToken()
+    {
+        $this->csrfProvider = null;
+
+        $object = new \stdClass();
+
+        $this->admin->expects($this->once())
+            ->method('getObject')
+            ->will($this->returnValue($object));
+
+        $this->admin->expects($this->once())
+            ->method('isGranted')
+            ->with($this->equalTo('DELETE'))
+            ->will($this->returnValue(true));
+
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $this->controller->deleteAction(1));
+
+        $this->assertEquals($this->admin, $this->parameters['admin']);
+        $this->assertEquals('SonataAdminBundle::standard_layout.html.twig', $this->parameters['base_template']);
+        $this->assertEquals($this->pool, $this->parameters['admin_pool']);
+
         $this->assertEquals('delete', $this->parameters['action']);
         $this->assertEquals($object, $this->parameters['object']);
         $this->assertEquals('', $this->parameters['csrf_token']);
@@ -726,6 +800,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->request->setMethod('DELETE');
 
         $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
 
         $response = $this->controller->deleteAction(1);
 
@@ -749,6 +824,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->request->setMethod('POST');
         $this->request->request->set('_method', 'DELETE');
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
 
         $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
 
@@ -774,12 +850,13 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->admin->expects($this->once())
             ->method('delete')
-            ->will($this->returnCallback(function() {
+            ->will($this->returnCallback(function () {
                     throw new ModelManagerException();
                 }));
 
         $this->request->setMethod('DELETE');
 
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
         $this->request->headers->set('X-Requested-With', 'XMLHttpRequest');
 
         $response = $this->controller->deleteAction(1);
@@ -811,6 +888,8 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->request->setMethod('DELETE');
 
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
+
         $response = $this->controller->deleteAction(1);
 
         $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
@@ -836,6 +915,35 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->request->setMethod('POST');
         $this->request->request->set('_method', 'DELETE');
 
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
+
+        $response = $this->controller->deleteAction(1);
+
+        $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
+        $this->assertSame(array('flash_delete_success'), $this->session->getFlashBag()->get('sonata_flash_success'));
+        $this->assertEquals('list', $response->getTargetUrl());
+    }
+
+    public function testDeleteActionSuccessNoCsrfTokenProvider()
+    {
+        $this->csrfProvider = null;
+
+        $object = new \stdClass();
+
+        $this->admin->expects($this->once())
+            ->method('getObject')
+            ->will($this->returnValue($object));
+
+        $this->admin->expects($this->once())
+            ->method('isGranted')
+            ->with($this->equalTo('DELETE'))
+            ->will($this->returnValue(true));
+
+        $this->expectTranslate('flash_delete_success');
+
+        $this->request->setMethod('POST');
+        $this->request->request->set('_method', 'DELETE');
+
         $response = $this->controller->deleteAction(1);
 
         $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response);
@@ -867,7 +975,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals('delete', $this->parameters['action']);
         $this->assertEquals($object, $this->parameters['object']);
-        $this->assertEquals('', $this->parameters['csrf_token']);
+        $this->assertEquals('csrf-token-123_sonata.delete', $this->parameters['csrf_token']);
 
         $this->assertEquals(array(), $this->session->getFlashBag()->all());
         $this->assertEquals('SonataAdminBundle:CRUD:delete.html.twig', $this->template);
@@ -890,11 +998,12 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->admin->expects($this->once())
             ->method('delete')
-            ->will($this->returnCallback(function() {
+            ->will($this->returnCallback(function () {
                 throw new ModelManagerException();
             }));
 
         $this->request->setMethod('DELETE');
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.delete');
 
         $response = $this->controller->deleteAction(1);
 
@@ -903,6 +1012,31 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals('list', $response->getTargetUrl());
     }
 
+    public function testDeleteActionInvalidCsrfToken()
+    {
+        $object = new \stdClass();
+
+        $this->admin->expects($this->once())
+            ->method('getObject')
+            ->will($this->returnValue($object));
+
+        $this->admin->expects($this->once())
+            ->method('isGranted')
+            ->with($this->equalTo('DELETE'))
+            ->will($this->returnValue(true));
+
+        $this->request->setMethod('POST');
+        $this->request->request->set('_method', 'DELETE');
+        $this->request->request->set('_sonata_csrf_token', 'CSRF-INVALID');
+
+        try {
+            $this->controller->deleteAction(1);
+        } catch (HttpException $e) {
+            $this->assertEquals('The csrf token is not valid, CSRF attack?', $e->getMessage());
+            $this->assertEquals(400, $e->getStatusCode());
+        }
+    }
+
     public function testEditActionNotFoundException()
     {
         $this->setExpectedException('Symfony\Component\HttpKernel\Exception\NotFoundHttpException');
@@ -1258,12 +1392,21 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
     public function testCreateActionSuccess()
     {
+        $object = new \stdClass();
+
         $this->admin->expects($this->exactly(2))
             ->method('isGranted')
-            ->with($this->equalTo('CREATE'))
-            ->will($this->returnValue(true));
+            ->will($this->returnCallback(function ($name, $objectIn = null) use ($object) {
+                if ($name != 'CREATE') {
+                    return false;
+                }
 
-        $object = new \stdClass();
+                if ($objectIn === null) {
+                    return true;
+                }
+
+                return ($objectIn === $object);
+            }));
 
         $this->admin->expects($this->once())
             ->method('getNewInstance')
@@ -1296,6 +1439,46 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals('stdClass_edit', $response->getTargetUrl());
     }
 
+    public function testCreateActionAccessDenied2()
+    {
+        $this->setExpectedException('Symfony\Component\Security\Core\Exception\AccessDeniedException');
+
+        $object = new \stdClass();
+
+        $this->admin->expects($this->any())
+            ->method('isGranted')
+            ->will($this->returnCallback(function ($name, $object = null) {
+                if ($name != 'CREATE') {
+                    return false;
+                }
+                if ($object === null) {
+                    return true;
+                }
+
+                return false;
+            }));
+
+        $this->admin->expects($this->once())
+            ->method('getNewInstance')
+            ->will($this->returnValue($object));
+
+        $form = $this->getMockBuilder('Symfony\Component\Form\Form')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $this->admin->expects($this->once())
+            ->method('getForm')
+            ->will($this->returnValue($form));
+
+        $form->expects($this->once())
+            ->method('isValid')
+            ->will($this->returnValue(true));
+
+        $this->request->setMethod('POST');
+
+        $this->controller->createAction();
+    }
+
     public function testCreateActionError()
     {
         $this->admin->expects($this->once())
@@ -1347,12 +1530,21 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
     public function testCreateActionAjaxSuccess()
     {
+        $object = new \stdClass();
+
         $this->admin->expects($this->exactly(2))
             ->method('isGranted')
-            ->with($this->equalTo('CREATE'))
-            ->will($this->returnValue(true));
+            ->will($this->returnCallback(function ($name, $objectIn = null) use ($object) {
+                if ($name != 'CREATE') {
+                    return false;
+                }
 
-        $object = new \stdClass();
+                if ($objectIn === null) {
+                    return true;
+                }
+
+                return ($objectIn === $object);
+            }));
 
         $this->admin->expects($this->once())
             ->method('getNewInstance')
@@ -2074,10 +2266,25 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->request->setMethod('POST');
         $this->request->request->set('data', json_encode(array('action'=>'foo', 'idx'=>array('123', '456'), 'all_elements'=>false)));
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
 
         $this->controller->batchAction();
     }
 
+    public function testBatchActionActionInvalidCsrfToken()
+    {
+        $this->request->setMethod('POST');
+        $this->request->request->set('data', json_encode(array('action'=>'foo', 'idx'=>array('123', '456'), 'all_elements'=>false)));
+        $this->request->request->set('_sonata_csrf_token', 'CSRF-INVALID');
+
+        try {
+            $this->controller->batchAction();
+        } catch (HttpException $e) {
+            $this->assertEquals('The csrf token is not valid, CSRF attack?', $e->getMessage());
+            $this->assertEquals(400, $e->getStatusCode());
+        }
+    }
+
     public function testBatchActionMethodNotExist()
     {
         $this->setExpectedException('RuntimeException', 'A `Sonata\AdminBundle\Controller\CRUDController::batchActionFoo` method must be created');
@@ -2095,6 +2302,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->request->setMethod('POST');
         $this->request->request->set('data', json_encode(array('action'=>'foo', 'idx'=>array('123', '456'), 'all_elements'=>false)));
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
 
         $this->controller->batchAction();
     }
@@ -2140,6 +2348,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->request->setMethod('POST');
         $this->request->request->set('data', json_encode(array('action'=>'delete', 'idx'=>array('123', '456'), 'all_elements'=>false)));
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
 
         $result = $this->controller->batchAction();
 
@@ -2190,6 +2399,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->request->setMethod('POST');
         $this->request->request->set('action', 'delete');
         $this->request->request->set('idx', array('123', '456'));
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
 
         $result = $this->controller->batchAction();
 
@@ -2215,6 +2425,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
 
         $this->request->setMethod('POST');
         $this->request->request->set('data', json_encode($data));
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
 
         $datagrid = $this->getMock('\Sonata\AdminBundle\Datagrid\DatagridInterface');
 
@@ -2244,7 +2455,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals($datagrid, $this->parameters['datagrid']);
         $this->assertInstanceOf('Symfony\Component\Form\FormView', $this->parameters['form']);
         $this->assertEquals($data, $this->parameters['data']);
-        $this->assertEquals('', $this->parameters['csrf_token']);
+        $this->assertEquals('csrf-token-123_sonata.batch', $this->parameters['csrf_token']);
 
         $this->assertEquals(array(), $this->session->getFlashBag()->all());
         $this->assertEquals('SonataAdminBundle:CRUD:batch_confirmation.html.twig', $this->template);
@@ -2270,6 +2481,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->request->setMethod('POST');
         $this->request->request->set('action', 'foo');
         $this->request->request->set('idx', array('789'));
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
 
         $result = $controller->batchAction();
 
@@ -2298,6 +2510,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->request->setMethod('POST');
         $this->request->request->set('action', 'foo');
         $this->request->request->set('idx', array('999'));
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
 
         $result = $controller->batchAction();
 
@@ -2323,6 +2536,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->request->setMethod('POST');
         $this->request->request->set('action', 'delete');
         $this->request->request->set('idx', array());
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
 
         $result = $this->controller->batchAction();
 
@@ -2366,6 +2580,7 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->request->setMethod('POST');
         $this->request->request->set('action', 'bar');
         $this->request->request->set('idx', array());
+        $this->request->request->set('_sonata_csrf_token', 'csrf-token-123_sonata.batch');
 
         $result = $controller->batchAction();
 
@@ -2373,13 +2588,18 @@ class CRUDControllerTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals('batchActionBar executed', $result->getContent());
     }
 
+    public function getCsrfProvider()
+    {
+        return $this->csrfProvider;
+    }
+
     private function expectTranslate()
     {
         $args = func_get_args();
 
         // creates equalTo of all arguments passed to this function
         $phpunit = $this; // PHP 5.3 compatibility
-        $argsCheck = array_map(function($item) use ($phpunit) {
+        $argsCheck = array_map(function ($item) use ($phpunit) {
             return $phpunit->equalTo($item);
         }, func_get_args());