[SECURITY] Disallow unauthorized module access
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Http / BackendModuleRequestHandler.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 TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
18 use TYPO3\CMS\Core\Core\Bootstrap;
19 use TYPO3\CMS\Core\FormProtection\BackendFormProtection;
20 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
21 use TYPO3\CMS\Core\Exception;
22 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Extbase\Object\ObjectManager;
25 use Psr\Http\Message\ServerRequestInterface;
26
27 /**
28 * Handles the request for backend modules and wizards
29 */
30 class BackendModuleRequestHandler implements RequestHandlerInterface {
31
32 /**
33 * @var Bootstrap
34 */
35 protected $bootstrap;
36
37 /**
38 * @var array
39 */
40 protected $moduleRegistry = array();
41
42 /**
43 * @var BackendUserAuthentication
44 */
45 protected $backendUserAuthentication;
46
47 /**
48 * Instance of the current Http Request
49 * @var ServerRequestInterface
50 */
51 protected $request;
52
53 /**
54 * Constructor handing over the bootstrap and the original request
55 *
56 * @param Bootstrap $bootstrap
57 */
58 public function __construct(Bootstrap $bootstrap) {
59 $this->bootstrap = $bootstrap;
60 }
61
62 /**
63 * Handles the request, evaluating the configuration and executes the module accordingly
64 *
65 * @param ServerRequestInterface $request
66 * @return NULL|\Psr\Http\Message\ResponseInterface
67 * @throws Exception
68 */
69 public function handleRequest(ServerRequestInterface $request) {
70 $this->request = $request;
71 $this->boot();
72
73 $this->moduleRegistry = $GLOBALS['TBE_MODULES'];
74
75 if (!$this->isValidModuleRequest()) {
76 throw new Exception('The CSRF protection token for the requested module is missing or invalid', 1417988921);
77 }
78
79 // Set to empty as it is not needed / always coming from typo3/index.php
80 $GLOBALS['BACK_PATH'] = '';
81
82 $this->backendUserAuthentication = $GLOBALS['BE_USER'];
83
84 $moduleName = (string)$this->request->getQueryParams()['M'];
85 if ($this->isDispatchedModule($moduleName)) {
86 $isDispatched = $this->dispatchModule($moduleName);
87 } else {
88 $isDispatched = $this->callTraditionalModule($moduleName);
89 }
90 if ($isDispatched === FALSE) {
91 throw new Exception('No module "' . $moduleName . '" could be found.', 1294585070);
92 }
93 }
94
95 /**
96 * Execute TYPO3 bootstrap
97 */
98 protected function boot() {
99 // Evaluate the constant for skipping the BE user check for the bootstrap, will be done without the constant at a later point
100 if (defined('TYPO3_PROCEED_IF_NO_USER') && TYPO3_PROCEED_IF_NO_USER) {
101 $proceedIfNoUserIsLoggedIn = TRUE;
102 } else {
103 $proceedIfNoUserIsLoggedIn = FALSE;
104 }
105
106 $this->bootstrap->checkLockedBackendAndRedirectOrDie()
107 ->checkBackendIpOrDie()
108 ->checkSslBackendAndRedirectIfNeeded()
109 ->loadExtensionTables(TRUE)
110 ->initializeSpriteManager()
111 ->initializeBackendUser()
112 ->initializeBackendAuthentication($proceedIfNoUserIsLoggedIn)
113 ->initializeLanguageObject()
114 ->initializeBackendTemplate()
115 ->endOutputBufferingAndCleanPreviousOutput()
116 ->initializeOutputCompression()
117 ->sendHttpHeaders();
118 }
119
120 /**
121 * This request handler can handle any backend request coming from index.php
122 *
123 * @param ServerRequestInterface $request
124 * @return bool
125 */
126 public function canHandleRequest(ServerRequestInterface $request) {
127 return $request->getAttribute('isModuleRequest', FALSE);
128 }
129
130 /**
131 * Checks if all parameters are met.
132 *
133 * @return bool
134 */
135 protected function isValidModuleRequest() {
136 return
137 $this->getFormProtection() instanceof BackendFormProtection
138 && $this->getFormProtection()->validateToken((string)$this->request->getQueryParams()['moduleToken'], 'moduleCall', (string)$this->request->getQueryParams()['M']);
139 }
140
141 /**
142 * A dispatched module, currently only Extbase modules are dispatched,
143 * traditional modules have a module path set.
144 *
145 * @param string $moduleName
146 * @return bool
147 */
148 protected function isDispatchedModule($moduleName) {
149 return empty($this->moduleRegistry['_PATHS'][$moduleName]);
150 }
151
152 /**
153 * Executes the module dispatcher which calls the module appropriately.
154 * Currently only used by Extbase
155 *
156 * @param string $moduleName
157 * @return bool
158 */
159 protected function dispatchModule($moduleName) {
160 if (is_array($this->moduleRegistry['_dispatcher'])) {
161 foreach ($this->moduleRegistry['_dispatcher'] as $dispatcherClassName) {
162 $dispatcher = GeneralUtility::makeInstance(ObjectManager::class)->get($dispatcherClassName);
163 if ($dispatcher->callModule($moduleName) === TRUE) {
164 return TRUE;
165 break;
166 }
167 }
168 }
169 return FALSE;
170 }
171
172 /**
173 * Calls traditional modules which are identified by having a index.php in their directory
174 * and were previously located within the global scope.
175 *
176 * @param string $moduleName
177 * @return bool
178 */
179 protected function callTraditionalModule($moduleName) {
180 $moduleBasePath = $this->moduleRegistry['_PATHS'][$moduleName];
181 $GLOBALS['MCONF'] = $moduleConfiguration = $this->getModuleConfiguration($moduleName);
182 if (!empty($moduleConfiguration['access'])) {
183 $this->backendUserAuthentication->modAccess($moduleConfiguration, TRUE);
184 }
185 if (file_exists($moduleBasePath . 'index.php')) {
186 global $SOBE;
187 require $moduleBasePath . 'index.php';
188 return TRUE;
189 }
190 return FALSE;
191 }
192
193 /**
194 * Returns the module configuration which is either provided in a conf.php file
195 * or during module registration
196 *
197 * @param string $moduleName
198 * @return array
199 */
200 protected function getModuleConfiguration($moduleName) {
201 $moduleBasePath = $this->moduleRegistry['_PATHS'][$moduleName];
202 if (file_exists($moduleBasePath . 'conf.php')) {
203 // Some modules still rely on this global configuration array in a conf.php file
204 require $moduleBasePath . 'conf.php';
205 $moduleConfiguration = $MCONF;
206 } else {
207 $moduleConfiguration = $this->moduleRegistry['_configuration'][$moduleName];
208 }
209 return $moduleConfiguration;
210 }
211
212
213 /**
214 * Returns the priority - how eager the handler is to actually handle the request.
215 *
216 * @return int The priority of the request handler.
217 */
218 public function getPriority() {
219 return 90;
220 }
221
222 /**
223 * Wrapper method for static form protection utility
224 *
225 * @return \TYPO3\CMS\Core\FormProtection\AbstractFormProtection
226 */
227 protected function getFormProtection() {
228 return FormProtectionFactory::get();
229 }
230
231 }