Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
1.83% |
2 / 109 |
|
8.70% |
2 / 23 |
CRAP | |
0.00% |
0 / 1 |
tao_actions_CommonModule | |
1.83% |
2 / 109 |
|
8.70% |
2 / 23 |
2047.65 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
initialize | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
hasAccess | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
hasWriteAccessToAction | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
hasReadAccessByContext | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasWriteAccessByContext | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUserRoles | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setView | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
defaultData | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
returnError | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
getTemplatePath | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getClientConfigUrl | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getClientTimeout | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
returnJson | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
returnReport | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
30 | |||
getSession | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getServiceManager | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getServiceLocator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setServiceLocator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
validateCsrf | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
20 | |||
logCsrfFailure | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
12 | |||
getPsrResponse | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getActionAccessControl | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License |
6 | * as published by the Free Software Foundation; under version 2 |
7 | * of the License (non-upgradable). |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | * |
18 | * Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg |
19 | * (under the project TAO & TAO2); |
20 | * 2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung |
21 | * (under the project TAO-TRANSFER); |
22 | * 2009-2012 (update and modification) Public Research Centre Henri Tudor |
23 | * (under the project TAO-SUSTAIN & TAO-DEV); |
24 | * 2013-2019 (update and modification) Open Assessment Technologies SA; |
25 | */ |
26 | |
27 | use oat\oatbox\user\User; |
28 | use oat\tao\model\http\LegacyController; |
29 | use oat\tao\helpers\LegacySessionUtils; |
30 | use oat\tao\model\action\CommonModuleInterface; |
31 | use oat\tao\model\mvc\RendererTrait; |
32 | use oat\tao\model\security\ActionProtector; |
33 | use oat\tao\helpers\Template; |
34 | use oat\tao\helpers\JavaScript; |
35 | use oat\oatbox\service\ServiceManager; |
36 | use oat\tao\model\accessControl\AclProxy; |
37 | use oat\oatbox\service\ServiceManagerAwareTrait; |
38 | use oat\oatbox\service\ServiceManagerAwareInterface; |
39 | use oat\tao\model\accessControl\ActionAccessControl; |
40 | use oat\tao\model\accessControl\Context as AclContext; |
41 | use oat\oatbox\service\exception\InvalidServiceManagerException; |
42 | use oat\oatbox\log\LoggerAwareTrait; |
43 | use oat\tao\model\security\xsrf\TokenService; |
44 | use Zend\ServiceManager\ServiceLocatorInterface; |
45 | |
46 | use function GuzzleHttp\Psr7\stream_for; |
47 | |
48 | /** |
49 | * Top level controller |
50 | * All children extensions module should extends the CommonModule to access the shared data |
51 | * |
52 | * @author CRP Henri Tudor - TAO Team - {@link http://www.tao.lu} |
53 | * @license GPLv2 http://www.opensource.org/licenses/gpl-2.0.php |
54 | * @package tao |
55 | * |
56 | */ |
57 | abstract class tao_actions_CommonModule extends LegacyController implements |
58 | ServiceManagerAwareInterface, |
59 | CommonModuleInterface |
60 | { |
61 | use ServiceManagerAwareTrait { |
62 | getServiceManager as protected getOriginalServiceManager; |
63 | getServiceLocator as protected getOriginalServiceLocator; |
64 | setServiceLocator as protected setOriginalServiceLocator; |
65 | } |
66 | use LoggerAwareTrait; |
67 | use RendererTrait { |
68 | setView as protected setRendererView; |
69 | } |
70 | use LegacySessionUtils; |
71 | |
72 | /** |
73 | * The Modules access the models through the service instance |
74 | * |
75 | * @var tao_models_classes_Service |
76 | * @deprecated |
77 | */ |
78 | protected $service; |
79 | |
80 | /** |
81 | * tao_actions_CommonModule constructor. |
82 | * @security("hide"); |
83 | */ |
84 | public function __construct() |
85 | { |
86 | } |
87 | |
88 | /** |
89 | * @inheritdoc |
90 | */ |
91 | public function initialize() |
92 | { |
93 | /** @var ActionProtector $actionProtector */ |
94 | $actionProtector = $this->getServiceLocator()->get(ActionProtector::SERVICE_ID); |
95 | $actionProtector->setHeaders(); |
96 | } |
97 | |
98 | /** |
99 | * Whenever or not the current user has access to a specific action |
100 | * using functional and data access control |
101 | * |
102 | * @param string $controllerClass |
103 | * @param string $action |
104 | * @param array $parameters |
105 | * @return boolean |
106 | * @throws common_exception_Error |
107 | */ |
108 | protected function hasAccess($controllerClass, $action, $parameters = []) |
109 | { |
110 | $user = $this->getSession()->getUser(); |
111 | return AclProxy::hasAccess($user, $controllerClass, $action, $parameters); |
112 | } |
113 | |
114 | /** |
115 | * @deprecated Use $this->hasWriteAccessByContext() |
116 | */ |
117 | protected function hasWriteAccessToAction(string $action, ?User $user = null): bool |
118 | { |
119 | $context = new AclContext([ |
120 | AclContext::PARAM_CONTROLLER => static::class, |
121 | AclContext::PARAM_ACTION => $action, |
122 | AclContext::PARAM_USER => $user, |
123 | ]); |
124 | |
125 | return $this->hasWriteAccessByContext($context); |
126 | } |
127 | |
128 | protected function hasReadAccessByContext(AclContext $context): bool |
129 | { |
130 | return $this->getActionAccessControl()->contextHasReadAccess($context); |
131 | } |
132 | |
133 | protected function hasWriteAccessByContext(AclContext $context): bool |
134 | { |
135 | return $this->getActionAccessControl()->contextHasWriteAccess($context); |
136 | } |
137 | |
138 | protected function getUserRoles(): array |
139 | { |
140 | return $this->getSession()->getUser()->getRoles(); |
141 | } |
142 | |
143 | /** |
144 | * |
145 | * @see Module::setView() |
146 | * @param string $path |
147 | * view identifier |
148 | * @param string $extensionID |
149 | * use the views in the specified extension instead of the current extension |
150 | */ |
151 | public function setView($path, $extensionID = null) |
152 | { |
153 | $this->setRendererView(Template::getTemplate($path, $extensionID)); |
154 | } |
155 | |
156 | /** |
157 | * Retrieve the data from the url and make the base initialization |
158 | * |
159 | * @return void |
160 | * @throws common_ext_ExtensionException |
161 | */ |
162 | protected function defaultData() |
163 | { |
164 | $context = Context::getInstance(); |
165 | |
166 | $this->setData('extension', $context->getExtensionName()); |
167 | $this->setData('module', $context->getModuleName()); |
168 | $this->setData('action', $context->getActionName()); |
169 | |
170 | if ($this->hasRequestParameter('uri')) { |
171 | // inform the client of new classUri |
172 | $this->setData('uri', $this->getRequestParameter('uri')); |
173 | } |
174 | |
175 | if ($this->hasRequestParameter('classUri')) { |
176 | // inform the client of new classUri |
177 | $this->setData('uri', $this->getRequestParameter('classUri')); |
178 | } |
179 | |
180 | if ($this->getRequestParameter('message')) { |
181 | $this->setData('message', $this->getRequestParameter('message')); |
182 | } |
183 | if ($this->getRequestParameter('errorMessage')) { |
184 | $this->setData('errorMessage', $this->getRequestParameter('errorMessage')); |
185 | } |
186 | |
187 | $this->setData('client_timeout', $this->getClientTimeout()); |
188 | $this->setData('client_config_url', $this->getClientConfigUrl()); |
189 | } |
190 | |
191 | /** |
192 | * Function to return an user readable error |
193 | * Does not work with ajax Requests yet |
194 | * |
195 | * @param string $description error to show |
196 | * @param boolean $returnLink whenever or not to add a return link |
197 | * @param int $httpStatus |
198 | * @throws common_Exception |
199 | */ |
200 | protected function returnError($description, $returnLink = true, $httpStatus = null) |
201 | { |
202 | if ($this->isXmlHttpRequest()) { |
203 | $this->logWarning('Called ' . __FUNCTION__ . ' in an unsupported AJAX context'); |
204 | throw new common_Exception($description); |
205 | } |
206 | $this->setData('message', $description); |
207 | $this->setData('returnLink', $returnLink); |
208 | if (parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) == parse_url(ROOT_URL, PHP_URL_HOST)) { |
209 | $this->setData('returnUrl', htmlentities($_SERVER['HTTP_REFERER'], ENT_QUOTES)); |
210 | } else { |
211 | $this->setData('returnUrl', false); |
212 | } |
213 | if ($httpStatus !== null && file_exists(Template::getTemplate("error/error${httpStatus}.tpl"))) { |
214 | $this->setView("error/error${httpStatus}.tpl", 'tao'); |
215 | } else { |
216 | $this->setView('error/user_error.tpl', 'tao'); |
217 | } |
218 | } |
219 | |
220 | /** |
221 | * Returns the absolute path to the specified template |
222 | * |
223 | * @param string $identifier |
224 | * @param string $extensionID |
225 | * @return string |
226 | * @throws common_exception_Error |
227 | * @throws common_ext_ExtensionException |
228 | */ |
229 | protected static function getTemplatePath($identifier, $extensionID = null) |
230 | { |
231 | if ($extensionID === true) { |
232 | $extensionID = 'tao'; |
233 | common_Logger::d('Deprecated use of setView() using a boolean'); |
234 | } |
235 | if ($extensionID === null) { |
236 | $extensionID = Context::getInstance()->getExtensionName(); |
237 | } |
238 | $ext = common_ext_ExtensionsManager::singleton()->getExtensionById($extensionID); |
239 | return $ext->getConstant('DIR_VIEWS') . 'templates' . DIRECTORY_SEPARATOR . $identifier; |
240 | } |
241 | |
242 | /** |
243 | * Helps you to add the URL of the client side config file |
244 | * |
245 | * @param array $extraParameters additional parameters to append to the URL |
246 | * @return string the URL |
247 | */ |
248 | protected function getClientConfigUrl($extraParameters = []) |
249 | { |
250 | return JavaScript::getClientConfigUrl($extraParameters); |
251 | } |
252 | |
253 | /** |
254 | * Get the client timeout value from the config. |
255 | * |
256 | * @return int the timeout value in seconds |
257 | * @throws common_ext_ExtensionException |
258 | */ |
259 | protected function getClientTimeout() |
260 | { |
261 | $ext = $this->getServiceManager()->get(common_ext_ExtensionsManager::SERVICE_ID)->getExtensionById('tao'); |
262 | $config = $ext->getConfig('js'); |
263 | if ($config !== null && isset($config['timeout'])) { |
264 | return (int)$config['timeout']; |
265 | } |
266 | return 30; |
267 | } |
268 | |
269 | /** |
270 | * Return json response. |
271 | * |
272 | * @param array|\JsonSerializable $data |
273 | * @param int $httpStatus |
274 | * |
275 | * @deprecated use \oat\tao\model\http\HttpJsonResponseTrait::setSuccessJsonResponse for standard response |
276 | * @deprecated use \oat\tao\model\http\HttpJsonResponseTrait::setErrorJsonResponse for standard response |
277 | */ |
278 | protected function returnJson($data, $httpStatus = 200) |
279 | { |
280 | header(HTTPToolkit::statusCodeHeader($httpStatus)); |
281 | Context::getInstance()->getResponse()->setContentHeader('application/json'); |
282 | $this->response = $this->getPsrResponse()->withBody(stream_for(json_encode($data))); |
283 | } |
284 | |
285 | /** |
286 | * Returns a report |
287 | * |
288 | * @param common_report_Report $report |
289 | */ |
290 | protected function returnReport(common_report_Report $report) |
291 | { |
292 | $data = $report->getData(); |
293 | $successes = $report->getSuccesses(); |
294 | |
295 | // if report has no data, try to get it from the sub report |
296 | while ($data === null && count($successes) > 0) { |
297 | $firstSubReport = current($successes); |
298 | $data = $firstSubReport->getData(); |
299 | $successes = $firstSubReport->getSuccesses(); |
300 | } |
301 | |
302 | if ($data !== null && $data instanceof core_kernel_classes_Resource) { |
303 | $this->setData('selectNode', tao_helpers_Uri::encode($data->getUri())); |
304 | } |
305 | $this->setData('report', $report); |
306 | $this->setView('report.tpl', 'tao'); |
307 | } |
308 | |
309 | /** |
310 | * Get the current session |
311 | * |
312 | * @return common_session_Session |
313 | * @throws common_exception_Error |
314 | */ |
315 | protected function getSession() |
316 | { |
317 | return common_session_SessionManager::getSession(); |
318 | } |
319 | |
320 | /** |
321 | * Get the service Manager |
322 | * |
323 | * @deprecated Use $this->propagate or $this->registerService to access ServiceManager functionalities |
324 | * @deprecated To get the service dependencies manager, use $this->getServiceLocator |
325 | * |
326 | * @return ServiceManager |
327 | */ |
328 | protected function getServiceManager() |
329 | { |
330 | try { |
331 | $serviceManager = $this->getOriginalServiceManager(); |
332 | } catch (InvalidServiceManagerException $e) { |
333 | $serviceManager = ServiceManager::getServiceManager(); |
334 | } |
335 | return $serviceManager; |
336 | } |
337 | |
338 | /** |
339 | * @return ServiceLocatorInterface |
340 | * @security("hide"); |
341 | */ |
342 | public function getServiceLocator() |
343 | { |
344 | return $this->getOriginalServiceLocator(); |
345 | } |
346 | |
347 | /** |
348 | * @param ServiceLocatorInterface $serviceLocator |
349 | * @return mixed |
350 | * @security("hide"); |
351 | */ |
352 | public function setServiceLocator(ServiceLocatorInterface $serviceLocator) |
353 | { |
354 | return $this->setOriginalServiceLocator($serviceLocator); |
355 | } |
356 | |
357 | /** |
358 | * Validate a CSRF token, based on the CSRF header. |
359 | * |
360 | * @throws common_exception_Unauthorized |
361 | */ |
362 | protected function validateCsrf() |
363 | { |
364 | if (!$this->getPsrRequest()->hasHeader(TokenService::CSRF_TOKEN_HEADER)) { |
365 | $this->logCsrfFailure(sprintf('Missing %s header.', TokenService::CSRF_TOKEN_HEADER)); |
366 | } |
367 | |
368 | $csrfTokenHeader = $this->getPsrRequest()->getHeader(TokenService::CSRF_TOKEN_HEADER); |
369 | $csrfToken = current($csrfTokenHeader); |
370 | |
371 | /** @var TokenService $tokenService */ |
372 | $tokenService = $this->getServiceLocator()->get(TokenService::SERVICE_ID); |
373 | $newToken = null; |
374 | |
375 | try { |
376 | if ($tokenService->validateToken($csrfToken)) { |
377 | $newToken = $tokenService->createToken()->getValue(); |
378 | } |
379 | } catch (common_exception_Unauthorized $e) { |
380 | $this->logCsrfFailure($e->getMessage(), $csrfToken); |
381 | } |
382 | |
383 | $this->response = $this->getPsrResponse()->withHeader(TokenService::CSRF_TOKEN_HEADER, $newToken); |
384 | } |
385 | |
386 | /** |
387 | * Logs a CSRF validation error |
388 | * |
389 | * @param string $exceptionMessage |
390 | * @param null $token |
391 | * @throws common_exception_Unauthorized |
392 | */ |
393 | private function logCsrfFailure($exceptionMessage, $token = null) |
394 | { |
395 | try { |
396 | $userIdentifier = $this->getSession()->getUser()->getIdentifier(); |
397 | } catch (common_exception_Error $e) { |
398 | $this->logError('Unable to retrieve session! ' . $e->getMessage()); |
399 | throw new common_exception_Unauthorized($exceptionMessage); |
400 | } |
401 | |
402 | $requestMethod = $this->getPsrRequest()->getMethod(); |
403 | $requestUri = $this->getPsrRequest()->getUri(); |
404 | $requestHeaders = $this->getHeaders(); |
405 | |
406 | $this->logWarning( |
407 | '[CSRF] - Failed to validate CSRF token. The following exception occurred: ' . $exceptionMessage |
408 | ); |
409 | $this->logWarning( |
410 | "[CSRF] \n" . |
411 | "CSRF validation information: \n" . |
412 | 'Provided token: ' . ($token ?: 'none') . " \n" . |
413 | 'User identifier: ' . $userIdentifier . " \n" . |
414 | 'Request: [' . $requestMethod . '] ' . $requestUri . " \n" . |
415 | "Request Headers : \n" . |
416 | urldecode(http_build_query($requestHeaders, '', "\n")) |
417 | ); |
418 | |
419 | throw new common_exception_Unauthorized($exceptionMessage); |
420 | } |
421 | |
422 | /** |
423 | * Ensure the template is rendered as part of the response |
424 | * {@inheritDoc} |
425 | * @see \oat\tao\model\http\Controller::getPsrResponse() |
426 | */ |
427 | public function getPsrResponse() |
428 | { |
429 | $response = parent::getPsrResponse(); |
430 | return $this->hasView() |
431 | ? $response->withBody(stream_for($this->getRenderer()->render())) |
432 | : $response; |
433 | } |
434 | |
435 | private function getActionAccessControl(): ActionAccessControl |
436 | { |
437 | return $this->getServiceLocator()->get(ActionAccessControl::SERVICE_ID); |
438 | } |
439 | } |