From 13a6e5386940cba95ed7dfba79059f8d62f6b90e Mon Sep 17 00:00:00 2001 From: Danil Lytvyn Date: Thu, 9 Mar 2023 15:45:39 +0200 Subject: [PATCH] Fix CORS Preflight Handling for Laravel 9 - Switch to native Laravel CORS middleware - Dynamically define CORS paths - Extend Fruitcake\CorsService to handle HTTP Methods --- composer.json | 1 - routes/routes.php | 2 +- src/LaravelServiceProvider.php | 16 +++---------- src/Providers/CorsServiceProvider.php | 21 +++++++---------- src/Services/DfCorsService.php | 34 +++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 src/Services/DfCorsService.php diff --git a/composer.json b/composer.json index 67b27c47..aab977a5 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,6 @@ "prefer-stable": true, "require": { "php": "^8.0", - "fruitcake/laravel-cors": "~3.0.0", "doctrine/dbal": "^3.1.4", "guzzlehttp/guzzle": "~7.4.5", "symfony/yaml": "^6.0", diff --git a/routes/routes.php b/routes/routes.php index 84bf89c2..1a212d60 100644 --- a/routes/routes.php +++ b/routes/routes.php @@ -17,7 +17,7 @@ $resourcePathPattern = '[0-9a-zA-ZÀ-ÿ-_@&\#\!=,:;\/\^\$\.\|\{\}\[\]\(\)\*\+\?\' ]+'; $controller = 'DreamFactory\Core\Http\Controllers\RestController'; // Don't use any() below, or include OPTIONS here, breaks CORS - $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']; + $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']; Route::get('', $controller . '@index'); // Support old versioning in URL, i.e api/v2 and api/v2/service diff --git a/src/LaravelServiceProvider.php b/src/LaravelServiceProvider.php index fa72794b..2b509fec 100644 --- a/src/LaravelServiceProvider.php +++ b/src/LaravelServiceProvider.php @@ -103,19 +103,9 @@ protected function addAliases() */ protected function addMiddleware() { - // the method name was changed in Laravel 5.4 - if (method_exists(\Illuminate\Routing\Router::class, 'aliasMiddleware')) { - Route::aliasMiddleware('df.auth_check', AuthCheck::class); - Route::aliasMiddleware('df.access_check', AccessCheck::class); - Route::aliasMiddleware('df.verb_override', VerbOverrides::class); - } else { - /** @noinspection PhpUndefinedMethodInspection */ - Route::middleware('df.auth_check', AuthCheck::class); - /** @noinspection PhpUndefinedMethodInspection */ - Route::middleware('df.access_check', AccessCheck::class); - /** @noinspection PhpUndefinedMethodInspection */ - Route::middleware('df.verb_override', VerbOverrides::class); - } + Route::aliasMiddleware('df.auth_check', AuthCheck::class); + Route::aliasMiddleware('df.access_check', AccessCheck::class); + Route::aliasMiddleware('df.verb_override', VerbOverrides::class); /** Add the first user check to the web group */ Route::prependMiddlewareToGroup('web', FirstUserCheck::class); diff --git a/src/Providers/CorsServiceProvider.php b/src/Providers/CorsServiceProvider.php index c9948dd9..8725df12 100644 --- a/src/Providers/CorsServiceProvider.php +++ b/src/Providers/CorsServiceProvider.php @@ -2,9 +2,10 @@ namespace DreamFactory\Core\Providers; -use Fruitcake\Cors\HandleCors; -use Fruitcake\Cors\CorsService; +use DreamFactory\Core\Services\DfCorsService; use DreamFactory\Core\Models\CorsConfig; +use Fruitcake\Cors\CorsService; +use Illuminate\Http\Middleware\HandleCors; use Illuminate\Database\QueryException; use Illuminate\Contracts\Http\Kernel; use Illuminate\Http\Request; @@ -35,21 +36,15 @@ public function register() */ public function boot(Request $request, Kernel $kernel) { + $api_prefix = config('df.api_route_prefix', 'api'); + config(['cors.paths' => [$api_prefix . '/*']]); + $config = $this->getOptions($request); $this->app->singleton(CorsService::class, function () use ($config){ - return new CorsService($config); + return new DfCorsService($config); }); - /** @noinspection PhpUndefinedMethodInspection */ - //$this->app['router']->middleware('cors', HandleCors::class); - - if (method_exists(\Illuminate\Routing\Router::class, 'aliasMiddleware')) { - Route::aliasMiddleware('df.cors', HandleCors::class); - } else { - /** @noinspection PhpUndefinedMethodInspection */ - Route::middleware('df.cors', HandleCors::class); - } - + Route::aliasMiddleware('df.cors', HandleCors::class); Route::prependMiddlewareToGroup('df.api', 'df.cors'); } diff --git a/src/Services/DfCorsService.php b/src/Services/DfCorsService.php new file mode 100644 index 00000000..efa6c766 --- /dev/null +++ b/src/Services/DfCorsService.php @@ -0,0 +1,34 @@ +allowedMethodsCopy = $options['allowedMethods'] ?? $options['allowed_methods'] ?? $this->allowedMethodsCopy; + parent::setOptions($options); + } + + public function handlePreflightRequest(Request $request): Response + { + $response = new Response(); + + $requestMethod = strtoupper($request->headers->get('Access-Control-Request-Method')); + if(!in_array($requestMethod, $this->allowedMethodsCopy)) { + $response->setStatusCode(405, 'Method not allowed'); + } else { + $response->setStatusCode(204); + } + + return $this->addPreflightRequestHeaders($response, $request); + } +}