diff --git a/application/Espo/Core/Api/Request.php b/application/Espo/Core/Api/Request.php index ad21beb86e..0140fdab4c 100644 --- a/application/Espo/Core/Api/Request.php +++ b/application/Espo/Core/Api/Request.php @@ -63,6 +63,11 @@ interface Request */ public function getRouteParam(string $name) : ?string; + /** + * Get all route parameters. + */ + public function getRouteParams() : array; + /** * Get a header value. */ diff --git a/application/Espo/Core/Api/RequestWrapper.php b/application/Espo/Core/Api/RequestWrapper.php index cb688328f8..0fa303afc1 100644 --- a/application/Espo/Core/Api/RequestWrapper.php +++ b/application/Espo/Core/Api/RequestWrapper.php @@ -55,10 +55,6 @@ class RequestWrapper implements ApiRequest { $this->request = $request; $this->basePath = $basePath; - - unset($routeParams['controller']); - unset($routeParams['actioon']); - $this->routeParams = $routeParams; } @@ -95,6 +91,11 @@ class RequestWrapper implements ApiRequest return $this->routeParams[$name] ?? null; } + public function getRouteParams() : array + { + return $this->routeParams; + } + public function hasQueryParam(string $name) : bool { return array_key_exists($name, $this->request->getQueryParams()); diff --git a/application/Espo/Core/Api/RouteProcessor.php b/application/Espo/Core/Api/RouteProcessor.php index e839bfec36..ca6a586b9e 100644 --- a/application/Espo/Core/Api/RouteProcessor.php +++ b/application/Espo/Core/Api/RouteProcessor.php @@ -35,6 +35,7 @@ use Espo\Core\{ Utils\Config, Utils\Json, ControllerManager, + Exceptions\Error, }; use StdClass; @@ -53,33 +54,11 @@ class RouteProcessor $this->controllerManager = $controllerManager; } - public function process(string $route, array $routeParams, RequestWrapper $request, ResponseWrapper $response, array $args) + public function process(string $route, RequestWrapper $request, ResponseWrapper $response) { $response->setHeader('Content-Type', 'application/json'); - $params = []; - - $paramKeys = array_keys($routeParams); - - $setKeyList = []; - - foreach ($paramKeys as $key) { - $value = $routeParams[$key]; - - $paramName = $key; - if ($value[0] === ':') { - $realKey = substr($value, 1); - $params[$paramName] = $args[$realKey]; - $setKeyList[] = $realKey; - } else { - $params[$paramName] = $value; - } - } - - foreach ($args as $key => $value) { - if (in_array($key, $setKeyList)) continue; - $params[$key] = $value; - } + $params = $request->getRouteParams(); $controllerName = $params['controller'] ?? null; $actionName = $params['action'] ?? null; @@ -107,9 +86,7 @@ class RouteProcessor unset($params['controller']); unset($params['action']); - $result = $this->controllerManager->process( - $controllerName, $requestMethod, $actionName, $params, $request, $response - ) ?? null; + $result = $this->controllerManager->process($controllerName, $actionName, $request, $response) ?? null; $responseContents = $result; diff --git a/application/Espo/Core/ApplicationRunners/Api.php b/application/Espo/Core/ApplicationRunners/Api.php index b32195a50f..ac3fbc2735 100644 --- a/application/Espo/Core/ApplicationRunners/Api.php +++ b/application/Espo/Core/ApplicationRunners/Api.php @@ -105,7 +105,10 @@ class Api implements ApplicationRunner $slim->$method( $route, function (Psr7Request $request, Psr7Response $response, array $args) use ($item, $slim) { - $requestWrapped = new RequestWrapper($request, $slim->getBasePath(), $args); + $routeParams = $this->getRouteParams($item, $args); + + $requestWrapped = new RequestWrapper($request, $slim->getBasePath(), $routeParams); + $responseWrapped = new ResponseWrapper($response); $this->processRequest($item, $requestWrapped, $responseWrapped, $args); @@ -115,6 +118,45 @@ class Api implements ApplicationRunner ); } + protected function getRouteParams(array $item, array $args) : array + { + $params = []; + + $routeParams = $item['params'] ?? []; + + $paramKeys = array_keys($routeParams); + + $setKeyList = []; + + foreach ($paramKeys as $key) { + $value = $routeParams[$key]; + + $paramName = $key; + + if ($value[0] === ':') { + $realKey = substr($value, 1); + + $params[$paramName] = $args[$realKey]; + + $setKeyList[] = $realKey; + + continue; + } + + $params[$paramName] = $value; + } + + foreach ($args as $key => $value) { + if (in_array($key, $setKeyList)) { + continue; + } + + $params[$key] = $value; + } + + return $params; + } + protected function processRequest(array $item, RequestWrapper $requestWrapped, ResponseWrapper $responseWrapped, array $args) { try { @@ -141,7 +183,7 @@ class Api implements ApplicationRunner ob_start(); - $routeProcessor->process($item['route'], $item['params'], $requestWrapped, $responseWrapped, $args); + $routeProcessor->process($item['route'], $requestWrapped, $responseWrapped); ob_clean(); } @@ -177,6 +219,7 @@ class Api implements ApplicationRunner if (!in_array($method, $this->allowedMethodList)) { $GLOBALS['log']->warning("Route: Method '{$method}' is not supported. Fix the route '{$route}'."); + return false; } return true; diff --git a/application/Espo/Core/ControllerManager.php b/application/Espo/Core/ControllerManager.php index 5b622231c2..5797fde92e 100644 --- a/application/Espo/Core/ControllerManager.php +++ b/application/Espo/Core/ControllerManager.php @@ -56,16 +56,12 @@ class ControllerManager $this->classFinder = $classFinder; } - public function process( - string $controllerName, - string $requestMethod, - string $actionName, - array $params, - Request $request, - Response $response - ) { + public function process(string $controllerName, string $actionName, Request $request, Response $response) + { $controller = $this->createController($controllerName); + $requestMethod = $request->getMethod(); + if ($actionName == 'index') { $actionName = $controller::$defaultAction ?? 'index'; } @@ -102,6 +98,8 @@ class ControllerManager $data = json_decode($data); } + $params = $request->getRouteParams(); + $beforeMethodName = 'before' . $actionNameUcfirst; if (method_exists($controller, $beforeMethodName)) { diff --git a/tests/integration/Core/BaseTestCase.php b/tests/integration/Core/BaseTestCase.php index 2e3297082d..bfa7f0f469 100644 --- a/tests/integration/Core/BaseTestCase.php +++ b/tests/integration/Core/BaseTestCase.php @@ -173,7 +173,7 @@ abstract class BaseTestCase extends \PHPUnit\Framework\TestCase } protected function createRequest( - string $method, array $queryParams = [], array $headers = [], ?string $body = null + string $method, array $queryParams = [], array $headers = [], ?string $body = null, array $routeParams = [] ) : RequestWrapper { $request = (new RequestFactory())->createRequest($method, 'http://localhost/?' . http_build_query($queryParams)); @@ -187,7 +187,7 @@ abstract class BaseTestCase extends \PHPUnit\Framework\TestCase ); } - return new RequestWrapper($request); + return new RequestWrapper($request, '', $routeParams); } protected function createResponse() diff --git a/tests/integration/Espo/User/AclTest.php b/tests/integration/Espo/User/AclTest.php index 23e8a2ac4c..790658e297 100644 --- a/tests/integration/Espo/User/AclTest.php +++ b/tests/integration/Espo/User/AclTest.php @@ -29,6 +29,8 @@ namespace tests\integration\Espo\User; +use Espo\Core\ControllerManager; + class AclTest extends \tests\integration\Core\BaseTestCase { protected $dataFile = 'User/Login.php'; @@ -47,7 +49,7 @@ class AclTest extends \tests\integration\Core\BaseTestCase public function testUserAccess0() { - $this->expectException('\\Espo\\Core\\Exceptions\\Forbidden'); + $this->expectException('Espo\\Core\\Exceptions\\Forbidden'); $this->createUser('tester', array( 'assignmentPermission' => 'team', @@ -77,18 +79,21 @@ class AclTest extends \tests\integration\Core\BaseTestCase $app = $this->createApplication(); - $controllerManager = $app->getContainer()->get('controllerManager'); + $controllerManager = $app->getContainer()->get('injectableFactory')->create(ControllerManager::class); - $request = $this->createRequest('POST', [], ['Content-Type' => 'application/json']); + $request = $this->createRequest( + 'POST', + [], + ['Content-Type' => 'application/json'], + '{"name":"Test Account"}' + ); - $data = json_decode('{"name":"Test Account"}'); - - $result = $controllerManager->process('Account', 'POST', 'create', [], $data, $request, $this->createResponse()); + $result = $controllerManager->process('Account', 'create', $request, $this->createResponse()); } public function testPortalUserAccess() { - $this->expectException('\\Espo\\Core\\Exceptions\\Forbidden'); + $this->expectException('Espo\\Core\\Exceptions\\Forbidden'); $newUser = $this->createUser(array( 'userName' => 'tester', @@ -117,12 +122,13 @@ class AclTest extends \tests\integration\Core\BaseTestCase $app = $this->createApplication(); - $controllerManager = $app->getContainer()->get('controllerManager'); + $controllerManager = $app->getContainer()->get('injectableFactory')->create(ControllerManager::class); - $params = []; $data = json_decode('{"name":"Test Account"}'); - $request = $this->createRequest('POST', $params, ['Content-Type' => 'application/json']); - $result = $controllerManager->process('Account', 'POST', 'create', $params, $data, $request, $this->createResponse()); + + $request = $this->createRequest('POST', [], ['Content-Type' => 'application/json'], '{"name":"Test Account"}'); + + $result = $controllerManager->process('Account', 'create', $request, $this->createResponse()); } public function testUserAccessEditOwn1() @@ -140,54 +146,68 @@ class AclTest extends \tests\integration\Core\BaseTestCase $user2 = $this->createUser('test-2', []); $this->auth('test-1'); + $app = $this->createApplication(); - $controllerManager = $app->getContainer()->get('controllerManager'); + + $controllerManager = $app->getContainer()->get('injectableFactory')->create(ControllerManager::class); $params = [ - 'id' => $user1->id + 'id' => $user1->id, ]; + $data = (object) [ 'id' => $user1->id, 'title' => 'Test' ]; - $request = $this->createRequest('PATCH', $params, ['Content-Type' => 'application/json']); + $request = $this->createRequest('PATCH', [], ['Content-Type' => 'application/json'], json_encode($data), $params); $result = $controllerManager->process( - 'User', 'PATCH', 'update', $params, $data, $request, $this->createResponse()); + 'User', 'update', $request, $this->createResponse() + ); $this->assertTrue(is_object($result)); $params = [ - 'id' => $user2->id + 'id' => $user2->id, ]; + $data = (object) [ 'id' => $user2->id, 'title' => 'Test' ]; - $request = $this->createRequest('PATCH', $params, ['Content-Type' => 'application/json']); + + $request = $this->createRequest('PATCH', [], ['Content-Type' => 'application/json'], json_encode($data), $params); $result = null; + try { $result = $controllerManager->process( - 'User', 'PATCH', 'update', $params, $data, $request, $this->createResponse()); - } catch (\Exception $e) {}; + 'User', 'update', $request, $this->createResponse() + ); + } + catch (\Exception $e) {}; $this->assertNull($result); - $params = [ 'id' => $user1->id ]; + $data = (object) [ 'id' => $user1->id, 'type' => 'admin', 'teamsIds' => ['id'] ]; - $request = $this->createRequest('PATCH', $params, ['Content-Type' => 'application/json']); - $resultData = $controllerManager->process('User', 'PATCH', 'update', $params, $data, $request, $this->createResponse()); + + $request = $this->createRequest('PATCH', [], ['Content-Type' => 'application/json'], json_encode($data), $params); + + $resultData = $controllerManager->process( + 'User', 'update', $request, $this->createResponse() + ); $this->assertTrue(!property_exists($resultData, 'type') || $resultData->type !== 'admin'); + $this->assertTrue( !property_exists($resultData, 'teamsIds') || !is_array($resultData->teamsIds) || !in_array('id', $resultData->teamsIds) @@ -207,24 +227,30 @@ class AclTest extends \tests\integration\Core\BaseTestCase ]); $this->auth('test-1'); + $app = $this->createApplication(); - $controllerManager = $app->getContainer()->get('controllerManager'); + + $controllerManager = $app->getContainer()->get('injectableFactory')->create(ControllerManager::class); $params = [ 'id' => $user1->id ]; + $data = (object) [ 'id' => $user1->id, 'title' => 'Test' ]; - $request = $this->createRequest('PUT', $params, ['Content-Type' => 'application/json']); + + $request = $this->createRequest('PUT', [], ['Content-Type' => 'application/json'], json_encode($data), $params); $result = null; + try { $result = $controllerManager->process( - 'User', 'PUT', 'update', $params, $data, $request, $this->createResponse() + 'User', 'update', $request, $this->createResponse() ); - } catch (\Exception $e) {}; + } + catch (\Exception $e) {}; $this->assertNull($result); } diff --git a/tests/integration/Espo/Webhook/AclTest.php b/tests/integration/Espo/Webhook/AclTest.php index 1a781b21fd..9e159ef943 100644 --- a/tests/integration/Espo/Webhook/AclTest.php +++ b/tests/integration/Espo/Webhook/AclTest.php @@ -29,6 +29,8 @@ namespace tests\integration\Espo\Webhook; +use Espo\Core\ControllerManager; + class AclTest extends \tests\integration\Core\BaseTestCase { @@ -51,15 +53,13 @@ class AclTest extends \tests\integration\Core\BaseTestCase $app = $this->createApplication(); - $controllerManager = $app->getContainer()->get('controllerManager'); + $controllerManager = $app->getContainer()->get('injectableFactory')->create(ControllerManager::class); $this->expectException(\Espo\Core\Exceptions\Forbidden::class); - $request = $this->createRequest('POST', [], ['Content-Type' => 'application/json']); + $request = $this->createRequest('POST', [], ['Content-Type' => 'application/json'], '{"event":"Account.create"}'); - $data = json_decode('{"event":"Account.create"}'); - - $result = $controllerManager->process('Webhook', 'POST', 'create', [], $data, $request, $this->createResponse()); + $result = $controllerManager->process('Webhook', 'create', $request, $this->createResponse()); } public function testApiUserNoAccess1() @@ -78,22 +78,27 @@ class AclTest extends \tests\integration\Core\BaseTestCase ] ); - $request = $this->createRequest('POST', [], [ - 'Content-Type' => 'application/json', - 'X-Api-Key' => 'test-key', - ]); + $request = $this->createRequest( + 'POST', + [], + [ + 'Content-Type' => 'application/json', + 'X-Api-Key' => 'test-key', + ], + '{"event":"Account.create", "url": "https://test"}' + ); $this->auth(null, null, null, 'ApiKey', $request); - $data = json_decode('{"event":"Account.create", "url": "https://test"}'); + $data = json_decode(); $app = $this->createApplication(); - $controllerManager = $app->getContainer()->get('controllerManager'); + $controllerManager = $app->getContainer()->get('injectableFactory')->create(ControllerManager::class); $this->expectException(\Espo\Core\Exceptions\Forbidden::class); - $result = $controllerManager->process('Webhook', 'POST', 'create', [], $data, $request, $this->createResponse()); + $result = $controllerManager->process('Webhook', 'create', $request, $this->createResponse()); } public function testApiUserNoAccess2() @@ -113,22 +118,25 @@ class AclTest extends \tests\integration\Core\BaseTestCase ] ); - $request = $this->createRequest('POST', [], [ - 'Content-Type' => 'application/json', - 'X-Api-Key' => 'test-key', - ]); + $request = $this->createRequest( + 'POST', + [], + [ + 'Content-Type' => 'application/json', + 'X-Api-Key' => 'test-key', + ], + '{"event":"Account.create", "url": "https://test"}' + ); $this->auth(null, null, null, 'ApiKey', $request); - $data = json_decode('{"event":"Account.create", "url": "https://test"}'); - $app = $this->createApplication(); - $controllerManager = $app->getContainer()->get('controllerManager'); + $controllerManager = $app->getContainer()->get('injectableFactory')->create(ControllerManager::class); $this->expectException(\Espo\Core\Exceptions\Forbidden::class); - $result = $controllerManager->process('Webhook', 'POST', 'create', [], $data, $request, $this->createResponse()); + $result = $controllerManager->process('Webhook', 'create', $request, $this->createResponse()); } public function testApiUserHasAccess1() @@ -148,20 +156,23 @@ class AclTest extends \tests\integration\Core\BaseTestCase ] ); - $request = $this->createRequest('POST', [], [ - 'Content-Type' => 'application/json', - 'X-Api-Key' => 'test-key', - ]); + $request = $this->createRequest( + 'POST', + [], + [ + 'Content-Type' => 'application/json', + 'X-Api-Key' => 'test-key', + ], + '{"event":"Account.create", "url": "https://test"}' + ); $this->auth(null, null, null, 'ApiKey', $request); $app = $this->createApplication(); - $controllerManager = $app->getContainer()->get('controllerManager'); + $controllerManager = $app->getContainer()->get('injectableFactory')->create(ControllerManager::class); - $data = json_decode('{"event":"Account.create", "url": "https://test"}'); - - $result = $controllerManager->process('Webhook', 'POST', 'create', [], $data, $request, $this->createResponse()); + $result = $controllerManager->process('Webhook', 'create', $request, $this->createResponse()); $this->assertTrue(!empty($result)); }