[FEATURE] Add support for PSR-15 HTTP middlewares
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Http / AjaxRequestHandler.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Backend\Http;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use Psr\Http\Server\RequestHandlerInterface as PsrRequestHandlerInterface;
21 use TYPO3\CMS\Backend\Routing\Exception\InvalidRequestTokenException;
22 use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException;
23 use TYPO3\CMS\Core\Core\Bootstrap;
24 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
25 use TYPO3\CMS\Core\Http\Response;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27
28 /**
29 * AJAX dispatcher
30 *
31 * Main entry point for AJAX calls in the TYPO3 Backend. Based on ?route=/ajax/* of the outside application.
32 * Before doing the basic BE-related set up of this request (see the additional calls on $this->bootstrap inside
33 * handleRequest()), some AJAX-calls can be made without a valid user, which is determined here.
34 *
35 * AJAX Requests are typically registered within EXT:myext/Configuration/Backend/AjaxRoutes.php
36 */
37 class AjaxRequestHandler implements RequestHandlerInterface, PsrRequestHandlerInterface
38 {
39 /**
40 * Instance of the current TYPO3 bootstrap
41 * @var Bootstrap
42 */
43 protected $bootstrap;
44
45 /**
46 * List of requests that don't need a valid BE user
47 * @var array
48 */
49 protected $publicAjaxRoutes = [
50 '/ajax/login',
51 '/ajax/logout',
52 '/ajax/login/refresh',
53 '/ajax/login/timedout',
54 '/ajax/rsa/publickey'
55 ];
56
57 /**
58 * Constructor handing over the bootstrap and the original request
59 *
60 * @param Bootstrap $bootstrap
61 */
62 public function __construct(Bootstrap $bootstrap)
63 {
64 $this->bootstrap = $bootstrap;
65 }
66
67 /**
68 * Handles any AJAX request in the TYPO3 Backend
69 *
70 * @param ServerRequestInterface $request
71 * @return ResponseInterface
72 */
73 public function handleRequest(ServerRequestInterface $request): ResponseInterface
74 {
75 return $this->handle($request);
76 }
77
78 /**
79 * Handles any AJAX request in the TYPO3 Backend, after finishing running middlewares
80 *
81 * @param ServerRequestInterface $request
82 * @return ResponseInterface
83 */
84 public function handle(ServerRequestInterface $request): ResponseInterface
85 {
86 // First get the name of the route
87 $routePath = $request->getParsedBody()['route'] ?? $request->getQueryParams()['route'] ?? '';
88 $request = $request->withAttribute('routePath', $routePath);
89
90 $proceedIfNoUserIsLoggedIn = $this->isLoggedInBackendUserRequired($routePath);
91 $this->boot($proceedIfNoUserIsLoggedIn);
92
93 // Backend Routing - check if a valid route is there, and dispatch
94 return $this->dispatch($request);
95 }
96
97 /**
98 * This request handler can handle any backend request having
99 * an /ajax/ request
100 *
101 * @param ServerRequestInterface $request
102 * @return bool If the request is an AJAX backend request, TRUE otherwise FALSE
103 */
104 public function canHandleRequest(ServerRequestInterface $request): bool
105 {
106 $routePath = $request->getParsedBody()['route'] ?? $request->getQueryParams()['route'] ?? '';
107 return strpos($routePath, '/ajax/') === 0;
108 }
109
110 /**
111 * Returns the priority - how eager the handler is to actually handle the request.
112 *
113 * @return int The priority of the request handler.
114 */
115 public function getPriority(): int
116 {
117 return 80;
118 }
119
120 /**
121 * Check if the user is required for the request
122 * If we're trying to do an ajax login, don't require a user
123 *
124 * @param string $routePath the Route path to check against, something like '
125 * @return bool whether the request can proceed without a login required
126 */
127 protected function isLoggedInBackendUserRequired(string $routePath): bool
128 {
129 return in_array($routePath, $this->publicAjaxRoutes, true);
130 }
131
132 /**
133 * Start the Backend bootstrap part
134 *
135 * @param bool $proceedIfNoUserIsLoggedIn a flag if a backend user is required
136 */
137 protected function boot(bool $proceedIfNoUserIsLoggedIn)
138 {
139 $this->bootstrap
140 ->checkLockedBackendAndRedirectOrDie($proceedIfNoUserIsLoggedIn)
141 ->checkBackendIpOrDie()
142 ->checkSslBackendAndRedirectIfNeeded()
143 ->initializeBackendRouter()
144 ->loadExtTables()
145 ->initializeBackendUser()
146 ->initializeBackendAuthentication($proceedIfNoUserIsLoggedIn)
147 ->initializeLanguageObject()
148 ->initializeBackendTemplate()
149 ->endOutputBufferingAndCleanPreviousOutput()
150 ->initializeOutputCompression()
151 ->sendHttpHeaders();
152 }
153
154 /**
155 * Creates a response object with JSON headers automatically, and then dispatches to the correct route
156 *
157 * @param ServerRequestInterface $request
158 * @return ResponseInterface $response
159 * @throws ResourceNotFoundException if no valid route was found
160 * @throws InvalidRequestTokenException if the request could not be verified
161 */
162 protected function dispatch(ServerRequestInterface $request): ResponseInterface
163 {
164 /** @var Response $response */
165 $response = GeneralUtility::makeInstance(Response::class, 'php://temp', 200, [
166 'Content-Type' => 'application/json; charset=utf-8',
167 'X-JSON' => 'true'
168 ]);
169
170 /** @var RouteDispatcher $dispatcher */
171 $dispatcher = GeneralUtility::makeInstance(RouteDispatcher::class);
172 return $dispatcher->dispatch($request, $response);
173 }
174 }