[!!!][TASK] Remove $response preparation in route dispatching
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Http / RouteDispatcher.php
1 <?php
2 namespace TYPO3\CMS\Backend\Http;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Routing\Exception\InvalidRequestTokenException;
20 use TYPO3\CMS\Backend\Routing\Route;
21 use TYPO3\CMS\Backend\Routing\Router;
22 use TYPO3\CMS\Backend\Utility\BackendUtility;
23 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
24 use TYPO3\CMS\Core\Http\Dispatcher;
25 use TYPO3\CMS\Core\Type\Bitmask\Permission;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27 use TYPO3\CMS\Core\Utility\MathUtility;
28
29 /**
30 * Dispatcher which resolves a route to call a controller and method (but also a callable)
31 */
32 class RouteDispatcher extends Dispatcher
33 {
34 /**
35 * Main method to resolve the route and checks the target of the route, and tries to call it.
36 *
37 * @param ServerRequestInterface $request the current server request
38 * @return ResponseInterface the filled response by the callable / controller/action
39 * @throws InvalidRequestTokenException if the route was not found
40 * @throws \InvalidArgumentException if the defined target for the route is invalid
41 */
42 public function dispatch(ServerRequestInterface $request): ResponseInterface
43 {
44 $router = GeneralUtility::makeInstance(Router::class);
45 $route = $router->matchRequest($request);
46 $request = $request->withAttribute('route', $route);
47 $request = $request->withAttribute('target', $route->getOption('target'));
48 if (!$this->isValidRequest($request)) {
49 throw new InvalidRequestTokenException('Invalid request for route "' . $route->getPath() . '"', 1425389455);
50 }
51
52 if ($route->getOption('module')) {
53 $this->addAndValidateModuleConfiguration($request, $route);
54 }
55 $targetIdentifier = $route->getOption('target');
56 $target = $this->getCallableFromTarget($targetIdentifier);
57 $arguments = [$request];
58 return call_user_func_array($target, $arguments);
59 }
60
61 /**
62 * Wrapper method for static form protection utility
63 *
64 * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection
65 */
66 protected function getFormProtection()
67 {
68 return FormProtectionFactory::get();
69 }
70
71 /**
72 * Checks if the request token is valid. This is checked to see if the route is really
73 * created by the same instance. Should be called for all routes in the backend except
74 * for the ones that don't require a login.
75 *
76 * @param \Psr\Http\Message\ServerRequestInterface $request
77 * @return bool
78 * @see \TYPO3\CMS\Backend\Routing\UriBuilder where the token is generated.
79 */
80 protected function isValidRequest($request)
81 {
82 $route = $request->getAttribute('route');
83 if ($route->getOption('access') === 'public') {
84 return true;
85 }
86 $token = (string)($request->getParsedBody()['token'] ?? $request->getQueryParams()['token']);
87 if ($token) {
88 return $this->getFormProtection()->validateToken($token, 'route', $route->getOption('_identifier'));
89 }
90 // backwards compatibility: check for M and module token params
91 // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
92 $token = (string)($request->getParsedBody()['moduleToken'] ?? $request->getQueryParams()['moduleToken']);
93 return $this->getFormProtection()->validateToken($token, 'moduleCall', $request->getParsedBody()['M'] ?? $request->getQueryParams()['M']);
94 }
95
96 /**
97 * Adds configuration for a module and checks module permissions for the
98 * current user.
99 *
100 * @param ServerRequestInterface $request
101 * @param Route $route
102 * @throws \RuntimeException
103 */
104 protected function addAndValidateModuleConfiguration(ServerRequestInterface $request, Route $route)
105 {
106 $moduleName = $route->getOption('moduleName');
107 $moduleConfiguration = $this->getModuleConfiguration($moduleName);
108 $route->setOption('moduleConfiguration', $moduleConfiguration);
109
110 $backendUserAuthentication = $GLOBALS['BE_USER'];
111
112 // Check permissions and exit if the user has no permission for entry
113 $backendUserAuthentication->modAccess($moduleConfiguration);
114 $id = $request->getQueryParams()['id'] ?? $request->getParsedBody()['id'];
115 if (MathUtility::canBeInterpretedAsInteger($id) && $id > 0) {
116 $permClause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
117 // Check page access
118 if (!is_array(BackendUtility::readPageAccess($id, $permClause))) {
119 // Check if page has been deleted
120 $deleteField = $GLOBALS['TCA']['pages']['ctrl']['delete'];
121 $pageInfo = BackendUtility::getRecord('pages', $id, $deleteField, $permClause ? ' AND ' . $permClause : '', false);
122 if (!$pageInfo[$deleteField]) {
123 throw new \RuntimeException('You don\'t have access to this page', 1289917924);
124 }
125 }
126 }
127 }
128
129 /**
130 * Returns the module configuration which is provided during module registration
131 *
132 * @param string $moduleName
133 * @return array
134 * @throws \RuntimeException
135 */
136 protected function getModuleConfiguration($moduleName)
137 {
138 if (!isset($GLOBALS['TBE_MODULES']['_configuration'][$moduleName])) {
139 throw new \RuntimeException('Module ' . $moduleName . ' is not configured.', 1289918325);
140 }
141 return $GLOBALS['TBE_MODULES']['_configuration'][$moduleName];
142 }
143 }