436 lines
15 KiB
PHP
436 lines
15 KiB
PHP
|
<?php
|
||
|
|
||
|
/**
|
||
|
* This program is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU General Public License
|
||
|
* as published by the Free Software Foundation; under version 2
|
||
|
* of the License (non-upgradable).
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; if not, write to the Free Software
|
||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
*
|
||
|
* Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg (under the project TAO & TAO2);
|
||
|
* 2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung (under the project TAO-TRANSFER);
|
||
|
* 2009-2012 (update and modification) Public Research Centre Henri Tudor (under the project TAO-SUSTAIN & TAO-DEV);
|
||
|
* 2013-2019 (update and modification) Open Assessment Technologies SA;
|
||
|
*/
|
||
|
|
||
|
use oat\oatbox\user\User;
|
||
|
use oat\tao\model\http\LegacyController;
|
||
|
use oat\tao\helpers\LegacySessionUtils;
|
||
|
use oat\tao\model\action\CommonModuleInterface;
|
||
|
use oat\tao\model\mvc\RendererTrait;
|
||
|
use oat\tao\model\security\ActionProtector;
|
||
|
use oat\tao\helpers\Template;
|
||
|
use oat\tao\helpers\JavaScript;
|
||
|
use oat\oatbox\service\ServiceManager;
|
||
|
use oat\tao\model\accessControl\AclProxy;
|
||
|
use oat\oatbox\service\ServiceManagerAwareTrait;
|
||
|
use oat\oatbox\service\ServiceManagerAwareInterface;
|
||
|
use oat\tao\model\accessControl\ActionAccessControl;
|
||
|
use oat\tao\model\accessControl\Context as AclContext;
|
||
|
use oat\oatbox\service\exception\InvalidServiceManagerException;
|
||
|
use oat\oatbox\log\LoggerAwareTrait;
|
||
|
|
||
|
use function GuzzleHttp\Psr7\stream_for;
|
||
|
|
||
|
use oat\tao\model\security\xsrf\TokenService;
|
||
|
use Zend\ServiceManager\ServiceLocatorInterface;
|
||
|
|
||
|
/**
|
||
|
* Top level controller
|
||
|
* All children extensions module should extends the CommonModule to access the shared data
|
||
|
*
|
||
|
* @author CRP Henri Tudor - TAO Team - {@link http://www.tao.lu}
|
||
|
* @license GPLv2 http://www.opensource.org/licenses/gpl-2.0.php
|
||
|
* @package tao
|
||
|
*
|
||
|
*/
|
||
|
abstract class tao_actions_CommonModule extends LegacyController implements ServiceManagerAwareInterface, CommonModuleInterface
|
||
|
{
|
||
|
use ServiceManagerAwareTrait {
|
||
|
getServiceManager as protected getOriginalServiceManager;
|
||
|
getServiceLocator as protected getOriginalServiceLocator;
|
||
|
setServiceLocator as protected setOriginalServiceLocator;
|
||
|
}
|
||
|
use LoggerAwareTrait;
|
||
|
use RendererTrait {
|
||
|
setView as protected setRendererView;
|
||
|
}
|
||
|
use LegacySessionUtils;
|
||
|
|
||
|
/**
|
||
|
* The Modules access the models through the service instance
|
||
|
*
|
||
|
* @var tao_models_classes_Service
|
||
|
* @deprecated
|
||
|
*/
|
||
|
protected $service;
|
||
|
|
||
|
/**
|
||
|
* tao_actions_CommonModule constructor.
|
||
|
* @security("hide");
|
||
|
*/
|
||
|
public function __construct()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @inheritdoc
|
||
|
*/
|
||
|
public function initialize()
|
||
|
{
|
||
|
/** @var ActionProtector $actionProtector */
|
||
|
$actionProtector = $this->getServiceLocator()->get(ActionProtector::SERVICE_ID);
|
||
|
$actionProtector->setHeaders();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Whenever or not the current user has access to a specific action
|
||
|
* using functional and data access control
|
||
|
*
|
||
|
* @param string $controllerClass
|
||
|
* @param string $action
|
||
|
* @param array $parameters
|
||
|
* @return boolean
|
||
|
* @throws common_exception_Error
|
||
|
*/
|
||
|
protected function hasAccess($controllerClass, $action, $parameters = [])
|
||
|
{
|
||
|
$user = $this->getSession()->getUser();
|
||
|
return AclProxy::hasAccess($user, $controllerClass, $action, $parameters);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated Use $this->hasWriteAccessByContext()
|
||
|
*/
|
||
|
protected function hasWriteAccessToAction(string $action, ?User $user = null): bool
|
||
|
{
|
||
|
$context = new AclContext([
|
||
|
AclContext::PARAM_CONTROLLER => static::class,
|
||
|
AclContext::PARAM_ACTION => $action,
|
||
|
AclContext::PARAM_USER => $user,
|
||
|
]);
|
||
|
|
||
|
return $this->hasWriteAccessByContext($context);
|
||
|
}
|
||
|
|
||
|
protected function hasReadAccessByContext(AclContext $context): bool
|
||
|
{
|
||
|
return $this->getActionAccessControl()->contextHasReadAccess($context);
|
||
|
}
|
||
|
|
||
|
protected function hasWriteAccessByContext(AclContext $context): bool
|
||
|
{
|
||
|
return $this->getActionAccessControl()->contextHasWriteAccess($context);
|
||
|
}
|
||
|
|
||
|
protected function getUserRoles(): array
|
||
|
{
|
||
|
return $this->getSession()->getUser()->getRoles();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @see Module::setView()
|
||
|
* @param string $path
|
||
|
* view identifier
|
||
|
* @param string $extensionID
|
||
|
* use the views in the specified extension instead of the current extension
|
||
|
*/
|
||
|
public function setView($path, $extensionID = null)
|
||
|
{
|
||
|
$this->setRendererView(Template::getTemplate($path, $extensionID));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retrieve the data from the url and make the base initialization
|
||
|
*
|
||
|
* @return void
|
||
|
* @throws common_ext_ExtensionException
|
||
|
*/
|
||
|
protected function defaultData()
|
||
|
{
|
||
|
$context = Context::getInstance();
|
||
|
|
||
|
$this->setData('extension', $context->getExtensionName());
|
||
|
$this->setData('module', $context->getModuleName());
|
||
|
$this->setData('action', $context->getActionName());
|
||
|
|
||
|
if ($this->hasRequestParameter('uri')) {
|
||
|
// inform the client of new classUri
|
||
|
$this->setData('uri', $this->getRequestParameter('uri'));
|
||
|
}
|
||
|
|
||
|
if ($this->hasRequestParameter('classUri')) {
|
||
|
// inform the client of new classUri
|
||
|
$this->setData('uri', $this->getRequestParameter('classUri'));
|
||
|
}
|
||
|
|
||
|
if ($this->getRequestParameter('message')) {
|
||
|
$this->setData('message', $this->getRequestParameter('message'));
|
||
|
}
|
||
|
if ($this->getRequestParameter('errorMessage')) {
|
||
|
$this->setData('errorMessage', $this->getRequestParameter('errorMessage'));
|
||
|
}
|
||
|
|
||
|
$this->setData('client_timeout', $this->getClientTimeout());
|
||
|
$this->setData('client_config_url', $this->getClientConfigUrl());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Function to return an user readable error
|
||
|
* Does not work with ajax Requests yet
|
||
|
*
|
||
|
* @param string $description error to show
|
||
|
* @param boolean $returnLink whenever or not to add a return link
|
||
|
* @param int $httpStatus
|
||
|
* @throws common_Exception
|
||
|
*/
|
||
|
protected function returnError($description, $returnLink = true, $httpStatus = null)
|
||
|
{
|
||
|
if ($this->isXmlHttpRequest()) {
|
||
|
$this->logWarning('Called ' . __FUNCTION__ . ' in an unsupported AJAX context');
|
||
|
throw new common_Exception($description);
|
||
|
}
|
||
|
$this->setData('message', $description);
|
||
|
$this->setData('returnLink', $returnLink);
|
||
|
if (parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) == parse_url(ROOT_URL, PHP_URL_HOST)) {
|
||
|
$this->setData('returnUrl', htmlentities($_SERVER['HTTP_REFERER'],ENT_QUOTES));
|
||
|
}else{
|
||
|
$this->setData('returnUrl', false);
|
||
|
}
|
||
|
if ($httpStatus !== null && file_exists(Template::getTemplate("error/error${httpStatus}.tpl"))) {
|
||
|
$this->setView("error/error${httpStatus}.tpl", 'tao');
|
||
|
} else {
|
||
|
$this->setView('error/user_error.tpl', 'tao');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the absolute path to the specified template
|
||
|
*
|
||
|
* @param string $identifier
|
||
|
* @param string $extensionID
|
||
|
* @return string
|
||
|
* @throws common_exception_Error
|
||
|
* @throws common_ext_ExtensionException
|
||
|
*/
|
||
|
protected static function getTemplatePath($identifier, $extensionID = null)
|
||
|
{
|
||
|
if ($extensionID === true) {
|
||
|
$extensionID = 'tao';
|
||
|
common_Logger::d('Deprecated use of setView() using a boolean');
|
||
|
}
|
||
|
if ($extensionID === null) {
|
||
|
$extensionID = Context::getInstance()->getExtensionName();
|
||
|
}
|
||
|
$ext = common_ext_ExtensionsManager::singleton()->getExtensionById($extensionID);
|
||
|
return $ext->getConstant('DIR_VIEWS') . 'templates' . DIRECTORY_SEPARATOR . $identifier;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helps you to add the URL of the client side config file
|
||
|
*
|
||
|
* @param array $extraParameters additional parameters to append to the URL
|
||
|
* @return string the URL
|
||
|
*/
|
||
|
protected function getClientConfigUrl($extraParameters = [])
|
||
|
{
|
||
|
return JavaScript::getClientConfigUrl($extraParameters);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the client timeout value from the config.
|
||
|
*
|
||
|
* @return int the timeout value in seconds
|
||
|
* @throws common_ext_ExtensionException
|
||
|
*/
|
||
|
protected function getClientTimeout()
|
||
|
{
|
||
|
$ext = $this->getServiceManager()->get(common_ext_ExtensionsManager::SERVICE_ID)->getExtensionById('tao');
|
||
|
$config = $ext->getConfig('js');
|
||
|
if ($config !== null && isset($config['timeout'])) {
|
||
|
return (int)$config['timeout'];
|
||
|
}
|
||
|
return 30;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return json response.
|
||
|
*
|
||
|
* @param array|\JsonSerializable $data
|
||
|
* @param int $httpStatus
|
||
|
*
|
||
|
* @deprecated use \oat\tao\model\http\HttpJsonResponseTrait::setSuccessJsonResponse for standard response
|
||
|
* @deprecated use \oat\tao\model\http\HttpJsonResponseTrait::setErrorJsonResponse for standard response
|
||
|
*/
|
||
|
protected function returnJson($data, $httpStatus = 200)
|
||
|
{
|
||
|
header(HTTPToolkit::statusCodeHeader($httpStatus));
|
||
|
Context::getInstance()->getResponse()->setContentHeader('application/json');
|
||
|
$this->response = $this->getPsrResponse()->withBody(stream_for(json_encode($data)));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a report
|
||
|
*
|
||
|
* @param common_report_Report $report
|
||
|
*/
|
||
|
protected function returnReport(common_report_Report $report)
|
||
|
{
|
||
|
$data = $report->getData();
|
||
|
$successes = $report->getSuccesses();
|
||
|
|
||
|
// if report has no data, try to get it from the sub report
|
||
|
while ($data === null && count($successes) > 0) {
|
||
|
$firstSubReport = current($successes);
|
||
|
$data = $firstSubReport->getData();
|
||
|
$successes = $firstSubReport->getSuccesses();
|
||
|
}
|
||
|
|
||
|
if ($data !== null && $data instanceof core_kernel_classes_Resource) {
|
||
|
$this->setData('selectNode', tao_helpers_Uri::encode($data->getUri()));
|
||
|
}
|
||
|
$this->setData('report', $report);
|
||
|
$this->setView('report.tpl', 'tao');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the current session
|
||
|
*
|
||
|
* @return common_session_Session
|
||
|
* @throws common_exception_Error
|
||
|
*/
|
||
|
protected function getSession()
|
||
|
{
|
||
|
return common_session_SessionManager::getSession();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the service Manager
|
||
|
*
|
||
|
* @deprecated Use $this->propagate or $this->registerService to access ServiceManager functionalities
|
||
|
* @deprecated To get the service dependencies manager, use $this->getServiceLocator
|
||
|
*
|
||
|
* @return ServiceManager
|
||
|
*/
|
||
|
protected function getServiceManager()
|
||
|
{
|
||
|
try {
|
||
|
$serviceManager = $this->getOriginalServiceManager();
|
||
|
} catch (InvalidServiceManagerException $e) {
|
||
|
$serviceManager = ServiceManager::getServiceManager();
|
||
|
}
|
||
|
return $serviceManager;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return ServiceLocatorInterface
|
||
|
* @security("hide");
|
||
|
*/
|
||
|
public function getServiceLocator()
|
||
|
{
|
||
|
return $this->getOriginalServiceLocator();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param ServiceLocatorInterface $serviceLocator
|
||
|
* @return mixed
|
||
|
* @security("hide");
|
||
|
*/
|
||
|
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
|
||
|
{
|
||
|
return $this->setOriginalServiceLocator($serviceLocator);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Validate a CSRF token, based on the CSRF header.
|
||
|
*
|
||
|
* @throws common_exception_Unauthorized
|
||
|
*/
|
||
|
protected function validateCsrf()
|
||
|
{
|
||
|
if (!$this->getPsrRequest()->hasHeader(TokenService::CSRF_TOKEN_HEADER)) {
|
||
|
$this->logCsrfFailure(sprintf('Missing %s header.', TokenService::CSRF_TOKEN_HEADER));
|
||
|
}
|
||
|
|
||
|
$csrfTokenHeader = $this->getPsrRequest()->getHeader(TokenService::CSRF_TOKEN_HEADER);
|
||
|
$csrfToken = current($csrfTokenHeader);
|
||
|
|
||
|
/** @var TokenService $tokenService */
|
||
|
$tokenService = $this->getServiceLocator()->get(TokenService::SERVICE_ID);
|
||
|
$newToken = null;
|
||
|
|
||
|
try {
|
||
|
if ($tokenService->validateToken($csrfToken)) {
|
||
|
$newToken = $tokenService->createToken()->getValue();
|
||
|
}
|
||
|
} catch (common_exception_Unauthorized $e) {
|
||
|
$this->logCsrfFailure($e->getMessage(), $csrfToken);
|
||
|
}
|
||
|
|
||
|
$this->response = $this->getPsrResponse()->withHeader(TokenService::CSRF_TOKEN_HEADER, $newToken);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Logs a CSRF validation error
|
||
|
*
|
||
|
* @param string $exceptionMessage
|
||
|
* @param null $token
|
||
|
* @throws common_exception_Unauthorized
|
||
|
*/
|
||
|
private function logCsrfFailure($exceptionMessage, $token = null)
|
||
|
{
|
||
|
try {
|
||
|
$userIdentifier = $this->getSession()->getUser()->getIdentifier();
|
||
|
} catch (common_exception_Error $e) {
|
||
|
$this->logError('Unable to retrieve session! ' . $e->getMessage());
|
||
|
throw new common_exception_Unauthorized($exceptionMessage);
|
||
|
}
|
||
|
|
||
|
$requestMethod = $this->getPsrRequest()->getMethod();
|
||
|
$requestUri = $this->getPsrRequest()->getUri();
|
||
|
$requestHeaders = $this->getHeaders();
|
||
|
|
||
|
$this->logWarning(
|
||
|
'[CSRF] - Failed to validate CSRF token. The following exception occurred: ' . $exceptionMessage
|
||
|
);
|
||
|
$this->logWarning(
|
||
|
"[CSRF] \n" .
|
||
|
"CSRF validation information: \n" .
|
||
|
'Provided token: ' . ($token ?: 'none') . " \n" .
|
||
|
'User identifier: ' . $userIdentifier . " \n" .
|
||
|
'Request: [' . $requestMethod . '] ' . $requestUri . " \n" .
|
||
|
"Request Headers : \n" .
|
||
|
urldecode(http_build_query($requestHeaders, '', "\n"))
|
||
|
);
|
||
|
|
||
|
throw new common_exception_Unauthorized($exceptionMessage);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Ensure the template is rendered as part of the response
|
||
|
* {@inheritDoc}
|
||
|
* @see \oat\tao\model\http\Controller::getPsrResponse()
|
||
|
*/
|
||
|
public function getPsrResponse()
|
||
|
{
|
||
|
$response = parent::getPsrResponse();
|
||
|
return $this->hasView()
|
||
|
? $response->withBody(stream_for($this->getRenderer()->render()))
|
||
|
: $response;
|
||
|
}
|
||
|
|
||
|
private function getActionAccessControl(): ActionAccessControl
|
||
|
{
|
||
|
return $this->getServiceLocator()->get(ActionAccessControl::SERVICE_ID);
|
||
|
}
|
||
|
}
|