0d4e539d6df62f48e772409fc360e94011a618cf
[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\Configuration\Features;
24 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
25 use TYPO3\CMS\Core\Http\Dispatcher;
26 use TYPO3\CMS\Core\Type\Bitmask\Permission;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28 use TYPO3\CMS\Core\Utility\MathUtility;
29
30 /**
31 * Dispatcher which resolves a route to call a controller and method (but also a callable)
32 */
33 class RouteDispatcher extends Dispatcher
34 {
35 /**
36 * Main method to resolve the route and checks the target of the route, and tries to call it.
37 *
38 * @param ServerRequestInterface $request the current server request
39 * @param ResponseInterface $response the prepared response @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
40 * @return ResponseInterface the filled response by the callable / controller/action
41 * @throws InvalidRequestTokenException if the route was not found
42 * @throws \InvalidArgumentException if the defined target for the route is invalid
43 */
44 public function dispatch(ServerRequestInterface $request, ResponseInterface $response = null): ResponseInterface
45 {
46 $router = GeneralUtility::makeInstance(Router::class);
47 $route = $router->matchRequest($request);
48 $request = $request->withAttribute('route', $route);
49 $request = $request->withAttribute('target', $route->getOption('target'));
50 if (!$this->isValidRequest($request)) {
51 throw new InvalidRequestTokenException('Invalid request for route "' . $route->getPath() . '"', 1425389455);
52 }
53
54 if ($route->getOption('module')) {
55 $this->addAndValidateModuleConfiguration($request, $route);
56 }
57 $targetIdentifier = $route->getOption('target');
58 $target = $this->getCallableFromTarget($targetIdentifier);
59 $arguments = [$request];
60
61 // @deprecated Test if target accepts one (ok) or two (deprecated) arguments
62 $scanForResponse = !GeneralUtility::makeInstance(Features::class)
63 ->isFeatureEnabled('simplifiedControllerActionDispatching');
64 if ($scanForResponse) {
65 if (is_array($targetIdentifier)) {
66 $controllerActionName = implode('::', $targetIdentifier);
67 $targetReflection = new \ReflectionMethod($controllerActionName);
68 } elseif (is_string($targetIdentifier) && strpos($targetIdentifier, '::') !== false) {
69 $controllerActionName = $targetIdentifier;
70 $targetReflection = new \ReflectionMethod($controllerActionName);
71 } elseif (is_callable($targetIdentifier)) {
72 $controllerActionName = 'closure function';
73 $targetReflection = new \ReflectionFunction($targetIdentifier);
74 } else {
75 $controllerActionName = $targetIdentifier . '::__invoke';
76 $targetReflection = new \ReflectionMethod($controllerActionName);
77 }
78 if ($targetReflection->getNumberOfParameters() >= 2) {
79 trigger_error(
80 'Handing over second argument $response to controller action ' . $controllerActionName . '() is deprecated and will be removed in TYPO3 v10.0.',
81 E_USER_DEPRECATED
82 );
83 $arguments[] = $response;
84 }
85 }
86
87 return call_user_func_array($target, $arguments);
88 }
89
90 /**
91 * Wrapper method for static form protection utility
92 *
93 * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection
94 */
95 protected function getFormProtection()
96 {
97 return FormProtectionFactory::get();
98 }
99
100 /**
101 * Checks if the request token is valid. This is checked to see if the route is really
102 * created by the same instance. Should be called for all routes in the backend except
103 * for the ones that don't require a login.
104 *
105 * @param \Psr\Http\Message\ServerRequestInterface $request
106 * @return bool
107 * @see \TYPO3\CMS\Backend\Routing\UriBuilder where the token is generated.
108 */
109 protected function isValidRequest($request)
110 {
111 $route = $request->getAttribute('route');
112 if ($route->getOption('access') === 'public') {
113 return true;
114 }
115 $token = (string)($request->getParsedBody()['token'] ?? $request->getQueryParams()['token']);
116 if ($token) {
117 return $this->getFormProtection()->validateToken($token, 'route', $route->getOption('_identifier'));
118 }
119 // backwards compatibility: check for M and module token params
120 // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
121 $token = (string)($request->getParsedBody()['moduleToken'] ?? $request->getQueryParams()['moduleToken']);
122 return $this->getFormProtection()->validateToken($token, 'moduleCall', $request->getParsedBody()['M'] ?? $request->getQueryParams()['M']);
123 }
124
125 /**
126 * Adds configuration for a module and checks module permissions for the
127 * current user.
128 *
129 * @param ServerRequestInterface $request
130 * @param Route $route
131 * @throws \RuntimeException
132 */
133 protected function addAndValidateModuleConfiguration(ServerRequestInterface $request, Route $route)
134 {
135 $moduleName = $route->getOption('moduleName');
136 $moduleConfiguration = $this->getModuleConfiguration($moduleName);
137 $route->setOption('moduleConfiguration', $moduleConfiguration);
138
139 $backendUserAuthentication = $GLOBALS['BE_USER'];
140
141 // Check permissions and exit if the user has no permission for entry
142 $backendUserAuthentication->modAccess($moduleConfiguration);
143 $id = $request->getQueryParams()['id'] ?? $request->getParsedBody()['id'];
144 if (MathUtility::canBeInterpretedAsInteger($id) && $id > 0) {
145 $permClause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
146 // Check page access
147 if (!is_array(BackendUtility::readPageAccess($id, $permClause))) {
148 // Check if page has been deleted
149 $deleteField = $GLOBALS['TCA']['pages']['ctrl']['delete'];
150 $pageInfo = BackendUtility::getRecord('pages', $id, $deleteField, $permClause ? ' AND ' . $permClause : '', false);
151 if (!$pageInfo[$deleteField]) {
152 throw new \RuntimeException('You don\'t have access to this page', 1289917924);
153 }
154 }
155 }
156 }
157
158 /**
159 * Returns the module configuration which is provided during module registration
160 *
161 * @param string $moduleName
162 * @return array
163 * @throws \RuntimeException
164 */
165 protected function getModuleConfiguration($moduleName)
166 {
167 if (!isset($GLOBALS['TBE_MODULES']['_configuration'][$moduleName])) {
168 throw new \RuntimeException('Module ' . $moduleName . ' is not configured.', 1289918325);
169 }
170 return $GLOBALS['TBE_MODULES']['_configuration'][$moduleName];
171 }
172 }