284 lines
9.8 KiB
PHP
284 lines
9.8 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) 2014-2021 (original work) Open Assessment Technologies SA;
|
|
*
|
|
*
|
|
*/
|
|
|
|
namespace oat\tao\model\routing;
|
|
|
|
use Context;
|
|
use GuzzleHttp\Psr7\Response;
|
|
use GuzzleHttp\Psr7\ServerRequest;
|
|
use oat\tao\model\Middleware\MiddlewareRequestHandler;
|
|
use ReflectionException;
|
|
use IExecutable;
|
|
use ActionEnforcingException;
|
|
use oat\tao\model\http\ResponseEmitter;
|
|
use oat\oatbox\service\ServiceManagerAwareInterface;
|
|
use oat\oatbox\service\ServiceManagerAwareTrait;
|
|
use oat\tao\model\http\Controller;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
use ReflectionMethod;
|
|
use common_session_SessionManager;
|
|
use tao_models_classes_AccessDeniedException;
|
|
use oat\tao\model\accessControl\AclProxy;
|
|
use oat\tao\model\accessControl\data\DataAccessControl;
|
|
use oat\tao\model\accessControl\data\PermissionException;
|
|
use oat\tao\model\accessControl\func\AclProxy as FuncProxy;
|
|
use oat\oatbox\event\EventManager;
|
|
use oat\tao\model\event\BeforeAction;
|
|
use oat\oatbox\log\LoggerAwareTrait;
|
|
use oat\oatbox\log\TaoLoggerAwareInterface;
|
|
use oat\tao\model\action\CommonModuleInterface;
|
|
|
|
/**
|
|
* @TODO ActionEnforcer class documentation.
|
|
*
|
|
* @author Jerome Bogaerts <jerome@taotesting.com>
|
|
* @author Joel Bout <joel@taotesting.com>
|
|
*/
|
|
class ActionEnforcer implements IExecutable, ServiceManagerAwareInterface, TaoLoggerAwareInterface
|
|
{
|
|
use ServiceManagerAwareTrait;
|
|
use LoggerAwareTrait;
|
|
|
|
private $extension;
|
|
|
|
private $controllerClass;
|
|
private $action;
|
|
private $parameters;
|
|
|
|
private $request;
|
|
private $response;
|
|
|
|
public function __construct($extensionId, $controller, $action, array $parameters)
|
|
{
|
|
$this->extension = $extensionId;
|
|
$this->controllerClass = $controller;
|
|
$this->action = $action;
|
|
$this->parameters = $parameters;
|
|
}
|
|
|
|
protected function getExtensionId()
|
|
{
|
|
return $this->extension;
|
|
}
|
|
|
|
protected function getControllerClass()
|
|
{
|
|
return $this->controllerClass;
|
|
}
|
|
|
|
protected function getAction()
|
|
{
|
|
return $this->action;
|
|
}
|
|
|
|
protected function getParameters()
|
|
{
|
|
return $this->parameters;
|
|
}
|
|
|
|
protected function getController()
|
|
{
|
|
$controllerClass = $this->getControllerClass();
|
|
if (!class_exists($controllerClass)) {
|
|
throw new ActionEnforcingException('Controller "' . $controllerClass . '" could not be loaded.', $controllerClass, $this->getAction());
|
|
}
|
|
|
|
$controller = $this->getClassInstance($controllerClass);
|
|
|
|
$this->propagate($controller);
|
|
if ($controller instanceof Controller) {
|
|
$controller->setRequest($this->getRequest());
|
|
$controller->setResponse($this->getResponse());
|
|
}
|
|
if ($controller instanceof CommonModuleInterface) {
|
|
$controller->initialize();
|
|
}
|
|
return $controller;
|
|
}
|
|
|
|
private function getClassInstance(string $className): object
|
|
{
|
|
$serviceId = defined("$className::SERVICE_ID")
|
|
? $className::SERVICE_ID
|
|
: $className;
|
|
|
|
return $this->getServiceLocator()->has($serviceId)
|
|
? $this->getServiceLocator()->get($serviceId)
|
|
: $this->propagate(new $className);
|
|
}
|
|
|
|
protected function getRequest()
|
|
{
|
|
if (!$this->request) {
|
|
$this->request = ServerRequest::fromGlobals();
|
|
}
|
|
|
|
return $this->request;
|
|
}
|
|
|
|
protected function getResponse()
|
|
{
|
|
if (!$this->response) {
|
|
$this->response = new Response();
|
|
}
|
|
return $this->response;
|
|
}
|
|
|
|
/**
|
|
* @throws PermissionException
|
|
* @throws \common_exception_Error
|
|
* @throws \common_exception_MissingParameter
|
|
* @throws tao_models_classes_AccessDeniedException
|
|
*/
|
|
protected function verifyAuthorization()
|
|
{
|
|
$user = common_session_SessionManager::getSession()->getUser();
|
|
if (!AclProxy::hasAccess($user, $this->getControllerClass(), $this->getAction(), $this->getParameters())) {
|
|
$func = new FuncProxy();
|
|
$data = new DataAccessControl();
|
|
//now go into details to see which kind of permissions are not correct
|
|
if (
|
|
$func->hasAccess($user, $this->getControllerClass(), $this->getAction(), $this->getParameters()) &&
|
|
!$data->hasAccess($user, $this->getControllerClass(), $this->getAction(), $this->getParameters())
|
|
) {
|
|
throw new PermissionException($user->getIdentifier(), $this->getAction(), $this->getControllerClass(), $this->getExtensionId());
|
|
}
|
|
|
|
throw new tao_models_classes_AccessDeniedException($user->getIdentifier(), $this->getAction(), $this->getControllerClass(), $this->getExtensionId());
|
|
}
|
|
}
|
|
|
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response = null)
|
|
{
|
|
$this->request = $request;
|
|
$this->response = $response;
|
|
$this->execute();
|
|
}
|
|
|
|
/**
|
|
* @throws ActionEnforcingException
|
|
* @throws ReflectionException
|
|
* @throws \common_exception_Error
|
|
* @throws \common_exception_MissingParameter
|
|
* @throws tao_models_classes_AccessDeniedException
|
|
*/
|
|
public function execute()
|
|
{
|
|
// Are we authorized to execute this action?
|
|
try {
|
|
$this->verifyAuthorization();
|
|
} catch (PermissionException $pe) {
|
|
//forward the action (yes it's an awful hack, but far better than adding a step in Bootstrap's dispatch error).
|
|
Context::getInstance()->setExtensionName('tao');
|
|
$this->action = 'denied';
|
|
$this->controllerClass = 'tao_actions_Permission';
|
|
$this->extension = 'tao';
|
|
}
|
|
|
|
$response = $this->resolve($this->getRequest());
|
|
|
|
$emitter = new ResponseEmitter();
|
|
$emitter($response);
|
|
}
|
|
|
|
/**
|
|
* @throws ActionEnforcingException
|
|
* @throws ReflectionException
|
|
* @throws \common_exception_Error
|
|
*/
|
|
public function resolve(ServerRequestInterface $request): ResponseInterface
|
|
{
|
|
$this->request = $request;
|
|
|
|
/** @var ControllerService $controllerService */
|
|
$controllerService = $this->getServiceLocator()->get(ControllerService::SERVICE_ID);
|
|
try {
|
|
$controllerService->checkController($this->getControllerClass());
|
|
$action = $controllerService->getAction($this->getControllerClass(), $this->getAction());
|
|
} catch (RouterException $e) {
|
|
throw new ActionEnforcingException($e->getMessage(), $this->getControllerClass(), $this->getAction());
|
|
}
|
|
|
|
$this->response = $this->getMiddlewareRequestHandler()->withOriginalResponse($this->getResponse())->handle(
|
|
$request
|
|
);
|
|
|
|
$controller = $this->getController();
|
|
|
|
if (!method_exists($controller, $action)) {
|
|
throw new ActionEnforcingException(
|
|
"Unable to find the action '" . $action . "' in '" . get_class($controller) . "'.",
|
|
$this->getControllerClass(),
|
|
$this->getAction()
|
|
);
|
|
}
|
|
|
|
$actionParameters = $this->resolveParameters($request, $controller, $action);
|
|
|
|
// Action method is invoked, passing request parameters as method parameters.
|
|
$user = common_session_SessionManager::getSession()->getUser();
|
|
$this->logDebug('Invoking ' . get_class($controller) . '::' . $action . ' by ' . $user->getIdentifier(), ['GENERIS', 'CLEARRFW']);
|
|
|
|
$eventManager = $this->getServiceLocator()->get(EventManager::SERVICE_ID);
|
|
$eventManager->trigger(new BeforeAction());
|
|
|
|
$response = call_user_func_array([$controller, $action], $actionParameters);
|
|
|
|
return $response instanceof ResponseInterface ? $response : $controller->getPsrResponse();
|
|
}
|
|
|
|
/**
|
|
* @throws ReflectionException
|
|
*/
|
|
private function resolveParameters(ServerRequestInterface $request, $controller, string $action): array
|
|
{
|
|
// search parameters method
|
|
$reflect = new ReflectionMethod($controller, $action);
|
|
$parameters = $this->getParameters();
|
|
|
|
$actionParameters = [];
|
|
foreach ($reflect->getParameters() as $param) {
|
|
$paramName = $param->getName();
|
|
$paramType = $param->getType();
|
|
$paramTypeName = $paramType !== null ? $paramType->getName() : null;
|
|
|
|
if (isset($parameters[$paramName])) {
|
|
$actionParameters[$paramName] = $parameters[$paramName];
|
|
} elseif($paramTypeName === ServerRequest::class) {
|
|
$actionParameters[$paramName] = $request;
|
|
} elseif (class_exists($paramTypeName) || interface_exists($paramTypeName)) {
|
|
$actionParameters[$paramName] = $this->getClassInstance($paramTypeName);
|
|
} elseif (!$param->isDefaultValueAvailable()) {
|
|
$this->logWarning('Missing parameter ' . $paramName . ' for ' . $this->getControllerClass() . '@' . $action);
|
|
}
|
|
}
|
|
|
|
return $actionParameters;
|
|
}
|
|
|
|
private function getMiddlewareRequestHandler(): MiddlewareRequestHandler
|
|
{
|
|
return $this->getServiceManager()->get(MiddlewareRequestHandler::SERVICE_ID);
|
|
}
|
|
}
|