5 Commits
13 changed files with 149 additions and 3 deletions
+1 -1
View File
@@ -5,4 +5,4 @@ DATABASES_USER=user
DATABASES_PASS=1234 DATABASES_PASS=1234
## CORS Einstellungen ## CORS Einstellungen
CORS_ALLOW_ORIGIN=localhost:* 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,60 @@
<?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)) {
$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);
// Optional: Allow Ajax CORS requests with Authorization header
$response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
}
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') ?? '';
}
}
+1 -1
View File
@@ -33,5 +33,5 @@ class Api extends Module
"path" => "string", "path" => "string",
]; ];
const TEST_ORIGIN = 'localhost:1234'; const TEST_ORIGIN = 'http://localhost:1234';
} }
@@ -8,6 +8,14 @@ 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);
}
/** /**
* @param ApiTester $I * @param ApiTester $I
*/ */
@@ -8,6 +8,14 @@ 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);
}
/** /**
* @param ApiTester $I * @param ApiTester $I
*/ */
@@ -8,6 +8,14 @@ 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);
}
/** /**
* @param ApiTester $I * @param ApiTester $I
*/ */
@@ -8,6 +8,14 @@ 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);
}
/** /**
* @param ApiTester $I * @param ApiTester $I
*/ */
@@ -8,6 +8,14 @@ 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);
}
public function createWorkingHoursWithNewValidRecord(ApiTester $I): void public function createWorkingHoursWithNewValidRecord(ApiTester $I): void
{ {
$I->haveHttpHeader('accept', 'application/json'); $I->haveHttpHeader('accept', 'application/json');
@@ -8,6 +8,14 @@ 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);
}
public function readExistingWorkingHours(ApiTester $I): void public function readExistingWorkingHours(ApiTester $I): void
{ {
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN); $I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
@@ -8,6 +8,14 @@ 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);
}
public function updateWorkingHoursWithExistingValidRecord(ApiTester $I): void public function updateWorkingHoursWithExistingValidRecord(ApiTester $I): void
{ {
$I->haveHttpHeader('accept', 'application/json'); $I->haveHttpHeader('accept', 'application/json');