[TASK] Install Tool: Introduce PSR-7 response objects 56/53856/5
authorBenni Mack <benni@typo3.org>
Thu, 31 Aug 2017 18:34:22 +0000 (20:34 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Thu, 31 Aug 2017 20:53:16 +0000 (22:53 +0200)
Uses PSR-7 instead of plain echo() and die - and shutdown the
install tool properly (except for redirects currently).

In the future, we should introduce proper PSR-7 response objects
for certain responses (JSON, Redirect) but this will happen in
a separate step.

Resolves: #82268
Releases: master
Change-Id: If8124f975936f6205f45009d30d979204765d8d1
Reviewed-on: https://review.typo3.org/53856
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/install/Classes/Controller/AbstractController.php
typo3/sysext/install/Classes/Controller/AjaxController.php
typo3/sysext/install/Classes/Controller/StepController.php
typo3/sysext/install/Classes/Controller/ToolController.php
typo3/sysext/install/Classes/Http/RecoveryRequestHandler.php
typo3/sysext/install/Classes/Http/RequestHandler.php

index 8a971c7..ad8e2d9 100644 (file)
@@ -14,6 +14,9 @@ namespace TYPO3\CMS\Install\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Http\Response;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Install\Controller\Action\Common\LoginForm;
@@ -31,10 +34,11 @@ class AbstractController
     /**
      * Show login form
      *
-     * @param FlashMessage $message Optional status message from controller
+     * @param ServerRequestInterface $request
+     * @param FlashMessage $message Optional status message
      * @return string Rendered HTML
      */
-    public function loginForm(FlashMessage $message = null)
+    protected function loginForm(ServerRequestInterface $request, FlashMessage $message = null)
     {
         /** @var LoginForm $action */
         $action = GeneralUtility::makeInstance(LoginForm::class);
@@ -195,17 +199,19 @@ class AbstractController
     }
 
     /**
-     * Output content.
-     * WARNING: This exits the script execution!
+     * Creates a PSR-7 response
      *
-     * @param string $content Content to output
+     * @param string $content
+     * @return ResponseInterface
      */
-    public function output($content = '')
+    public function output($content = ''): ResponseInterface
     {
-        header('Content-Type: text/html; charset=utf-8');
-        header('Cache-Control: no-cache, must-revalidate');
-        header('Pragma: no-cache');
-        echo $content;
-        die;
+        $response = new Response('php://temp', 200, [
+            'Content-Type' => 'text/html; charset=utf-8',
+            'Cache-Control' => 'no-cache, must-revalidate',
+            'Pragma' => 'no-cache'
+        ]);
+        $response->getBody()->write($content);
+        return $response;
     }
 }
index e6dc541..e46a2ed 100644 (file)
@@ -14,6 +14,10 @@ namespace TYPO3\CMS\Install\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Http\Response;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -84,27 +88,12 @@ class AjaxController extends AbstractController
 
     /**
      * Main entry point
-     */
-    public function execute()
-    {
-        $this->dispatchAuthenticationActions();
-    }
-
-    /**
-     * Check login status
-     */
-    public function unauthorizedAction()
-    {
-        $this->output($this->unauthorized);
-    }
-
-    /**
-     * Call an action that needs authentication
      *
+     * @param $request ServerRequestInterface
+     * @return ResponseInterface
      * @throws Exception
-     * @return string Rendered content
      */
-    protected function dispatchAuthenticationActions()
+    public function execute(ServerRequestInterface $request): ResponseInterface
     {
         $action = $this->getAction();
         if ($action === '') {
@@ -124,22 +113,36 @@ class AjaxController extends AbstractController
         $toolAction->setAction($action);
         $toolAction->setToken($this->generateTokenForAction($action));
         $toolAction->setPostValues($this->getPostValues());
-        $this->output($toolAction->handle());
+        return $this->output($toolAction->handle());
+    }
+
+    /**
+     * Render "unauthorized"
+     *
+     * @param ServerRequestInterface $request
+     * @param FlashMessage $message
+     * @return ResponseInterface
+     */
+    public function unauthorizedAction(ServerRequestInterface $request, FlashMessage $message = null): ResponseInterface
+    {
+        return $this->output($this->unauthorized);
     }
 
     /**
-     * Output content.
-     * WARNING: This exits the script execution!
+     * Creates a PSR-7 response
      *
-     * @param string $content JSON encoded content to output
+     * @param string $content
+     * @return ResponseInterface
      */
-    public function output($content = '')
+    public function output($content = ''): ResponseInterface
     {
         ob_clean();
-        header('Content-Type: application/json; charset=utf-8');
-        header('Cache-Control: no-cache, must-revalidate');
-        header('Pragma: no-cache');
-        echo $content;
-        die;
+        $response = new Response('php://temp', 200, [
+            'Content-Type' => 'application/json; charset=utf-8',
+            'Cache-Control' => 'no-cache, must-revalidate',
+            'Pragma' => 'no-cache'
+        ]);
+        $response->getBody()->write($content);
+        return $response;
     }
 }
index fe18497..f39ec34 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Install\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Install\Controller\Action\Step\AbstractStepAction;
@@ -50,15 +52,20 @@ class StepController extends AbstractController
     }
 
     /**
-     * Index action acts as a dispatcher to different steps
+     * Main dispatch method
      *
+     * @param $request ServerRequestInterface
+     * @return ResponseInterface
      * @throws Exception
      */
-    public function execute()
+    public function execute(ServerRequestInterface $request): ResponseInterface
     {
         $this->executeSpecificStep();
-        $this->outputSpecificStep();
-        $this->redirectToTool();
+        $response = $this->outputSpecificStep();
+        if (!($response instanceof ResponseInterface)) {
+            $this->redirectToTool();
+        }
+        return $response;
     }
 
     /**
@@ -68,7 +75,7 @@ class StepController extends AbstractController
      * As soon as there is a typo3conf directory at all (not step 1 of "first install"),
      * the file must be there and valid in order to proceed.
      */
-    public function outputInstallToolNotEnabledMessage()
+    public function outputInstallToolNotEnabledMessage(): ResponseInterface
     {
         if (!EnableFileService::isFirstInstallAllowed() && !\TYPO3\CMS\Core\Core\Bootstrap::getInstance()->checkIfEssentialConfigurationExists()) {
             /** @var \TYPO3\CMS\Install\Controller\Action\ActionInterface $action */
@@ -80,7 +87,7 @@ class StepController extends AbstractController
             $action->setAction('installToolDisabled');
         }
         $action->setController('common');
-        $this->output($action->handle());
+        return $this->output($action->handle());
     }
 
     /**
@@ -89,13 +96,13 @@ class StepController extends AbstractController
      * If installation is completed - LocalConfiguration exists and
      * installProcess is not running, and installToolPassword must be set
      */
-    public function outputInstallToolPasswordNotSetMessage()
+    public function outputInstallToolPasswordNotSetMessage(): ResponseInterface
     {
         /** @var \TYPO3\CMS\Install\Controller\Action\ActionInterface $action */
         $action = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Controller\Action\Common\InstallToolPasswordNotSetAction::class);
         $action->setController('common');
         $action->setAction('installToolPasswordNotSet');
-        $this->output($action->handle());
+        return $this->output($action->handle());
     }
 
     /**
@@ -157,14 +164,13 @@ class StepController extends AbstractController
                 $stepAction->setStepsCounter($currentStep, $totalSteps);
             }
             $stepAction->setMessages($this->session->getMessagesAndFlush());
-            $this->output($stepAction->handle());
-        } else {
-            // Redirect to next step if there are any
-            $currentPosition = array_keys($this->authenticationActions, $action, true);
-            $nextAction = array_slice($this->authenticationActions, $currentPosition[0] + 1, 1);
-            if (!empty($nextAction)) {
-                $this->redirect('', $nextAction[0]);
-            }
+            return $this->output($stepAction->handle());
+        }
+        // Redirect to next step if there are any
+        $currentPosition = array_keys($this->authenticationActions, $action, true);
+        $nextAction = array_slice($this->authenticationActions, $currentPosition[0] + 1, 1);
+        if (!empty($nextAction)) {
+            $this->redirect('', $nextAction[0]);
         }
     }
 
@@ -210,6 +216,8 @@ class StepController extends AbstractController
      * So, if no typo3conf directory exists yet, the first step is just rendered, or
      * executed if called so. After that, a redirect is initiated to proceed with
      * other tasks.
+     *
+     * @return ResponseInterface|null
      */
     public function executeOrOutputFirstInstallStepIfNeeded()
     {
@@ -238,7 +246,7 @@ class StepController extends AbstractController
             if (!empty($errorMessagesFromExecute)) {
                 $action->setMessages($errorMessagesFromExecute);
             }
-            $this->output($action->handle());
+            return $this->output($action->handle());
         }
 
         if ($wasExecuted) {
@@ -280,4 +288,16 @@ class StepController extends AbstractController
             $this->session->addMessage($message);
         }
     }
+
+    /**
+     * Show login for if user is not authorized yet
+     *
+     * @param ServerRequestInterface $request
+     * @param FlashMessage $message
+     * @return ResponseInterface
+     */
+    public function unauthorizedAction(ServerRequestInterface $request, FlashMessage $message = null): ResponseInterface
+    {
+        return $this->output($this->loginForm($request, $message));
+    }
 }
index 17d6fdf..1c1aa17 100644 (file)
@@ -14,6 +14,10 @@ namespace TYPO3\CMS\Install\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+
 /**
  * Install tool controller, dispatcher class of the install tool.
  *
@@ -34,27 +38,12 @@ class ToolController extends AbstractController
 
     /**
      * Main dispatch method
-     */
-    public function execute()
-    {
-        $this->dispatchAuthenticationActions();
-    }
-
-    /**
-     * Show login for if user is not authorized yet
-     */
-    public function unauthorizedAction()
-    {
-        $this->output($this->loginForm());
-    }
-
-    /**
-     * Call an action that needs authentication
      *
+     * @param $request ServerRequestInterface
+     * @return ResponseInterface
      * @throws Exception
-     * @return string Rendered content
      */
-    protected function dispatchAuthenticationActions()
+    public function execute(ServerRequestInterface $request): ResponseInterface
     {
         $action = $this->getAction();
         if ($action === '') {
@@ -74,6 +63,18 @@ class ToolController extends AbstractController
         $toolAction->setAction($action);
         $toolAction->setToken($this->generateTokenForAction($action));
         $toolAction->setPostValues($this->getPostValues());
-        $this->output($toolAction->handle());
+        return $this->output($toolAction->handle());
+    }
+
+    /**
+     * Show login for if user is not authorized yet
+     *
+     * @param ServerRequestInterface $request
+     * @param FlashMessage $message
+     * @return ResponseInterface
+     */
+    public function unauthorizedAction(ServerRequestInterface $request, FlashMessage $message = null): ResponseInterface
+    {
+        return $this->output($this->loginForm($request, $message));
     }
 }
index a375a35..f5c009d 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Install\Http;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
@@ -65,8 +66,9 @@ class RecoveryRequestHandler implements RequestHandlerInterface
      * Handles an install tool request when nothing is there
      *
      * @param ServerRequestInterface $request
+     * @return ResponseInterface
      */
-    public function handleRequest(ServerRequestInterface $request)
+    public function handleRequest(ServerRequestInterface $request): ResponseInterface
     {
         $this->request = $request;
         $controller = GeneralUtility::makeInstance(StepController::class);
@@ -75,18 +77,21 @@ class RecoveryRequestHandler implements RequestHandlerInterface
             // Warning: Order of these methods is security relevant and interferes with different access
             // conditions (new/existing installation). See the single method comments for details.
             if (!$this->isInstallToolAvailable()) {
-                $controller->outputInstallToolNotEnabledMessage();
+                return $controller->outputInstallToolNotEnabledMessage();
             }
             if (!$this->isInitialInstallationInProgress()
                 && !$this->isInstallToolPasswordSet()
             ) {
-                $controller->outputInstallToolPasswordNotSetMessage();
+                return $controller->outputInstallToolPasswordNotSetMessage();
             }
             $this->recreatePackageStatesFileIfNotExisting();
 
             // todo: this would be nice, if this is detected by the Request workflow and not the controller
             // controller should just execute this
-            $controller->executeOrOutputFirstInstallStepIfNeeded();
+            $response = $controller->executeOrOutputFirstInstallStepIfNeeded();
+            if ($response instanceof ResponseInterface) {
+                return $response;
+            }
             $this->adjustTrustedHostsPatternIfNeeded();
             $this->executeSilentConfigurationUpgradesIfNeeded();
             $this->initializeSession();
@@ -101,9 +106,9 @@ class RecoveryRequestHandler implements RequestHandlerInterface
             $this->session->refreshSession();
 
             $controller->setSessionService($this->session);
-            $controller->execute();
+            return $controller->execute($this->request);
         } catch (AuthenticationRequiredException $e) {
-            $controller->output($controller->loginForm($e->getMessageObject()));
+            return $controller->unauthorizedAction($this->request, $e->getMessageObject());
         } catch (RedirectException $e) {
             $controller->redirect();
         }
index 297710f..b3ee7ff 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Install\Http;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
@@ -63,14 +64,16 @@ class RequestHandler implements RequestHandlerInterface
      * Handles an install tool request for normal operations
      *
      * @param ServerRequestInterface $request
+     * @return ResponseInterface
      */
-    public function handleRequest(ServerRequestInterface $request)
+    public function handleRequest(ServerRequestInterface $request): ResponseInterface
     {
         $this->request = $request;
         $getPost = !empty($request->getQueryParams()['install']) ? $request->getQueryParams()['install'] : $request->getParsedBody()['install'];
-        if ($getPost['controller'] === 'ajax') {
+        $isAjaxRequest = $getPost['controller'] === 'ajax';
+
+        if ($isAjaxRequest) {
             $controllerClassName = \TYPO3\CMS\Install\Controller\AjaxController::class;
-            $this->request = $this->request->withAttribute('isAjaxRequest', true);
         } else {
             $controllerClassName = \TYPO3\CMS\Install\Controller\ToolController::class;
         }
@@ -88,19 +91,14 @@ class RequestHandler implements RequestHandlerInterface
             $this->loginIfRequested();
 
             if (!$this->session->isAuthorized()) {
-                $controller->unauthorizedAction();
-            } else {
-                $this->session->refreshSession();
+                return $controller->unauthorizedAction($this->request);
             }
+            $this->session->refreshSession();
 
-            $controller->execute();
+            return $controller->execute($this->request);
         } catch (AuthenticationRequiredException $e) {
-            // show the login form (or, if AJAX call, just do
-            if ($this->request->getAttribute('isAjaxRequest', false)) {
-                $controller->unauthorizedAction();
-            } else {
-                $controller->output($controller->loginForm($e->getMessageObject()));
-            }
+            // Show the login form (or, if AJAX call, just return "unauthorized"
+            return $controller->unauthorizedAction($this->request, $e->getMessageObject());
         } catch (Exception\RedirectException $e) {
             $controller->redirect();
         }