Skip to content
Snippets Groups Projects
Commit f92aee2e authored by Benni Mack's avatar Benni Mack Committed by Christian Kuhn
Browse files

[TASK] Install Tool: Introduce PSR-7 response objects

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: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: default avatarSusanne Moog <susanne.moog@typo3.org>
Tested-by: default avatarSusanne Moog <susanne.moog@typo3.org>
Reviewed-by: default avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: default avatarChristian Kuhn <lolli@schwarzbu.ch>
parent 11716119
Branches
Tags
No related merge requests found
......@@ -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;
}
}
......@@ -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;
}
}
......@@ -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));
}
}
......@@ -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));
}
}
......@@ -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();
}
......
......@@ -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();
}
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment