[FEATURE] Enforce cHash argument for Extbase actions 76/49976/20
authorHelmut Hummel <info@helhum.io>
Wed, 21 Sep 2016 20:26:16 +0000 (23:26 +0300)
committerSusanne Moog <susanne.moog@typo3.org>
Fri, 2 Dec 2016 13:35:57 +0000 (14:35 +0100)
TypoScriptFrontendController::reqCHash() is now called for Extbase
frontend plugin actions just like they were usually called for the
legacy AbstractPlugin.

This throws a 404, if plugin arguments are present, but cHash is not,
which would also happen, if the plugin arguments were added
to "cHashRequiredParameters" configuration.

This provides a more reliable page caching behavior
by default and with zero configuration for extension authors.

With the feature switch "requireCHashArgumentForActionArguments" this
behavior can be disabled, which could be useful, if all actions in a plugin
are uncached or one wants to manually control the cHash behavior.

Resolves: #78002
Releases: master
Change-Id: Ic6f71c3e5c8a94a0d422372a08c944aef5663f06
Reviewed-on: https://review.typo3.org/49976
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
typo3/sysext/core/Documentation/Changelog/master/Breaking-78002-EnforceCHashArgumentForExtbaseActions.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-78002-EnforceCHashArgumentForExtbaseActions.rst [new file with mode: 0644]
typo3/sysext/extbase/Classes/Mvc/Web/CacheHashEnforcer.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Mvc/Web/FrontendRequestHandler.php
typo3/sysext/extbase/Tests/Unit/Mvc/Web/CacheHashEnforcerTest.php [new file with mode: 0644]
typo3/sysext/extbase/ext_typoscript_setup.txt

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-78002-EnforceCHashArgumentForExtbaseActions.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-78002-EnforceCHashArgumentForExtbaseActions.rst
new file mode 100644 (file)
index 0000000..5fa94ae
--- /dev/null
@@ -0,0 +1,40 @@
+.. include:: ../../Includes.txt
+
+=============================================================
+Breaking: #78002 - Enforce cHash argument for Extbase actions
+=============================================================
+
+See :issue:`78002`
+
+Description
+===========
+
+URIs to Extbase actions now need a valid cHash per default. This is required for
+both cached and uncached actions. The behavior can be disabled for all actions
+using the feature switch ``requireCHashArgumentForActionArguments``.
+
+
+Impact
+======
+
+All generated links to Extbase actions without having a valid cHash will fail.
+
+
+Affected Installations
+======================
+
+All generated links to Extbase actions that explicitly disabled the cHash are
+affected - like ``<f:link.action action="..." noCacheHash="1"/>``
+
+
+Migration
+=========
+
+Either one of the following:
+
++ ensure to use a valid cHash, e.g. by removing the
+  ``noCacheHash="1"`` argument from link view-helpers
++ disable the ``feature.requireCHashArgumentForActionArguments``
+  setting for the particular extension
+
+.. index:: Frontend, PHP-API
\ No newline at end of file
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-78002-EnforceCHashArgumentForExtbaseActions.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-78002-EnforceCHashArgumentForExtbaseActions.rst
new file mode 100644 (file)
index 0000000..a9c6352
--- /dev/null
@@ -0,0 +1,29 @@
+.. include:: ../../Includes.txt
+
+============================================================
+Feature: #78002 - Enforce cHash argument for Extbase actions
+============================================================
+
+See :issue:`78002`
+
+Description
+===========
+
+TypoScriptFrontendController::reqCHash() is now called for Extbase frontend
+plugin actions just like they were usually called for the legacy AbstractPlugin.
+This provides a more reliable page caching behavior by default and with zero
+configuration for extension authors.
+
+With the feature switch ``requireCHashArgumentForActionArguments`` this behavior
+can be disabled, which could be useful, if all actions in a plugin are uncached
+or one wants to manually control the cHash behavior.
+
+
+Impact
+======
+
+The enforcing of a cHash results in a 404, if plugin arguments are present, but
+cHash is not, which would also happen if the plugin arguments were added to
+``cHashRequiredParameters`` configuration.
+
+.. index:: Frontend, PHP-API
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Classes/Mvc/Web/CacheHashEnforcer.php b/typo3/sysext/extbase/Classes/Mvc/Web/CacheHashEnforcer.php
new file mode 100644 (file)
index 0000000..dba0a5d
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Extbase\Mvc\Web;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\SingletonInterface;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
+
+/**
+ * Enforces cHash argument if it is required for a given request
+ */
+class CacheHashEnforcer implements SingletonInterface
+{
+    /**
+     * @var CacheHashCalculator
+     */
+    protected $cacheHashCalculator;
+
+    /**
+     * @var TypoScriptFrontendController
+     */
+    protected $typoScriptFrontendController;
+
+    /**
+     * CacheHashEnforcer constructor.
+     *
+     * @param CacheHashCalculator $cacheHashCalculator
+     * @param TypoScriptFrontendController|null $typoScriptFrontendController
+     */
+    public function __construct(
+        CacheHashCalculator $cacheHashCalculator,
+        TypoScriptFrontendController $typoScriptFrontendController = null
+    ) {
+        $this->cacheHashCalculator = $cacheHashCalculator;
+        $this->typoScriptFrontendController = $typoScriptFrontendController ?: $GLOBALS['TSFE'];
+    }
+
+    /**
+     * Checks if cHash is required for the current request and calls
+     * TypoScriptFrontendController::reqCHash() if so.
+     * This call will trigger a PageNotFoundException if arguments are required and cHash is not present.
+     *
+     * @param Request $request
+     * @param string $pluginNamespace
+     */
+    public function enforceForRequest(Request $request, string $pluginNamespace)
+    {
+        $arguments = $request->getArguments();
+        if (is_array($arguments) && count($arguments) > 0) {
+            $parameters = [$pluginNamespace => $arguments];
+            $parameters['id'] = $this->typoScriptFrontendController->id;
+            $relevantParameters = $this->cacheHashCalculator->getRelevantParameters(
+                http_build_query($parameters)
+            );
+            if (count($relevantParameters) > 0) {
+                $this->typoScriptFrontendController->reqCHash();
+            }
+        }
+    }
+}
index 2c831f8..fa156ef 100644 (file)
@@ -30,6 +30,11 @@ class FrontendRequestHandler extends AbstractRequestHandler
     protected $extensionService;
 
     /**
+     * @var \TYPO3\CMS\Extbase\Mvc\Web\CacheHashEnforcer
+     */
+    protected $cacheHashEnforcer;
+
+    /**
      * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
      */
     public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
@@ -46,6 +51,14 @@ class FrontendRequestHandler extends AbstractRequestHandler
     }
 
     /**
+     * @param \TYPO3\CMS\Extbase\Mvc\Web\CacheHashEnforcer $cacheHashEnforcer
+     */
+    public function injectCacheHashEnforcer(\TYPO3\CMS\Extbase\Mvc\Web\CacheHashEnforcer $cacheHashEnforcer)
+    {
+        $this->cacheHashEnforcer = $cacheHashEnforcer;
+    }
+
+    /**
      * Handles the web request. The response will automatically be sent to the client.
      *
      * @return \TYPO3\CMS\Extbase\Mvc\ResponseInterface|NULL
@@ -64,6 +77,15 @@ class FrontendRequestHandler extends AbstractRequestHandler
             }
             $request->setIsCached(false);
         }
+
+        if ($this->configurationManager->isFeatureEnabled('requireCHashArgumentForActionArguments')) {
+            $pluginNamespace = $this->extensionService->getPluginNamespace(
+                $request->getControllerExtensionName(),
+                $request->getPluginName()
+            );
+            $this->cacheHashEnforcer->enforceForRequest($request, $pluginNamespace);
+        }
+
         /** @var $response \TYPO3\CMS\Extbase\Mvc\ResponseInterface */
         $response = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Response::class);
         $this->dispatcher->dispatch($request, $response);
diff --git a/typo3/sysext/extbase/Tests/Unit/Mvc/Web/CacheHashEnforcerTest.php b/typo3/sysext/extbase/Tests/Unit/Mvc/Web/CacheHashEnforcerTest.php
new file mode 100644 (file)
index 0000000..43bf953
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Extbase\Tests\Unit\Mvc\Web;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Extbase\Mvc\Web\CacheHashEnforcer;
+use TYPO3\CMS\Extbase\Mvc\Web\Request;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
+
+/**
+ * Test case
+ */
+class CacheHashEnforcerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
+{
+    /**
+     * @var \TYPO3\CMS\Extbase\Mvc\Web\CacheHashEnforcer
+     */
+    protected $subject;
+
+    /**
+     * @var TypoScriptFrontendController|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $frontendControllerMock;
+
+    protected function setUp()
+    {
+        $this->frontendControllerMock = $this->getMockBuilder(TypoScriptFrontendController::class)->disableOriginalConstructor()->getMock();
+        $this->frontendControllerMock->id = 42;
+        $cacheHashCalculator = new CacheHashCalculator();
+        $this->subject = new CacheHashEnforcer(
+            $cacheHashCalculator,
+            $this->frontendControllerMock
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function validateCallsReqCHashIfRequestArgumentsArePresent()
+    {
+        $request = new Request();
+        $request->setArguments(['foo' => 'bar']);
+        $this->frontendControllerMock
+            ->expects($this->once())
+            ->method('reqCHash');
+
+        $this->subject->enforceForRequest($request, 'tx_foo');
+    }
+
+    /**
+     * @test
+     */
+    public function validateDoesNotCallsReqCHashIfNoRequestArgumentsArePresent()
+    {
+        $request = new Request();
+        $this->frontendControllerMock
+            ->expects($this->never())
+            ->method('reqCHash');
+
+        $this->subject->enforceForRequest($request, 'tx_foo');
+    }
+}
index 125ac94..ffaa907 100644 (file)
@@ -96,5 +96,7 @@ config.tx_extbase {
                skipDefaultArguments = 0
                # if set to 1, the enable fields are ignored in BE context
                ignoreAllEnableFieldsInBe = 0
+               # Should be on by default, but can be disabled if all action in the plugin are uncached
+               requireCHashArgumentForActionArguments = 1
        }
 }