[TASK] Use &route=/ajax/ instead of &ajaxId for BE AJAX calls 59/53459/6
authorBenni Mack <benni@typo3.org>
Thu, 13 Jul 2017 14:38:44 +0000 (16:38 +0200)
committerSusanne Moog <susanne.moog@typo3.org>
Mon, 31 Jul 2017 09:29:11 +0000 (11:29 +0200)
In order to streamline backend usage even further, the AJAX Request Handler
is now using the "route" parameter the same way.

This way, the RouteDispatcher does not have to distinguish between the BE
calls anymore, and the Form Protections are streamlined.

Resolves: #81899
Releases: master
Change-Id: I48bf2406eaff2316d3f0fe5dc631a51067a570f6
Reviewed-on: https://review.typo3.org/53459
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Joerg Boesche <typo3@joergboesche.de>
Tested-by: Joerg Boesche <typo3@joergboesche.de>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
typo3/sysext/backend/Classes/Http/AjaxRequestHandler.php
typo3/sysext/backend/Classes/Http/Application.php
typo3/sysext/backend/Classes/Http/RouteDispatcher.php
typo3/sysext/backend/Classes/Routing/UriBuilder.php
typo3/sysext/backend/Resources/Public/JavaScript/LegacyTree.js
typo3/sysext/backend/Tests/Unit/Routing/UriBuilderTest.php
typo3/sysext/core/Documentation/Changelog/master/Important-81899-BackendAJAXRoutesUseRouteajaxInsteadOfAjaxIdParameter.rst [new file with mode: 0644]

index cedc02b..e2145c7 100644 (file)
@@ -26,7 +26,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 /**
  * AJAX dispatcher
  *
- * Main entry point for AJAX calls in the TYPO3 Backend. Based on ?ajaxId of the outside application.
+ * Main entry point for AJAX calls in the TYPO3 Backend. Based on ?route=/ajax/* of the outside application.
  * Before doing the basic BE-related set up of this request (see the additional calls on $this->bootstrap inside
  * handleRequest()), some AJAX-calls can be made without a valid user, which is determined here.
  *
@@ -44,7 +44,7 @@ class AjaxRequestHandler implements RequestHandlerInterface
      * List of requests that don't need a valid BE user
      * @var array
      */
-    protected $publicAjaxIds = [
+    protected $publicAjaxRoutes = [
         '/ajax/login',
         '/ajax/logout',
         '/ajax/login/refresh',
@@ -70,10 +70,11 @@ class AjaxRequestHandler implements RequestHandlerInterface
      */
     public function handleRequest(ServerRequestInterface $request)
     {
-        // First get the ajaxID
-        $ajaxID = isset($request->getParsedBody()['ajaxID']) ? $request->getParsedBody()['ajaxID'] : $request->getQueryParams()['ajaxID'];
-        $request = $request->withAttribute('routePath', $ajaxID);
-        $proceedIfNoUserIsLoggedIn = $this->isLoggedInBackendUserRequired($ajaxID);
+        // First get the name of the route
+        $routePath = isset($request->getParsedBody()['route']) ? $request->getParsedBody()['route'] : $request->getQueryParams()['route'];
+        $request = $request->withAttribute('routePath', $routePath);
+
+        $proceedIfNoUserIsLoggedIn = $this->isLoggedInBackendUserRequired($routePath);
         $this->boot($proceedIfNoUserIsLoggedIn);
 
         // Backend Routing - check if a valid route is there, and dispatch
@@ -82,14 +83,15 @@ class AjaxRequestHandler implements RequestHandlerInterface
 
     /**
      * This request handler can handle any backend request having
-     * an ajaxID as parameter (see Application.php in EXT:backend)
+     * an /ajax/ request
      *
      * @param ServerRequestInterface $request
      * @return bool If the request is an AJAX backend request, TRUE otherwise FALSE
      */
     public function canHandleRequest(ServerRequestInterface $request)
     {
-        return $request->getAttribute('isAjaxRequest', false);
+        $routePath = isset($request->getParsedBody()['route']) ? $request->getParsedBody()['route'] : $request->getQueryParams()['route'];
+        return strpos($routePath, '/ajax/') === 0;
     }
 
     /**
@@ -106,12 +108,12 @@ class AjaxRequestHandler implements RequestHandlerInterface
      * Check if the user is required for the request
      * If we're trying to do an ajax login, don't require a user
      *
-     * @param string $ajaxId the Ajax ID to check against
+     * @param string $routePath the Route path to check against, something like '
      * @return bool whether the request can proceed without a login required
      */
-    protected function isLoggedInBackendUserRequired($ajaxId)
+    protected function isLoggedInBackendUserRequired($routePath)
     {
-        return in_array($ajaxId, $this->publicAjaxIds, true);
+        return in_array($routePath, $this->publicAjaxRoutes, true);
     }
 
     /**
index 859e382..683a1b3 100644 (file)
@@ -59,7 +59,7 @@ class Application implements ApplicationInterface
 
         $this->bootstrap = Bootstrap::getInstance()
             ->initializeClassLoader($classLoader)
-            ->setRequestType(TYPO3_REQUESTTYPE_BE | (!empty($_GET['ajaxID']) ? TYPO3_REQUESTTYPE_AJAX : 0))
+            ->setRequestType(TYPO3_REQUESTTYPE_BE | (isset($_REQUEST['route']) && strpos($_REQUEST['route'], '/ajax/') === 0 ? TYPO3_REQUESTTYPE_AJAX : 0))
             ->baseSetup($this->entryPointLevel);
 
         // Redirect to install tool if base configuration is not found
@@ -82,10 +82,7 @@ class Application implements ApplicationInterface
     public function run(callable $execute = null)
     {
         $this->request = \TYPO3\CMS\Core\Http\ServerRequestFactory::fromGlobals();
-        // see below when this option is set and Bootstrap::defineTypo3RequestTypes() for more details
-        if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
-            $this->request = $this->request->withAttribute('isAjaxRequest', true);
-        } elseif (isset($this->request->getQueryParams()['M'])) {
+        if (isset($this->request->getQueryParams()['M'])) {
             $this->request = $this->request->withAttribute('isModuleRequest', true);
         }
 
index 66a32b8..957f85c 100644 (file)
@@ -78,12 +78,8 @@ class RouteDispatcher extends Dispatcher implements DispatcherInterface
         $route = $request->getAttribute('route');
         if ($route->getOption('access') === 'public') {
             return true;
-        } elseif ($route->getOption('ajax')) {
-            $token = (string)(isset($request->getParsedBody()['ajaxToken']) ? $request->getParsedBody()['ajaxToken'] : $request->getQueryParams()['ajaxToken']);
-            return $this->getFormProtection()->validateToken($token, 'ajaxCall', $route->getOption('_identifier'));
-        } else {
-            $token = (string)(isset($request->getParsedBody()['token']) ? $request->getParsedBody()['token'] : $request->getQueryParams()['token']);
-            return $this->getFormProtection()->validateToken($token, 'route', $route->getOption('_identifier'));
         }
+        $token = (string)(isset($request->getParsedBody()['token']) ? $request->getParsedBody()['token'] : $request->getQueryParams()['token']);
+        return $this->getFormProtection()->validateToken($token, 'route', $route->getOption('_identifier'));
     }
 }
index 7f546e5..93e5fad 100644 (file)
@@ -79,34 +79,18 @@ class UriBuilder
             $parameters
         );
 
-        // The Route is an AJAX route, so the parameters are different in order
-        // for the AjaxRequestHandler to be triggered
-        if ($route->getOption('ajax')) {
-            // If the route has the "public" option set, no token is generated.
-            if ($route->getOption('access') !== 'public') {
-                $parameters = [
-                    'ajaxToken' => FormProtectionFactory::get('backend')->generateToken('ajaxCall', $name)
-                ] + $parameters;
-            }
-
-            // Add the Route path as &ajaxID=XYZ
-            $parameters = [
-                'ajaxID' => $route->getPath()
-            ] + $parameters;
-        } else {
-            // If the route has the "public" option set, no token is generated.
-            if ($route->getOption('access') !== 'public') {
-                $parameters = [
-                    'token' => FormProtectionFactory::get('backend')->generateToken('route', $name)
-                ] + $parameters;
-            }
-
-            // Add the Route path as &route=XYZ
+        // If the route has the "public" option set, no token is generated.
+        if ($route->getOption('access') !== 'public') {
             $parameters = [
-                'route' => $route->getPath()
+                'token' => FormProtectionFactory::get('backend')->generateToken('route', $name)
             ] + $parameters;
         }
 
+        // Add the Route path as &route=XYZ
+        $parameters = [
+            'route' => $route->getPath()
+        ] + $parameters;
+
         return $this->buildUri($parameters, $referenceType);
     }
 
index 13074e4..2f1db74 100644 (file)
@@ -107,7 +107,7 @@ define(['jquery'], function($) {
        };
 
        Tree = {
-               ajaxID: 'sc_alt_file_navframe_expandtoggle',
+               ajaxRoute: 'sc_alt_file_navframe_expandtoggle',
                frameSetModule: null,
                activateDragDrop: true,
                highlightClass: 'active',
@@ -133,7 +133,7 @@ define(['jquery'], function($) {
                                $obj.css({cursor: 'wait'});
                        }
                        $.ajax({
-                               url: TYPO3.settings.ajaxUrls[this.ajaxID],
+                               url: TYPO3.settings.ajaxUrls[this.ajaxRoute],
                                data: {
                                        PM: params,
                                        scopeData: scopeData,
index 667c348..e4cf27d 100644 (file)
@@ -57,7 +57,7 @@ class UriBuilderTest extends UnitTestCase
                 [ 'route' => new Route('/test/route', [ 'ajax' => true ]) ],
                 'route',
                 [],
-                '/typo3/index.php?ajaxID=%2Ftest%2Froute&ajaxToken=dummyToken',
+                '/typo3/index.php?route=%2Ftest%2Froute&token=dummyToken',
             ],
             'plain route with default parameters' => [
                 [ 'route' => new Route('/test/route', [ 'parameters' => [ 'key' => 'value' ] ]) ],
@@ -69,7 +69,7 @@ class UriBuilderTest extends UnitTestCase
                 [ 'route' => new Route('/test/route', [ 'ajax' => true, 'parameters' => [ 'key' => 'value' ] ]) ],
                 'route',
                 [],
-                '/typo3/index.php?ajaxID=%2Ftest%2Froute&ajaxToken=dummyToken&key=value',
+                '/typo3/index.php?route=%2Ftest%2Froute&token=dummyToken&key=value',
             ],
             'plain route with overridden parameters' => [
                 [ 'route' => new Route('/test/route', [ 'parameters' => [ 'key' => 'value' ] ]) ],
@@ -81,7 +81,7 @@ class UriBuilderTest extends UnitTestCase
                 [ 'route' => new Route('/test/route', [ 'ajax' => true, 'parameters' => [ 'key' => 'value' ] ]) ],
                 'route',
                 ['key' => 'overridden'],
-                '/typo3/index.php?ajaxID=%2Ftest%2Froute&ajaxToken=dummyToken&key=overridden',
+                '/typo3/index.php?route=%2Ftest%2Froute&token=dummyToken&key=overridden',
             ],
         ];
     }
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Important-81899-BackendAJAXRoutesUseRouteajaxInsteadOfAjaxIdParameter.rst b/typo3/sysext/core/Documentation/Changelog/master/Important-81899-BackendAJAXRoutesUseRouteajaxInsteadOfAjaxIdParameter.rst
new file mode 100644 (file)
index 0000000..e5c9fbe
--- /dev/null
@@ -0,0 +1,18 @@
+.. include:: ../../Includes.txt
+
+=========================================================================================
+Important: #81899 - Backend AJAX routes use "&route=/ajax/" instead of "ajaxId" parameter
+=========================================================================================
+
+See :issue:`81899`
+
+Description
+===========
+
+The TYPO3 Backend uses AJAX calls by calling routes with the ``&route=/ajax/*`` GET/POST parameter
+now instead of the "&ajaxId" GET/POST parameter.
+
+Although this is not a breaking change, some PHP code might rely on GET/POST parameters being
+set, and must check for the route parameter instead.
+
+.. index:: Backend
\ No newline at end of file