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 | } |