Merge pull request 'bugfixes/cors-problem' (#7) from bugfixes/cors-problem into master

Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
2021-04-13 12:44:21 +02:00
13 changed files with 203 additions and 2 deletions
+4 -1
View File
@@ -2,4 +2,7 @@
DATABASES_HOST=psql.domain.tld DATABASES_HOST=psql.domain.tld
DATABASES_NAME=db DATABASES_NAME=db
DATABASES_USER=user DATABASES_USER=user
DATABASES_PASS=1234 DATABASES_PASS=1234
## CORS Einstellungen
CORS_ALLOW_ORIGIN_REGEX=/https?:\/\/localhost:\d+/
+10
View File
@@ -7,10 +7,12 @@ $dotenv->load();
use DI\Container; use DI\Container;
use Slim\Factory\AppFactory; use Slim\Factory\AppFactory;
use Slim\Routing\RouteCollectorProxy; use Slim\Routing\RouteCollectorProxy;
use TorstenHettstedt\TimekeepingApi\Controller\PreflightController;
use TorstenHettstedt\TimekeepingApi\Controller\WorkingHoursController; use TorstenHettstedt\TimekeepingApi\Controller\WorkingHoursController;
use TorstenHettstedt\TimekeepingApi\Controller\WorkingHoursViewController; use TorstenHettstedt\TimekeepingApi\Controller\WorkingHoursViewController;
use TorstenHettstedt\TimekeepingApi\Middleware\ErrorHandler; use TorstenHettstedt\TimekeepingApi\Middleware\ErrorHandler;
use TorstenHettstedt\TimekeepingApi\Middleware\JsonBodyParserMiddleware; use TorstenHettstedt\TimekeepingApi\Middleware\JsonBodyParserMiddleware;
use TorstenHettstedt\TimekeepingApi\Middleware\OriginAccessControlHandler;
$container = new Container(); $container = new Container();
@@ -22,17 +24,25 @@ $container->set('databases', function () {
AppFactory::setContainer($container); AppFactory::setContainer($container);
$app = AppFactory::create(); $app = AppFactory::create();
$app->add(new JsonBodyParserMiddleware()); $app->add(new JsonBodyParserMiddleware());
$app->addBodyParsingMiddleware();
$app->add(new OriginAccessControlHandler());
$app->addRoutingMiddleware();
$app->group('/views/working-hours', function (RouteCollectorProxy $group) { $app->group('/views/working-hours', function (RouteCollectorProxy $group) {
$group->options('/weekly', PreflightController::class . ':preflight');
$group->get('/weekly', WorkingHoursViewController::class . ':browseWeekly'); $group->get('/weekly', WorkingHoursViewController::class . ':browseWeekly');
$group->options('/monthly', PreflightController::class . ':preflight');
$group->get('/monthly', WorkingHoursViewController::class . ':browseMonthly'); $group->get('/monthly', WorkingHoursViewController::class . ':browseMonthly');
$group->options('/yearly', PreflightController::class . ':preflight');
$group->get('/yearly', WorkingHoursViewController::class . ':browseYearly'); $group->get('/yearly', WorkingHoursViewController::class . ':browseYearly');
}); });
$app->group('/working-hours', function (RouteCollectorProxy $group) { $app->group('/working-hours', function (RouteCollectorProxy $group) {
$group->options('', PreflightController::class . ':preflight');
$group->get('', WorkingHoursController::class . ':browse'); $group->get('', WorkingHoursController::class . ':browse');
$group->post('', WorkingHoursController::class . ':creat'); $group->post('', WorkingHoursController::class . ':creat');
$group->group('/{id:\d\d\d\d-\d\d-\d\d}', function (RouteCollectorProxy $group) { $group->group('/{id:\d\d\d\d-\d\d-\d\d}', function (RouteCollectorProxy $group) {
$group->options('', PreflightController::class . ':preflight');
$group->get('', WorkingHoursController::class . ':read'); $group->get('', WorkingHoursController::class . ':read');
$group->put('', WorkingHoursController::class . ':update'); $group->put('', WorkingHoursController::class . ':update');
}); });
@@ -0,0 +1,18 @@
<?php
namespace TorstenHettstedt\TimekeepingApi\Controller;
use Psr\Http\Message\ResponseInterface;
use Slim\Psr7\Request;
use Slim\Psr7\Response;
use TorstenHettstedt\TimekeepingApi\Middleware\OriginAccessControlHandler;
class PreflightController
{
public function preflight(Request $request, Response $response): ResponseInterface
{
return (new OriginAccessControlHandler())->originAccessControl($request, $response);
}
}
+3 -1
View File
@@ -84,7 +84,9 @@ class ErrorHandler
json_encode($payload, JSON_UNESCAPED_UNICODE) json_encode($payload, JSON_UNESCAPED_UNICODE)
); );
return $response; $originAccessControlHandler = new OriginAccessControlHandler();
return $originAccessControlHandler->originAccessControl($request, $response);
} }
} }
@@ -0,0 +1,58 @@
<?php
namespace TorstenHettstedt\TimekeepingApi\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Routing\RouteContext;
class OriginAccessControlHandler implements MiddlewareInterface
{
/**
* Process an incoming server request.
*
* Processes an incoming server request in order to produce a response.
* If unable to produce the response itself, it may delegate to the provided
* request handler to do so.
*/
public function process(Request $request, RequestHandler $handler): Response
{
$response = $handler->handle($request);
return $this->originAccessControl($request, $response);
}
public function originAccessControl(Request $request, Response $response): Response
{
$routeContext = RouteContext::fromRequest($request);
$routingResults = $routeContext->getRoutingResults();
$methods = $routingResults->getAllowedMethods();
$requestHeaders = $request->getHeaderLine('Access-Control-Request-Headers');
if ($this->testRoute($request)) {
// Stunden Minuten Sekunden
$accessControlMaxAge = 24 * 60 * 60;
$response = $response->withHeader('Access-Control-Allow-Origin', $this->buildOrigin($request));
$response = $response->withHeader('Access-Control-Allow-Methods', implode(',', $methods));
$response = $response->withHeader('Access-Control-Allow-Headers', $requestHeaders);
$response = $response->withHeader('Access-Control-Max-Age', (string)$accessControlMaxAge);
}
return $response;
}
protected function testRoute(Request $request): bool
{
$corsAllowOriginRegex = $_ENV['CORS_ALLOW_ORIGIN_REGEX'] ?? '/https?:\/\/.*:.*/';
return preg_match($corsAllowOriginRegex, $this->buildOrigin($request));
}
protected function buildOrigin(Request $request): string
{
return $request->getHeaderLine('Origin') ?? '';
}
}
+2
View File
@@ -32,4 +32,6 @@ class Api extends Module
"message" => "string", "message" => "string",
"path" => "string", "path" => "string",
]; ];
const TEST_ORIGIN = 'http://localhost:1234';
} }
@@ -8,13 +8,27 @@ use Helper\Api;
class BrowseWorkingHoursMonthlyCest class BrowseWorkingHoursMonthlyCest
{ {
public static function preflight(ApiTester $I): void
{
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->haveHttpHeader('Access-Control-Request-Method', 'GET');
$I->sendOptions('/views/working-hours/monthly');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeHttpHeader('Access-Control-Allow-Methods');
$I->seeHttpHeader('Access-Control-Allow-Headers');
$I->seeHttpHeader('Access-Control-Max-Age');
}
/** /**
* @param ApiTester $I * @param ApiTester $I
*/ */
public function browseWorkingHoursView(ApiTester $I): void public function browseWorkingHoursView(ApiTester $I): void
{ {
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendGet('/views/working-hours/monthly'); $I->sendGet('/views/working-hours/monthly');
$I->seeResponseCodeIs(HttpCode::OK); $I->seeResponseCodeIs(HttpCode::OK);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT);
} }
@@ -8,13 +8,27 @@ use Helper\Api;
class BrowseWorkingHoursWeeklyCest class BrowseWorkingHoursWeeklyCest
{ {
public static function preflight(ApiTester $I): void
{
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->haveHttpHeader('Access-Control-Request-Method', 'GET');
$I->sendOptions('/views/working-hours/weekly');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeHttpHeader('Access-Control-Allow-Methods');
$I->seeHttpHeader('Access-Control-Allow-Headers');
$I->seeHttpHeader('Access-Control-Max-Age');
}
/** /**
* @param ApiTester $I * @param ApiTester $I
*/ */
public function browseWorkingHoursView(ApiTester $I): void public function browseWorkingHoursView(ApiTester $I): void
{ {
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendGet('/views/working-hours/weekly'); $I->sendGet('/views/working-hours/weekly');
$I->seeResponseCodeIs(HttpCode::OK); $I->seeResponseCodeIs(HttpCode::OK);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT);
} }
@@ -8,13 +8,27 @@ use Helper\Api;
class BrowseWorkingHoursYearlyCest class BrowseWorkingHoursYearlyCest
{ {
public static function preflight(ApiTester $I): void
{
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->haveHttpHeader('Access-Control-Request-Method', 'GET');
$I->sendOptions('/views/working-hours/yearly');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeHttpHeader('Access-Control-Allow-Methods');
$I->seeHttpHeader('Access-Control-Allow-Headers');
$I->seeHttpHeader('Access-Control-Max-Age');
}
/** /**
* @param ApiTester $I * @param ApiTester $I
*/ */
public function browseWorkingHoursView(ApiTester $I): void public function browseWorkingHoursView(ApiTester $I): void
{ {
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendGet('/views/working-hours/yearly'); $I->sendGet('/views/working-hours/yearly');
$I->seeResponseCodeIs(HttpCode::OK); $I->seeResponseCodeIs(HttpCode::OK);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT);
} }
@@ -8,13 +8,27 @@ use Helper\Api;
class BrowseWorkingHoursCest class BrowseWorkingHoursCest
{ {
public static function preflight(ApiTester $I): void
{
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->haveHttpHeader('Access-Control-Request-Method', 'GET');
$I->sendOptions('/working-hours');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeHttpHeader('Access-Control-Allow-Methods');
$I->seeHttpHeader('Access-Control-Allow-Headers');
$I->seeHttpHeader('Access-Control-Max-Age');
}
/** /**
* @param ApiTester $I * @param ApiTester $I
*/ */
public function browseWorkingHours(ApiTester $I): void public function browseWorkingHours(ApiTester $I): void
{ {
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendGet('/working-hours'); $I->sendGet('/working-hours');
$I->seeResponseCodeIs(HttpCode::OK); $I->seeResponseCodeIs(HttpCode::OK);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
} }
@@ -8,15 +8,29 @@ use Helper\Api;
class CreateWorkingHoursCest class CreateWorkingHoursCest
{ {
public static function preflight(ApiTester $I): void
{
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->haveHttpHeader('Access-Control-Request-Method', 'POST');
$I->sendOptions('/working-hours');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeHttpHeader('Access-Control-Allow-Methods');
$I->seeHttpHeader('Access-Control-Allow-Headers');
$I->seeHttpHeader('Access-Control-Max-Age');
}
public function createWorkingHoursWithNewValidRecord(ApiTester $I): void public function createWorkingHoursWithNewValidRecord(ApiTester $I): void
{ {
$I->haveHttpHeader('accept', 'application/json'); $I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json'); $I->haveHttpHeader('content-type', 'application/json');
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendPost('/working-hours', [ $I->sendPost('/working-hours', [
'workingDay' => '2020-04-01', 'workingDay' => '2020-04-01',
'workingTime' => '08:00:00', 'workingTime' => '08:00:00',
]); ]);
$I->seeResponseCodeIs(HttpCode::CREATED); $I->seeResponseCodeIs(HttpCode::CREATED);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
} }
@@ -26,11 +40,13 @@ class CreateWorkingHoursCest
{ {
$I->haveHttpHeader('accept', 'application/json'); $I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json'); $I->haveHttpHeader('content-type', 'application/json');
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendPost('/working-hours', [ $I->sendPost('/working-hours', [
'workingDay' => '2020-04-01', 'workingDay' => '2020-04-01',
'workingTime' => 8.0, 'workingTime' => 8.0,
]); ]);
$I->seeResponseCodeIs(HttpCode::BAD_REQUEST); $I->seeResponseCodeIs(HttpCode::BAD_REQUEST);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
} }
@@ -40,11 +56,13 @@ class CreateWorkingHoursCest
{ {
$I->haveHttpHeader('accept', 'application/json'); $I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json'); $I->haveHttpHeader('content-type', 'application/json');
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendPost('/working-hours', [ $I->sendPost('/working-hours', [
'workingDay' => '2020-01-15', 'workingDay' => '2020-01-15',
'workingTime' => '08:00:00', 'workingTime' => '08:00:00',
]); ]);
$I->seeResponseCodeIs(HttpCode::CONFLICT); $I->seeResponseCodeIs(HttpCode::CONFLICT);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
} }
@@ -8,18 +8,34 @@ use Helper\Api;
class ReadWorkingHoursCest class ReadWorkingHoursCest
{ {
public static function preflight(ApiTester $I): void
{
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->haveHttpHeader('Access-Control-Request-Method', 'GET');
$I->sendOptions('/working-hours/2020-04-15');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeHttpHeader('Access-Control-Allow-Methods');
$I->seeHttpHeader('Access-Control-Allow-Headers');
$I->seeHttpHeader('Access-Control-Max-Age');
}
public function readExistingWorkingHours(ApiTester $I): void public function readExistingWorkingHours(ApiTester $I): void
{ {
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendGet('/working-hours/2020-01-15'); $I->sendGet('/working-hours/2020-01-15');
$I->seeResponseCodeIs(HttpCode::OK); $I->seeResponseCodeIs(HttpCode::OK);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
} }
public function readNotExistingWorkingHours(ApiTester $I): void public function readNotExistingWorkingHours(ApiTester $I): void
{ {
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendGet('/working-hours/2020-04-15'); $I->sendGet('/working-hours/2020-04-15');
$I->seeResponseCodeIs(HttpCode::NOT_FOUND); $I->seeResponseCodeIs(HttpCode::NOT_FOUND);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
} }
@@ -8,15 +8,29 @@ use Helper\Api;
class UpdateWorkingHoursCest class UpdateWorkingHoursCest
{ {
public static function preflight(ApiTester $I): void
{
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->haveHttpHeader('Access-Control-Request-Method', 'PUT');
$I->sendOptions('/working-hours/2020-01-15');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeHttpHeader('Access-Control-Allow-Methods');
$I->seeHttpHeader('Access-Control-Allow-Headers');
$I->seeHttpHeader('Access-Control-Max-Age');
}
public function updateWorkingHoursWithExistingValidRecord(ApiTester $I): void public function updateWorkingHoursWithExistingValidRecord(ApiTester $I): void
{ {
$I->haveHttpHeader('accept', 'application/json'); $I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json'); $I->haveHttpHeader('content-type', 'application/json');
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendPut('/working-hours/2020-01-15', [ $I->sendPut('/working-hours/2020-01-15', [
'workingDay' => '2020-01-15', 'workingDay' => '2020-01-15',
'workingTime' => '10:00:00', 'workingTime' => '10:00:00',
]); ]);
$I->seeResponseCodeIs(HttpCode::OK); $I->seeResponseCodeIs(HttpCode::OK);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
} }
@@ -25,11 +39,13 @@ class UpdateWorkingHoursCest
{ {
$I->haveHttpHeader('accept', 'application/json'); $I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json'); $I->haveHttpHeader('content-type', 'application/json');
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendPut('/working-hours/2020-04-15', [ $I->sendPut('/working-hours/2020-04-15', [
'workingDay' => '2020-04-15', 'workingDay' => '2020-04-15',
'workingTime' => '10:00:00', 'workingTime' => '10:00:00',
]); ]);
$I->seeResponseCodeIs(HttpCode::NOT_FOUND); $I->seeResponseCodeIs(HttpCode::NOT_FOUND);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
} }
@@ -38,11 +54,13 @@ class UpdateWorkingHoursCest
{ {
$I->haveHttpHeader('accept', 'application/json'); $I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json'); $I->haveHttpHeader('content-type', 'application/json');
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendPut('/working-hours/2020-01-15', [ $I->sendPut('/working-hours/2020-01-15', [
'workingDay' => '2020-01-15', 'workingDay' => '2020-01-15',
'workingTime' => 8.0, 'workingTime' => 8.0,
]); ]);
$I->seeResponseCodeIs(HttpCode::BAD_REQUEST); $I->seeResponseCodeIs(HttpCode::BAD_REQUEST);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson(); $I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT); $I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
} }