1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
<?php
/**
* BEdita, API-first content management framework
* Copyright 2016 ChannelWeb Srl, Chialab Srl
*
* This file is part of BEdita: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
*/
namespace BEdita\API\Middleware;
use Cake\Http\CorsBuilder;
use Cake\Http\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* Handle cross-origin HTTP requests setting the proper headers.
*
* The response of preflight request (OPTIONS) is delivered directly after the headers are applied.
* For simple requests the CORS headers are applied before sending response.
*
* @since 4.0.0
*/
class CorsMiddleware
{
/**
* CORS configuration
*
* where:
* - 'allowOrigin' is a single domain or an array of domains
* - 'allowMethods' is an array of HTTP methods (it's applied only to preflight requests)
* - 'allowHeaders' is an array of HTTP headers (it's applied only to preflight requests)
* - 'allowCredentials' enable cookies to be sent in CORS requests
* - 'exposeHeaders' is an array of headers that a client library/browser can expose to scripting
* - 'maxAge' is the max-age preflight OPTIONS requests are valid for (it's applied only to preflight requests)
*
* When value is falsy the related configuration is skipped.
*
* `'allowOrigin'`, `'allowMethods'` and `'allowHeaders'` support the `'*'` wildcard
* to allow respectively every origin, every methods and every headers.
*
* @var array
*/
protected $corsConfig = [
'allowOrigin' => false,
'allowMethods' => false,
'allowHeaders' => false,
'allowCredentials' => false,
'exposeHeaders' => false,
'maxAge' => false,
];
/**
* Constructor
*
* Setup CORS using `$corsConfig` array
*
* @see self::corsConfig
* @param array|null $corsConfig CORS configuration
*/
public function __construct($corsConfig = null)
{
if (empty($corsConfig) || !is_array($corsConfig)) {
return;
}
$allowedConfig = array_intersect_key($corsConfig, $this->corsConfig);
$this->corsConfig = $allowedConfig + $this->corsConfig;
if ($this->corsConfig['allowMethods'] == '*') {
$this->corsConfig['allowMethods'] = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
}
}
/**
* If no CORS configuration is present delegate to server
* If the request is a preflight send the response applying CORS rules.
* If it is a simple request it applies CORS rules to the response and call next middleware
*
* @param \Psr\Http\Message\ServerRequestInterface $request The request.
* @param \Psr\Http\Message\ResponseInterface $response The response.
* @param callable $next The next middleware to call.
* @return \Psr\Http\Message\ResponseInterface A response.
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
if ($request->getMethod() == 'OPTIONS') {
return $this->buildCors($request, $response);
}
$response = $next($request, $response);
return $this->buildCors($request, $response);
}
/**
* Tell if CORS is configured
*
* @return bool
*/
public function isConfigured()
{
return (bool)array_filter($this->corsConfig);
}
/**
* Build response headers following CORS configuration
* and return the new response
*
* @param \Psr\Http\Message\ServerRequestInterface $request The request.
* @param \Psr\Http\Message\ResponseInterface $response The response.
* @return \Psr\Http\Message\ResponseInterface A response.
* @throws \Cake\Http\Exception\ForbiddenException When origin
*/
protected function buildCors(ServerRequestInterface $request, ResponseInterface $response)
{
if (!$this->isConfigured() || !($response instanceof Response)) {
return $response;
}
$origin = $request->getHeaderLine('Origin');
$isSsl = ($request->getUri()->getScheme() == 'https');
$corsBuilder = new CorsBuilder($response, $origin, $isSsl);
$options = array_filter($this->corsConfig);
if (!empty($options['allowHeaders']) && $options['allowHeaders'] === '*') {
$options['allowHeaders'] = $request->getHeader('Access-Control-Request-Headers');
}
foreach ($options as $corsOption => $corsValue) {
$corsBuilder->{$corsOption}($corsValue);
}
$response = $corsBuilder->build();
if ($response->getHeaderLine('Access-Control-Allow-Origin') !== '*') {
$response = $response->withAddedHeader('Vary', 'Origin');
}
return $response;
}
}