diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index a0bd186609..8ad986736c 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -16,6 +16,7 @@ class Kernel extends HttpKernel */ protected $middleware = [ \App\Http\Middleware\TrustProxies::class, + \App\Http\Middleware\HandleCors::class, \App\Http\Middleware\CheckForMaintenanceMode::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, @@ -54,7 +55,6 @@ class Kernel extends HttpKernel 'api' => [ \Illuminate\Routing\Middleware\SubstituteBindings::class, 'authenticate:token', - \Spatie\Cors\Cors::class, ], ]; diff --git a/app/Http/Middleware/HandleCors.php b/app/Http/Middleware/HandleCors.php new file mode 100644 index 0000000000..b7b096acb3 --- /dev/null +++ b/app/Http/Middleware/HandleCors.php @@ -0,0 +1,61 @@ +. + * + * @package LibreNMS + * @link http://librenms.org + * @copyright 2020 Tony Murray + * @author Tony Murray + */ + +namespace App\Http\Middleware; + +use Asm89\Stack\CorsService; +use Illuminate\Contracts\Container\Container; + +class HandleCors extends \Fruitcake\Cors\HandleCors +{ + private $map = [ + 'allowmethods' => 'allowed_methods', + 'origin' => 'allowed_origins', + 'allowheaders' => 'allowed_headers', + 'exposeheaders' => 'exposed_headers', + 'maxage' => 'max_age', + 'allowcredentials' => 'supports_credentials', + ]; + + public function __construct(Container $container) + { + // load legacy config settings before booting the CorsService + if (\LibreNMS\Config::get('api.cors.enabled')) { + $laravel_config = $container['config']->get('cors'); + $legacy = \LibreNMS\Config::get('api.cors'); + + $laravel_config['paths'][] = 'api/*'; + + foreach ($this->map as $config_key => $option_key) { + $laravel_config[$option_key] = $legacy[$config_key] ?? $laravel_config[$option_key]; + } + + $container['config']->set('cors', $laravel_config); + } + + $cors = $container->make(CorsService::class); + parent::__construct($cors, $container); + } +} diff --git a/app/Http/Profile/CorsApiProfile.php b/app/Http/Profile/CorsApiProfile.php deleted file mode 100644 index 655abe77c7..0000000000 --- a/app/Http/Profile/CorsApiProfile.php +++ /dev/null @@ -1,77 +0,0 @@ -. - * - * @package LibreNMS - * @link http://librenms.org - * @copyright 2019 Tony Murray - * @author Tony Murray - */ - -namespace App\Http\Profile; - -use Illuminate\Support\Arr; -use LibreNMS\Config; -use Spatie\Cors\CorsProfile\DefaultProfile; - -class CorsApiProfile extends DefaultProfile -{ - public function addCorsHeaders($response) - { - return Config::get('api.cors.enabled') ? - parent::addCorsHeaders($response) : - $response; - } - - public function addPreflightHeaders($response) - { - return Config::get('api.cors.enabled') ? - parent::addPreflightHeaders($response) : - $response; - } - - public function allowHeaders(): array - { - return Arr::wrap(Config::get('api.cors.allowheaders', [])); - } - - public function allowMethods(): array - { - return Arr::wrap(Config::get('api.cors.allowmethods', [])); - } - - public function maxAge(): int - { - return (int)Config::get('api.cors.maxage', 86400); - } - - public function allowOrigins(): array - { - return Arr::wrap(Config::get('api.cors.origin', [])); - } - - public function exposeHeaders(): array - { - return Arr::wrap(Config::get('api.cors.exposeheaders', [])); - } - - public function allowCredentials(): bool - { - return (bool)Config::get('api.cors.allowcredentials'); - } -} diff --git a/composer.json b/composer.json index c4f3259819..3cb583ac33 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,7 @@ "ezyang/htmlpurifier": "^4.8", "fico7489/laravel-pivot": "^3.0", "fideloper/proxy": "^4.0", + "fruitcake/laravel-cors": "^2.0", "influxdb/influxdb-php": "^1.14", "laravel/framework": "^6.18", "laravel/tinker": "^2.0", @@ -49,7 +50,6 @@ "php-amqplib/php-amqplib": "^2.0", "phpmailer/phpmailer": "~6.0", "rmccue/requests": "^1.7", - "spatie/laravel-cors": "^1.6", "symfony/yaml": "^4.0", "tecnickcom/tcpdf": "~6.2.0", "tightenco/ziggy": "^0.8.0", diff --git a/composer.lock b/composer.lock index ecaff2b046..9704e373df 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e72ee8f57b19bfafb68f1ea62ecad1b2", + "content-hash": "af8c6b05826511d29e36dd2d38338192", "packages": [ { "name": "amenadiel/jpgraph", @@ -61,6 +61,58 @@ ], "time": "2018-10-14T21:43:30+00:00" }, + { + "name": "asm89/stack-cors", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/asm89/stack-cors.git", + "reference": "23f469e81c65e2fb7fc7bce371fbdc363fe32adf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/asm89/stack-cors/zipball/23f469e81c65e2fb7fc7bce371fbdc363fe32adf", + "reference": "23f469e81c65e2fb7fc7bce371fbdc363fe32adf", + "shasum": "" + }, + "require": { + "php": "^7.0", + "symfony/http-foundation": "~2.7|~3.0|~4.0|~5.0", + "symfony/http-kernel": "~2.7|~3.0|~4.0|~5.0" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Asm89\\Stack\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexander", + "email": "iam.asm89@gmail.com" + } + ], + "description": "Cross-origin resource sharing library and stack middleware", + "homepage": "https://github.com/asm89/stack-cors", + "keywords": [ + "cors", + "stack" + ], + "time": "2020-05-31T07:17:05+00:00" + }, { "name": "clue/socket-raw", "version": "v1.4.1", @@ -1031,6 +1083,82 @@ ], "time": "2020-06-23T01:36:47+00:00" }, + { + "name": "fruitcake/laravel-cors", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/laravel-cors.git", + "reference": "dbfc311b25d4873c3c2382b26860be3567492bd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/dbfc311b25d4873c3c2382b26860be3567492bd6", + "reference": "dbfc311b25d4873c3c2382b26860be3567492bd6", + "shasum": "" + }, + "require": { + "asm89/stack-cors": "^2.0.1", + "illuminate/contracts": "^5.6|^6.0|^7.0|^8.0", + "illuminate/support": "^5.6|^6.0|^7.0|^8.0", + "php": ">=7.1", + "symfony/http-foundation": "^4.0|^5.0", + "symfony/http-kernel": "^4.0|^5.0" + }, + "require-dev": { + "laravel/framework": "^5.5|^6.0|^7.0|^8.0", + "orchestra/dusk-updater": "^1.2", + "orchestra/testbench": "^3.5|^4.0|^5.0|^6.0", + "orchestra/testbench-dusk": "^5.1", + "phpro/grumphp": "^0.16|^0.17", + "phpunit/phpunit": "^6.0|^7.0|^8.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + }, + "laravel": { + "providers": [ + "Fruitcake\\Cors\\CorsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Laravel application", + "keywords": [ + "api", + "cors", + "crossdomain", + "laravel" + ], + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2020-05-31T07:30:16+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "6.5.5", @@ -2986,66 +3114,6 @@ ], "time": "2016-10-13T00:11:37+00:00" }, - { - "name": "spatie/laravel-cors", - "version": "1.6.0", - "source": { - "type": "git", - "url": "https://github.com/spatie/laravel-cors.git", - "reference": "d74099d57821d5a72ae21416c0be0dcd58779355" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-cors/zipball/d74099d57821d5a72ae21416c0be0dcd58779355", - "reference": "d74099d57821d5a72ae21416c0be0dcd58779355", - "shasum": "" - }, - "require": { - "illuminate/support": "5.5.*|5.6.*|5.7.*|5.8.*|^6.0", - "php": "^7.2" - }, - "require-dev": { - "orchestra/testbench": "3.5.*|3.6.*|3.7.*|3.8.*|^4.0", - "phpunit/phpunit": "^8.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Spatie\\Cors\\CorsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Spatie\\Cors\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://spatie.be", - "role": "Developer" - } - ], - "description": "Send CORS headers in a Laravel or Lumen application", - "homepage": "https://github.com/spatie/laravel-cors", - "keywords": [ - "ajax", - "api", - "cors", - "laravel-cors", - "request", - "spatie" - ], - "abandoned": "laravel/framework", - "time": "2019-09-04T06:55:15+00:00" - }, { "name": "swiftmailer/swiftmailer", "version": "v6.2.3", diff --git a/config/cors.php b/config/cors.php index c248f44825..d700af69ba 100644 --- a/config/cors.php +++ b/config/cors.php @@ -3,59 +3,58 @@ return [ /* - * A cors profile determines which origins, methods, headers are allowed for - * a given requests. The `DefaultProfile` reads its configuration from this - * config file. - * - * You can easily create your own cors profile. - * More info: https://github.com/spatie/laravel-cors/#creating-your-own-cors-profile - */ - 'cors_profile' => App\Http\Profile\CorsApiProfile::class, + |-------------------------------------------------------------------------- + | Laravel CORS Options + |-------------------------------------------------------------------------- + | + | The allowed_methods and allowed_headers options are case-insensitive. + | + | You don't need to provide both allowed_origins and allowed_origins_patterns. + | If one of the strings passed matches, it is considered a valid origin. + | + | If array('*') is provided to allowed_methods, allowed_origins or allowed_headers + | all methods / origins / headers are allowed. + | + */ /* - * This configuration is used by `DefaultProfile`. + * You can enable CORS for 1 or multiple paths. + * Example: ['api/*'] */ - 'default_profile' => [ + 'paths' => [], - 'allow_credentials' => false, + /* + * Matches the request method. `[*]` allows all methods. + */ + 'allowed_methods' => ['*'], - 'allow_origins' => [ - '*', - ], + /* + * Matches the request origin. `[*]` allows all origins. Wildcards can be used, eg `*.mydomain.com` + */ + 'allowed_origins' => [], - 'allow_methods' => [ - 'POST', - 'GET', - 'OPTIONS', - 'PUT', - 'PATCH', - 'DELETE', - ], + /* + * Patterns that can be used with `preg_match` to match the origin. + */ + 'allowed_origins_patterns' => [], - 'allow_headers' => [ - 'Content-Type', - 'X-Auth-Token', - 'Origin', - 'Authorization', - ], + /* + * Sets the Access-Control-Allow-Headers response header. `[*]` allows all headers. + */ + 'allowed_headers' => ['*'], - 'expose_headers' => [ - 'Cache-Control', - 'Content-Language', - 'Content-Type', - 'Expires', - 'Last-Modified', - 'Pragma', - ], + /* + * Sets the Access-Control-Expose-Headers response header with these headers. + */ + 'exposed_headers' => [], - 'forbidden_response' => [ - 'message' => 'Forbidden (cors).', - 'status' => 403, - ], + /* + * Sets the Access-Control-Max-Age response header when > 0. + */ + 'max_age' => 0, - /* - * Preflight request will respond with value for the max age header. - */ - 'max_age' => 60 * 60 * 24, - ], + /* + * Sets the Access-Control-Allow-Credentials header. + */ + 'supports_credentials' => false, ]; diff --git a/html/mix-manifest.json b/html/mix-manifest.json index 13f767e3cc..60110b4e6e 100644 --- a/html/mix-manifest.json +++ b/html/mix-manifest.json @@ -3,11 +3,12 @@ "/css/app.css": "/css/app.css?id=ffec4165a9c98d892a32", "/js/manifest.js": "/js/manifest.js?id=3c768977c2574a34506e", "/js/vendor.js": "/js/vendor.js?id=c0e0ebbfd027a8baefb4", - "/js/lang/de.js": "/js/lang/de.js?id=2c4ad02fa89b684d4f57", - "/js/lang/en.js": "/js/lang/en.js?id=8aeb65879e99c385460f", - "/js/lang/fr.js": "/js/lang/fr.js?id=3b61631feb2cb579f713", - "/js/lang/it.js": "/js/lang/it.js?id=514765c5399ffaa111b9", - "/js/lang/ru.js": "/js/lang/ru.js?id=f6b7c078755312a0907c", - "/js/lang/uk.js": "/js/lang/uk.js?id=c19a5dcee4724579cb41", - "/js/lang/zh-TW.js": "/js/lang/zh-TW.js?id=6cec27c0472c6d721d30" + "/js/lang/de.js": "/js/lang/de.js?id=73ed23dde31af205f171", + "/js/lang/en.js": "/js/lang/en.js?id=e2297f39a1eabc180200", + "/js/lang/fr.js": "/js/lang/fr.js?id=91daac2f7383c820457b", + "/js/lang/it.js": "/js/lang/it.js?id=c202a58a7f5bca08801b", + "/js/lang/ru.js": "/js/lang/ru.js?id=aaab82593592e9368a08", + "/js/lang/uk.js": "/js/lang/uk.js?id=9b0b074259847e7aaff3", + "/js/lang/zh-CN.js": "/js/lang/zh-CN.js?id=f6d951b7d6b1f25810fc", + "/js/lang/zh-TW.js": "/js/lang/zh-TW.js?id=21cdd68dc06a428e7260" } diff --git a/misc/config_definitions.json b/misc/config_definitions.json index 6d2119b24a..041e81aed7 100644 --- a/misc/config_definitions.json +++ b/misc/config_definitions.json @@ -273,8 +273,18 @@ "Accept", "X-Auth-Token" ], + "group": "api", + "section": "cors", + "order": 4, "type": "array" }, + "api.cors.allowcredentials": { + "default": false, + "group": "api", + "section": "cors", + "order": 7, + "type": "boolean" + }, "api.cors.allowmethods": { "default": [ "POST", @@ -283,14 +293,37 @@ "DELETE", "PATCH" ], + "group": "api", + "section": "cors", + "order": 2, "type": "array" }, "api.cors.enabled": { "default": false, + "group": "api", + "section": "cors", + "order": 1, "type": "boolean" }, + "api.cors.exposeheaders": { + "default": [ + "Cache-Control", + "Content-Language", + "Content-Type", + "Expires", + "Last-Modified", + "Pragma" + ], + "group": "api", + "section": "cors", + "order": 5, + "type": "array" + }, "api.cors.maxage": { "default": 86400, + "group": "api", + "section": "cors", + "order": 6, "type": "integer", "units": "seconds", "validate": { @@ -298,8 +331,11 @@ } }, "api.cors.origin": { - "default": "*", - "type": "text" + "default": ["*"], + "group": "api", + "section": "cors", + "order": 3, + "type": "array" }, "api_demo": { "default": false, diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index 380664e30b..bcf73c4608 100644 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -5,6 +5,7 @@ return [ 'readonly' => 'Set in config.php, remove from config.php to enable.', 'groups' => [ 'alerting' => 'Alerting', + 'api' => 'API', 'auth' => 'Authentication', 'authorization' => 'Authorization', 'external' => 'External', @@ -22,6 +23,9 @@ return [ 'email' => 'Email Options', 'rules' => 'Alert Rule Default Settings', ], + 'api' => [ + 'cors' => 'CORS', + ], 'auth' => [ 'general' => 'General Authentication Settings', 'ad' => 'Active Directory Settings', @@ -193,6 +197,38 @@ return [ 'description' => 'Allow the given networks graph access', 'help' => 'Allow the given networks unauthenticated graph access (does not apply when unauthenticated graphs is enabled)' ], + 'api' => [ + 'cors' => [ + 'allowheaders' => [ + 'description' => 'Allow Headers', + 'help' => 'Sets the Access-Control-Allow-Headers response header', + ], + 'allowcredentials' => [ + 'description' => 'Allow Credentials', + 'help' => 'Sets the Access-Control-Allow-Credentials header', + ], + 'allowmethods' => [ + 'description' => 'Allowed Methods', + 'help' => 'Matches the request method.', + ], + 'enabled' => [ + 'description' => 'Enable CORS support for the API', + 'help' => 'Allows you to load api resources from a web client', + ], + 'exposeheaders' => [ + 'description' => 'Expose Headers', + 'help' => 'Sets the Access-Control-Expose-Headers response header', + ], + 'maxage' => [ + 'description' => 'Max Age', + 'help' => 'Sets the Access-Control-Max-Age response header', + ], + 'origin' => [ + 'description' => 'Allow Request Origins', + 'help' => 'Matches the request origin. Wildcards can be used, eg. *.mydomain.com', + ], + ], + ], 'api_demo' => [ 'description' => 'This is the demo' ],