cors
This commit is contained in:
parent
679bbb1d51
commit
16aed5f193
|
|
@ -0,0 +1 @@
|
||||||
|
/vendor/
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# How to contribute
|
||||||
|
|
||||||
|
Contributions to this project are highly welcome.
|
||||||
|
|
||||||
|
1. Submit your pull requests to the `develop` branch
|
||||||
|
1. Adhere to the [PSR-2 coding](http://www.php-fig.org/psr/psr-2/) standard
|
||||||
|
1. If you are not sure if your ideas are fit for this project, create an issue and ask
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 OFFLINE GmbH
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php namespace OFFLINE\CORS;
|
||||||
|
|
||||||
|
use OFFLINE\CORS\Classes\HandleCors;
|
||||||
|
use OFFLINE\CORS\Classes\HandlePreflight;
|
||||||
|
use OFFLINE\CORS\Classes\ServiceProvider;
|
||||||
|
use OFFLINE\CORS\Models\Settings;
|
||||||
|
use System\Classes\PluginBase;
|
||||||
|
|
||||||
|
class Plugin extends PluginBase
|
||||||
|
{
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
\App::register(ServiceProvider::class);
|
||||||
|
|
||||||
|
$this->app['Illuminate\Contracts\Http\Kernel']
|
||||||
|
->prependMiddleware(HandleCors::class);
|
||||||
|
|
||||||
|
if (request()->isMethod('OPTIONS')) {
|
||||||
|
$this->app['Illuminate\Contracts\Http\Kernel']
|
||||||
|
->prependMiddleware(HandlePreflight::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerPermissions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'offline.cors.manage' => [
|
||||||
|
'label' => 'Can manage cors settings',
|
||||||
|
'tab' => 'CORS',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerSettings()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'cors' => [
|
||||||
|
'label' => 'CORS-Settings',
|
||||||
|
'description' => 'Manage CORS headers',
|
||||||
|
'category' => 'system::lang.system.categories.cms',
|
||||||
|
'icon' => 'icon-code',
|
||||||
|
'class' => Settings::class,
|
||||||
|
'order' => 500,
|
||||||
|
'keywords' => 'cors',
|
||||||
|
'permissions' => ['offline.cors.manage'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
# CORS plugin for October CMS
|
||||||
|
|
||||||
|
This plugin is based on [https://github.com/barryvdh/laravel-cors](https://github.com/barryvdh/laravel-cors/blob/master/config/cors.php).
|
||||||
|
|
||||||
|
All configuration for the plugin can be done via the backend settings.
|
||||||
|
|
||||||
|
The following cors headers are supported:
|
||||||
|
|
||||||
|
* Access-Control-Allow-Origin
|
||||||
|
* Access-Control-Allow-Headers
|
||||||
|
* Access-Control-Allow-Methods
|
||||||
|
* Access-Control-Allow-Credentials
|
||||||
|
* Access-Control-Expose-Headers
|
||||||
|
* Access-Control-Max-Age
|
||||||
|
|
||||||
|
Currently these headers are sent for every request. There is no per-route configuration possible at this time.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
After installing the plugin visit the CORS settings page in your October CMS backend settings.
|
||||||
|
|
||||||
|
You can add `*` as an entry to `Allowed origins`, `Allowed headers` and `Allowed methods` to allow any kind of CORS request from everywhere.
|
||||||
|
|
||||||
|
It is advised to be more explicit about these settings. You can add values for each header via the repeater fields.
|
||||||
|
|
||||||
|
> It is important to set these intial settings once for the plugin to work as excpected!
|
||||||
|
|
||||||
|
### Filesystem configuration
|
||||||
|
|
||||||
|
As an alternative to the backend settings you can create a `config/config.php` file in the plugins root directory to configure it.
|
||||||
|
|
||||||
|
The filesystem configuration will overwrite any defined backend setting.
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
// plugins/offline/cors/config/config.php
|
||||||
|
return [
|
||||||
|
'supportsCredentials' => true,
|
||||||
|
'maxAge' => 3600,
|
||||||
|
'allowedOrigins' => ['*'],
|
||||||
|
'allowedHeaders' => ['*'],
|
||||||
|
'allowedMethods' => ['GET', 'POST'],
|
||||||
|
'exposedHeaders' => [''],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OFFLINE\CORS\Classes;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on asm89/stack-cors
|
||||||
|
*/
|
||||||
|
class Cors implements HttpKernelInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
|
||||||
|
*/
|
||||||
|
private $app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var CorsService
|
||||||
|
*/
|
||||||
|
private $cors;
|
||||||
|
|
||||||
|
private $defaultOptions = [
|
||||||
|
'allowedHeaders' => [],
|
||||||
|
'allowedMethods' => [],
|
||||||
|
'allowedOrigins' => [],
|
||||||
|
'exposedHeaders' => false,
|
||||||
|
'maxAge' => false,
|
||||||
|
'supportsCredentials' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cors constructor.
|
||||||
|
*
|
||||||
|
* @param HttpKernelInterface $app
|
||||||
|
* @param array $options
|
||||||
|
*/
|
||||||
|
public function __construct(HttpKernelInterface $app, array $options = [])
|
||||||
|
{
|
||||||
|
$this->app = $app;
|
||||||
|
$this->cors = new CorsService(array_merge($this->defaultOptions, $options));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
* @param int $type
|
||||||
|
* @param bool $catch
|
||||||
|
*
|
||||||
|
* @return bool|Response
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
|
||||||
|
{
|
||||||
|
if ( ! $this->cors->isCorsRequest($request)) {
|
||||||
|
return $this->app->handle($request, $type, $catch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->cors->isPreflightRequest($request)) {
|
||||||
|
return $this->cors->handlePreflightRequest($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $this->cors->isActualRequestAllowed($request)) {
|
||||||
|
return new Response('Not allowed.', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->app->handle($request, $type, $catch);
|
||||||
|
|
||||||
|
return $this->cors->addActualRequestHeaders($response, $request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OFFLINE\CORS\Classes;
|
||||||
|
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on asm89/stack-cors
|
||||||
|
*/
|
||||||
|
class CorsService
|
||||||
|
{
|
||||||
|
private $options;
|
||||||
|
|
||||||
|
public function __construct(array $options = [])
|
||||||
|
{
|
||||||
|
$this->options = $this->normalizeOptions($options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeOptions(array $options = [])
|
||||||
|
{
|
||||||
|
$options += [
|
||||||
|
'supportsCredentials' => false,
|
||||||
|
'maxAge' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Make sure these values are arrays, if not specified in the backend settings.
|
||||||
|
$arrayKeys = [
|
||||||
|
'allowedOrigins',
|
||||||
|
'allowedHeaders',
|
||||||
|
'exposedHeaders',
|
||||||
|
'allowedMethods',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($arrayKeys as $key) {
|
||||||
|
if (!$options[$key]) {
|
||||||
|
$options[$key] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize array('*') to true
|
||||||
|
if (in_array('*', $options['allowedOrigins'])) {
|
||||||
|
$options['allowedOrigins'] = true;
|
||||||
|
}
|
||||||
|
if (in_array('*', $options['allowedHeaders'])) {
|
||||||
|
$options['allowedHeaders'] = true;
|
||||||
|
} else {
|
||||||
|
$options['allowedHeaders'] = array_map('strtolower', $options['allowedHeaders']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('*', $options['allowedMethods'])) {
|
||||||
|
$options['allowedMethods'] = true;
|
||||||
|
} else {
|
||||||
|
$options['allowedMethods'] = array_map('strtoupper', $options['allowedMethods']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isActualRequestAllowed(Request $request)
|
||||||
|
{
|
||||||
|
return $this->checkOrigin($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCorsRequest(Request $request)
|
||||||
|
{
|
||||||
|
return $request->headers->has('Origin') && $request->headers->get('Origin') !== $request->getSchemeAndHttpHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPreflightRequest(Request $request)
|
||||||
|
{
|
||||||
|
return $this->isCorsRequest($request)
|
||||||
|
&& $request->getMethod() === 'OPTIONS'
|
||||||
|
&& $request->headers->has('Access-Control-Request-Method');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addActualRequestHeaders(Response $response, Request $request)
|
||||||
|
{
|
||||||
|
if ( ! $this->checkOrigin($request)) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
|
||||||
|
|
||||||
|
if ( ! $response->headers->has('Vary')) {
|
||||||
|
$response->headers->set('Vary', 'Origin');
|
||||||
|
} else {
|
||||||
|
$response->headers->set('Vary', $response->headers->get('Vary') . ', Origin');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->options['supportsCredentials']) {
|
||||||
|
$response->headers->set('Access-Control-Allow-Credentials', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->options['exposedHeaders']) {
|
||||||
|
$response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->options['exposedHeaders']));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handlePreflightRequest(Request $request)
|
||||||
|
{
|
||||||
|
if (true !== $check = $this->checkPreflightRequestConditions($request)) {
|
||||||
|
return $check;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->buildPreflightCheckResponse($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildPreflightCheckResponse(Request $request)
|
||||||
|
{
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
if ($this->options['supportsCredentials']) {
|
||||||
|
$response->headers->set('Access-Control-Allow-Credentials', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
|
||||||
|
|
||||||
|
if ($this->options['maxAge']) {
|
||||||
|
$response->headers->set('Access-Control-Max-Age', $this->options['maxAge']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowMethods = $this->options['allowedMethods'] === true
|
||||||
|
? strtoupper($request->headers->get('Access-Control-Request-Method'))
|
||||||
|
: implode(', ', $this->options['allowedMethods']);
|
||||||
|
$response->headers->set('Access-Control-Allow-Methods', $allowMethods);
|
||||||
|
|
||||||
|
$allowHeaders = $this->options['allowedHeaders'] === true
|
||||||
|
? strtoupper($request->headers->get('Access-Control-Request-Headers'))
|
||||||
|
: implode(', ', $this->options['allowedHeaders']);
|
||||||
|
$response->headers->set('Access-Control-Allow-Headers', $allowHeaders);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkPreflightRequestConditions(Request $request)
|
||||||
|
{
|
||||||
|
if ( ! $this->checkOrigin($request)) {
|
||||||
|
return $this->createBadRequestResponse(403, 'Origin not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $this->checkMethod($request)) {
|
||||||
|
return $this->createBadRequestResponse(405, 'Method not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$requestHeaders = [];
|
||||||
|
// if allowedHeaders has been set to true ('*' allow all flag) just skip this check
|
||||||
|
if ($this->options['allowedHeaders'] !== true && $request->headers->has('Access-Control-Request-Headers')) {
|
||||||
|
$headers = strtolower($request->headers->get('Access-Control-Request-Headers'));
|
||||||
|
$requestHeaders = explode(',', $headers);
|
||||||
|
|
||||||
|
foreach ($requestHeaders as $header) {
|
||||||
|
if ( ! in_array(trim($header), $this->options['allowedHeaders'])) {
|
||||||
|
return $this->createBadRequestResponse(403, 'Header not allowed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createBadRequestResponse($code, $reason = '')
|
||||||
|
{
|
||||||
|
return new Response($reason, $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkOrigin(Request $request)
|
||||||
|
{
|
||||||
|
if ($this->options['allowedOrigins'] === true) {
|
||||||
|
// allow all '*' flag
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$origin = $request->headers->get('Origin');
|
||||||
|
|
||||||
|
foreach ($this->options['allowedOrigins'] as $allowedOrigin) {
|
||||||
|
if (OriginMatcher::matches($allowedOrigin, $origin)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkMethod(Request $request)
|
||||||
|
{
|
||||||
|
if ($this->options['allowedMethods'] === true) {
|
||||||
|
// allow all '*' flag
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$requestMethod = strtoupper($request->headers->get('Access-Control-Request-Method'));
|
||||||
|
|
||||||
|
return in_array($requestMethod, $this->options['allowedMethods']);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OFFLINE\CORS\Classes;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
|
||||||
|
class HandleCors
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The CORS service
|
||||||
|
*
|
||||||
|
* @var CorsService
|
||||||
|
*/
|
||||||
|
protected $cors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CorsService $cors
|
||||||
|
*/
|
||||||
|
public function __construct(CorsService $cors)
|
||||||
|
{
|
||||||
|
$this->cors = $cors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming request. Based on Asm89\Stack\Cors by asm89
|
||||||
|
* @see https://github.com/asm89/stack-cors/blob/master/src/Asm89/Stack/Cors.php
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle($request, Closure $next)
|
||||||
|
{
|
||||||
|
if ( ! $this->cors->isCorsRequest($request)) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $this->cors->isActualRequestAllowed($request)) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \Illuminate\Http\Response $response */
|
||||||
|
$response = $next($request);
|
||||||
|
|
||||||
|
return $this->cors->addActualRequestHeaders($response, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OFFLINE\CORS\Classes;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Http\Kernel;
|
||||||
|
use Illuminate\Routing\Router;
|
||||||
|
|
||||||
|
class HandlePreflight
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param CorsService $cors
|
||||||
|
*/
|
||||||
|
public function __construct(CorsService $cors, Router $router, Kernel $kernel)
|
||||||
|
{
|
||||||
|
$this->cors = $cors;
|
||||||
|
$this->router = $router;
|
||||||
|
$this->kernel = $kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming OPTIONS request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle($request, Closure $next)
|
||||||
|
{
|
||||||
|
$response = $next($request);
|
||||||
|
if ($this->cors->isPreflightRequest($request) && $this->hasMatchingCorsRoute($request)) {
|
||||||
|
$preflight = $this->cors->handlePreflightRequest($request);
|
||||||
|
$response->headers->add($preflight->headers->all());
|
||||||
|
}
|
||||||
|
$response->setStatusCode(204);
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the current OPTIONS request matches a CORS-enabled route
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
private function hasMatchingCorsRoute($request)
|
||||||
|
{
|
||||||
|
// Check if CORS is added in a global middleware
|
||||||
|
if ($this->kernel->hasMiddleware(HandleCors::class)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if CORS is added as a route middleware
|
||||||
|
$request = clone $request;
|
||||||
|
$request->setMethod($request->header('Access-Control-Request-Method'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$route = $this->router->getRoutes()->match($request);
|
||||||
|
// change of method name in laravel 5.3
|
||||||
|
if (method_exists($this->router, 'gatherRouteMiddleware')) {
|
||||||
|
$middleware = $this->router->gatherRouteMiddleware($route);
|
||||||
|
} else {
|
||||||
|
$middleware = $this->router->gatherRouteMiddlewares($route);
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array(HandleCors::class, $middleware);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
app('log')->error($e);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OFFLINE\CORS\Classes;
|
||||||
|
|
||||||
|
class OriginMatcher
|
||||||
|
{
|
||||||
|
|
||||||
|
public static function matches($pattern, $origin)
|
||||||
|
{
|
||||||
|
if ($pattern === $origin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$scheme = parse_url($origin, PHP_URL_SCHEME);
|
||||||
|
$host = parse_url($origin, PHP_URL_HOST);
|
||||||
|
$port = parse_url($origin, PHP_URL_PORT);
|
||||||
|
|
||||||
|
$schemePattern = static::parseOriginPattern($pattern, PHP_URL_SCHEME);
|
||||||
|
$hostPattern = static::parseOriginPattern($pattern, PHP_URL_HOST);
|
||||||
|
$portPattern = static::parseOriginPattern($pattern, PHP_URL_PORT);
|
||||||
|
|
||||||
|
$schemeMatches = static::schemeMatches($schemePattern, $scheme);
|
||||||
|
$hostMatches = static::hostMatches($hostPattern, $host);
|
||||||
|
$portMatches = static::portMatches($portPattern, $port);
|
||||||
|
|
||||||
|
return $schemeMatches && $hostMatches && $portMatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function schemeMatches($pattern, $scheme)
|
||||||
|
{
|
||||||
|
return is_null($pattern) || $pattern === $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function hostMatches($pattern, $host)
|
||||||
|
{
|
||||||
|
$patternComponents = array_reverse(explode('.', $pattern));
|
||||||
|
$hostComponents = array_reverse(explode('.', $host));
|
||||||
|
foreach ($patternComponents as $index => $patternComponent) {
|
||||||
|
if ($patternComponent === '*') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( ! isset($hostComponents[$index])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($hostComponents[$index] !== $patternComponent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($patternComponents) === count($hostComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function portMatches($pattern, $port)
|
||||||
|
{
|
||||||
|
if ($pattern === "*") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((string)$pattern === "") {
|
||||||
|
return (string)$port === "";
|
||||||
|
}
|
||||||
|
if (preg_match('/\A\d+\z/', $pattern)) {
|
||||||
|
return (string)$pattern === (string)$port;
|
||||||
|
}
|
||||||
|
if (preg_match('/\A(?P<from>\d+)-(?P<to>\d+)\z/', $pattern, $captured)) {
|
||||||
|
return $captured['from'] <= $port && $port <= $captured['to'];
|
||||||
|
}
|
||||||
|
throw new \InvalidArgumentException("Invalid port pattern: ${pattern}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function parseOriginPattern($originPattern, $component = -1)
|
||||||
|
{
|
||||||
|
$matched = preg_match(
|
||||||
|
'!\A
|
||||||
|
(?: (?P<scheme> ([a-z][a-z0-9+\-.]*) ):// )?
|
||||||
|
(?P<host> (?:\*|[\w-]+)(?:\.[\w-]+)* )
|
||||||
|
(?: :(?P<port> (?: \*|\d+(?:-\d+)? ) ) )?
|
||||||
|
\z!x',
|
||||||
|
$originPattern,
|
||||||
|
$captured
|
||||||
|
);
|
||||||
|
if ( ! $matched) {
|
||||||
|
throw new \InvalidArgumentException("Invalid origin pattern ${originPattern}");
|
||||||
|
}
|
||||||
|
$components = [
|
||||||
|
'scheme' => $captured['scheme'] ?: null,
|
||||||
|
'host' => $captured['host'],
|
||||||
|
'port' => array_key_exists('port', $captured) ? $captured['port'] : null,
|
||||||
|
];
|
||||||
|
switch ($component) {
|
||||||
|
case -1:
|
||||||
|
return $components;
|
||||||
|
case PHP_URL_SCHEME:
|
||||||
|
return $components['scheme'];
|
||||||
|
case PHP_URL_HOST:
|
||||||
|
return $components['host'];
|
||||||
|
case PHP_URL_PORT:
|
||||||
|
return $components['port'];
|
||||||
|
}
|
||||||
|
throw new \InvalidArgumentException("Invalid component: ${component}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OFFLINE\CORS\Classes;
|
||||||
|
|
||||||
|
use October\Rain\Support\ServiceProvider as BaseServiceProvider;
|
||||||
|
use OFFLINE\CORS\Models\Settings;
|
||||||
|
|
||||||
|
class ServiceProvider extends BaseServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Indicates if loading of the provider is deferred.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $defer = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the service provider.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
$this->app->singleton(CorsService::class, function ($app) {
|
||||||
|
return new CorsService($this->getSettings());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return default Settings
|
||||||
|
*/
|
||||||
|
protected function getSettings()
|
||||||
|
{
|
||||||
|
$supportsCredentials = (bool)$this->getConfigValue('supportsCredentials', false);
|
||||||
|
$maxAge = (int)$this->getConfigValue('maxAge', 0);
|
||||||
|
$allowedOrigins = $this->getConfigValue('allowedOrigins', []);
|
||||||
|
$allowedHeaders = $this->getConfigValue('allowedHeaders', []);
|
||||||
|
$allowedMethods = $this->getConfigValue('allowedMethods', []);
|
||||||
|
$exposedHeaders = $this->getConfigValue('exposedHeaders', []);
|
||||||
|
|
||||||
|
return compact(
|
||||||
|
'supportsCredentials',
|
||||||
|
'allowedOrigins',
|
||||||
|
'allowedHeaders',
|
||||||
|
'allowedMethods',
|
||||||
|
'exposedHeaders',
|
||||||
|
'maxAge'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an effective config value.
|
||||||
|
*
|
||||||
|
* If a filesystem config is available it takes precedence
|
||||||
|
* over the backend settings values.
|
||||||
|
*
|
||||||
|
* @param $key
|
||||||
|
* @param null $default
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getConfigValue($key, $default = null)
|
||||||
|
{
|
||||||
|
return $this->filesystemConfig($key) ?: $this->getValues(Settings::get($key, $default));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the filesystem config value if available.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function filesystemConfig($key)
|
||||||
|
{
|
||||||
|
return config('offline.cors::' . $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the repeater field values.
|
||||||
|
*
|
||||||
|
* @param mixed $values
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getValues($values)
|
||||||
|
{
|
||||||
|
return \is_array($values) ? collect($values)->pluck('value')->toArray() : $values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "offline/oc-cors-plugin",
|
||||||
|
"description": "Setup and manage Cross-Origin Resource Sharing headers in October CMS",
|
||||||
|
"type": "october-plugin",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Tobias Kündig",
|
||||||
|
"email": "tobias@offline.swiss"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?php return [
|
||||||
|
'plugin' => [
|
||||||
|
'name' => 'CORS',
|
||||||
|
'description' => 'Verwalte Cross-Origin Resource Sharing Header in October CMS',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?php return [
|
||||||
|
'plugin' => [
|
||||||
|
'name' => 'CORS',
|
||||||
|
'description' => 'Setup and manage Cross-Origin Resource Sharing headers',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OFFLINE\CORS\Models;
|
||||||
|
|
||||||
|
use Model;
|
||||||
|
|
||||||
|
class Settings extends Model
|
||||||
|
{
|
||||||
|
public $implement = ['System.Behaviors.SettingsModel'];
|
||||||
|
public $settingsCode = 'offline_cors_settings';
|
||||||
|
public $settingsFields = 'fields.yaml';
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
fields:
|
||||||
|
supportsCredentials:
|
||||||
|
label: Supports credentials
|
||||||
|
type: switch
|
||||||
|
comment: 'Set Access-Control-Allow-Credentials header to true'
|
||||||
|
default: false
|
||||||
|
span: left
|
||||||
|
maxAge:
|
||||||
|
label: Max age
|
||||||
|
type: number
|
||||||
|
comment: 'Set Access-Control-Max-Age to this value'
|
||||||
|
default: 0
|
||||||
|
span: right
|
||||||
|
tabs:
|
||||||
|
fields:
|
||||||
|
allowedOrigins:
|
||||||
|
label: Allowed origins
|
||||||
|
tab: Allowed origins
|
||||||
|
type: repeater
|
||||||
|
span: left
|
||||||
|
form:
|
||||||
|
fields:
|
||||||
|
value:
|
||||||
|
type: text
|
||||||
|
label: Origin
|
||||||
|
allowedHeaders:
|
||||||
|
label: Allowed headers
|
||||||
|
tab: Allowed headers
|
||||||
|
type: repeater
|
||||||
|
span: left
|
||||||
|
form:
|
||||||
|
fields:
|
||||||
|
value:
|
||||||
|
type: text
|
||||||
|
label: Header
|
||||||
|
allowedMethods:
|
||||||
|
label: Allowed methods
|
||||||
|
tab: Allowed methods
|
||||||
|
type: repeater
|
||||||
|
span: left
|
||||||
|
form:
|
||||||
|
fields:
|
||||||
|
value:
|
||||||
|
type: text
|
||||||
|
label: Method
|
||||||
|
exposedHeaders:
|
||||||
|
label: Exposed headers
|
||||||
|
tab: Exposed headers
|
||||||
|
type: repeater
|
||||||
|
span: left
|
||||||
|
form:
|
||||||
|
fields:
|
||||||
|
value:
|
||||||
|
type: text
|
||||||
|
label: Header
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
plugin:
|
||||||
|
name: 'offline.cors::lang.plugin.name'
|
||||||
|
description: 'offline.cors::lang.plugin.description'
|
||||||
|
author: 'OFFLINE GmbH'
|
||||||
|
icon: oc-icon-code
|
||||||
|
homepage: ''
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
1.0.1:
|
||||||
|
- Initial release.
|
||||||
|
1.0.2:
|
||||||
|
- Fixed backend settings label (thanks to LukeTowers)
|
||||||
|
1.0.3:
|
||||||
|
- Added support for filesystem configuration file / Added plugin to Packagist (https://packagist.org/packages/offline/oc-cors-plugin)
|
||||||
|
1.0.4:
|
||||||
|
- Fixed minor bug when running the plugin without custom settings
|
||||||
|
1.0.5:
|
||||||
|
- "Return proper 204 response code for preflight requests (thanks to @adrian-marinescu-ch on GitHub)"
|
||||||
|
1.0.6:
|
||||||
|
- "Dummy release to sync with Packagist version"
|
||||||
|
1.0.7:
|
||||||
|
- "Optimized compatibility with October v2"
|
||||||
Loading…
Reference in New Issue