This package has framework agnostic Cross-Origin Resource Sharing (CORS) implementation.

Related tags

Frameworks cors-psr7
Overview

Build Status Scrutinizer Code Quality Code Coverage License

Description

This package has framework agnostic Cross-Origin Resource Sharing (CORS) implementation. It is complaint with PSR-7 HTTP message interfaces.

Why this package?

Sample usage

The package is designed to be used as a middleware. Typical usage

use Neomerx\Cors\Analyzer;
use Psr\Http\Message\RequestInterface;
use Neomerx\Cors\Contracts\AnalysisResultInterface;

class CorsMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param RequestInterface $request
     * @param Closure          $next
     *
     * @return mixed
     */
    public function handle(RequestInterface $request, Closure $next)
    {
        $cors = Analyzer::instance($this->getCorsSettings())->analyze($request);
        
        switch ($cors->getRequestType()) {
            case AnalysisResultInterface::ERR_NO_HOST_HEADER:
            case AnalysisResultInterface::ERR_ORIGIN_NOT_ALLOWED:
            case AnalysisResultInterface::ERR_METHOD_NOT_SUPPORTED:
            case AnalysisResultInterface::ERR_HEADERS_NOT_SUPPORTED:
                // return 4XX HTTP error
                return ...;

            case AnalysisResultInterface::TYPE_PRE_FLIGHT_REQUEST:
                $corsHeaders = $cors->getResponseHeaders();
                // return 200 HTTP with $corsHeaders
                return ...;

            case AnalysisResultInterface::TYPE_REQUEST_OUT_OF_CORS_SCOPE:
                // call next middleware handler
                return $next($request);
            
            default:
                // actual CORS request
                $response    = $next($request);
                $corsHeaders = $cors->getResponseHeaders();
                
                // add CORS headers to Response $response
                ...
                return $response;
        }
    }
}

Settings

Analyzer accepts settings in Analyzer::instance($settings) which must implement AnalysisStrategyInterface. You can use default implementation \Neomerx\Cors\Strategies\Settings to set the analyzer up.

For example,

use Neomerx\Cors\Strategies\Settings;

$settings = (new Settings())
    ->setServerOrigin('https', 'api.example.com', 443)
    ->setPreFlightCacheMaxAge(0)
    ->setCredentialsSupported()
    ->setAllowedOrigins(['https://www.example.com', ...]) // or enableAllOriginsAllowed()
    ->setAllowedMethods(['GET', 'POST', 'DELETE', ...])   // or enableAllMethodsAllowed()
    ->setAllowedHeaders(['X-Custom-Header', ...])         // or enableAllHeadersAllowed()
    ->setExposedHeaders(['X-Custom-Header', ...])
    ->disableAddAllowedMethodsToPreFlightResponse()
    ->disableAddAllowedHeadersToPreFlightResponse()
    ->enableCheckHost();

$cors = Analyzer::instance($settings)->analyze($request);

Settings could be cached which improves performance. If you already have settings configured as in the example above you can get internal settings state as

/** @var array $dataToCache */
$dataToCache = $settings->getData();

Cached state should be used as

$settings = (new Settings())->setData($dataFromCache);
$cors     = Analyzer::instance($settings)->analyze($request);

Install

composer require neomerx/cors-psr7

Debug Mode

Debug logging will provide a detailed step-by-step description of how requests are handled. In order to activate it a PSR-3 compatible Logger should be set to Analyzer.

/** @var \Psr\Log\LoggerInterface $logger */
$logger   = ...;

$analyzer = Analyzer::instance($settings);
$analyzer->setLogger($logger)
$cors     = $analyzer->analyze($request);

Advanced Usage

There are many possible strategies for handling cross and same origin requests which might and might not depend on data from requests.

This built-in strategy Settings implements simple settings identical for all requests (same list of allowed origins, same allowed methods for all requests and etc).

However you can customize such behaviour. For example you can send different sets of allowed methods depending on request. This might be helpful when you have some kind of Access Control System and wish to differentiate response based on request (for example on its origin). You can either implement AnalysisStrategyInterface from scratch or override methods in Settings class if only a minor changes are needed to Settings. The new strategy could be sent to Analyzer constructor or Analyzer::instance method could be used for injection.

Example

class CustomMethodsSettings extends Settings
{
    public function getRequestAllowedMethods(RequestInterface $request): string
    {
        // An external Access Control System could be used to determine
        // which methods are allowed for this request.
        
        return ...;
    }
}

$cors = Analyzer::instance(new CustomMethodsSettings())->analyze($request);

Testing

composer test

Questions?

Do not hesitate to check issues or post a new one.

Contributing

If you have spotted any compliance issues with the CORS Recommendation please post an issue. Pull requests for documentation and code improvements (PSR-2, tests) are welcome.

Versioning

This package is using Semantic Versioning.

License

Apache License (Version 2.0). Please see License File for more information.

Comments
  • 3rd party issue. Analyzer::isSameHost fails to compare Host when SSL + zend-diactoros

    3rd party issue. Analyzer::isSameHost fails to compare Host when SSL + zend-diactoros

    When the request header 'Host' value is in form of Host: example.com (without port) the parse_url method isn't capable of parsing the url correctly.

    Feeding a url without a prefixed '' to parse_url will result in a non discovered domain name if the port is not present.

    But I think a Host header in this form should parseable as url - or am I mistaking?

    Host: example.com
    

    https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.23 https://github.com/neomerx/cors-psr7/blob/master/src/Analyzer.php#L256 https://github.com/neomerx/cors-psr7/blob/master/tests/Http/ParsedUrlTest.php#L59

    question 
    opened by basz 15
  • In some cases it incorrectly checks `Host` header

    In some cases it incorrectly checks `Host` header

    I am trying to implement this as a middleware on Silex Framework (version 2). Silex by default does not support PSR 7 request and responses, so I use DiactorosFactory Library to convert the Symfony Foundation Request/Response to & from PSR 7 Request and Response.

    In the CORS Settings, I have something like this

    ->setServerOrigin([
        'scheme' => 'http',
        'host'   => 'www.something.test',
        'port'   => 80,
    ])
    

    And the check fails even when the call is made to the host that is configured above i.e. www.something.test.

    I am making a preflight request to http://www.something.test and when it comes to Analyzer.php's isSameHost method $host get's value of www.something.test and $hostURL which should be parsed object, has host property as null.

    https://github.com/neomerx/cors-psr7/blob/24944f39483d1a89f66ae9d58cca9f82b8815b35/src/Analyzer.php#L320

    This is the because internally it uses php's function parse_url to parse url if it is string.

    https://github.com/neomerx/cors-psr7/blob/24944f39483d1a89f66ae9d58cca9f82b8815b35/src/Http/ParsedUrl.php#L64

    and after parsing www.something.test returns array which looks like

    array(..) {
      ["path"]=>
      string(5) "www.something.test"
    }
    

    and thus does not have enough information to match the host.

    https://github.com/neomerx/cors-psr7/blob/24944f39483d1a89f66ae9d58cca9f82b8815b35/src/Http/ParsedUrl.php#L71-L73

    I am not sure why

    https://github.com/neomerx/cors-psr7/blob/24944f39483d1a89f66ae9d58cca9f82b8815b35/src/Analyzer.php#L320-L321

    $host where which returns www.something.test passed to createParsedURL as it does not have enough data to properly parse all the request.

    bug fixed 
    opened by starx 13
  • Tag 1.1 release?

    Tag 1.1 release?

    Would it be possible to tag an 1.1 release?

    This is because versions before 1.0.4 throw an error with PHP 7.2 because of assert('is_string($url) || is_array($url)'); in ParsedUrl.php. Currently only way of ignoring everything below 1.0.4 would be to manually list all the minor releases.

    "neomerx/cors-psr7": "1.0.4|1.0.5|1.0.6|1.0.7|1.0.8|1.0.9|1.0.11|1.0.12|1.0.13",
    

    This not optimal and requires manually updating composer.json on my side always when neomerx/cors-psr7 has a new release.

    question 
    opened by tuupola 6
  • Creating Settings from an Array (Settings Factory)

    Creating Settings from an Array (Settings Factory)

    Are you planning to create a factory for Settings?

    For example:

    (new SettingsFactory)->createFromArray([
        'foo' => 'bar',
        'bar' => 'baz',
        'baz' => 'qux',
    ]);
    

    I would like not to write this:

    https://github.com/sunrise-php/awesome-skeleton/blob/master/src/Middleware/CorsMiddleware.php#L80

    What are your thoughts on this?

    question 
    opened by fenric 5
  • Allowing all headers should be removed

    Allowing all headers should be removed

    Currently there is a config option Settings::VALUE_ALLOW_ALL_HEADERS which should allow all headers pass through CORS. It works fine for internal lib logic. No problem here. The problem is that this value * is actually sent to client in Access-Control-Allow-Headers and browser don't understand this value.

    It looks the only possible way is listing all allowed headers and special * should be removed.

    It was added mostly to make development easier. However since logging has been added to the lib this feature is not so important.

    It is recommended avoid using Settings::VALUE_ALLOW_ALL_HEADERS and just list all allowed headers in Settings::KEY_ALLOWED_HEADERS

    bug enhancement 
    opened by neomerx 5
  • Make compatibility with IE easier

    Make compatibility with IE easier

    If using the exposed header keys they are set as an array into the http response header. As a result, there are several header lines with the same name in the response.

    The Internet Explorer cannot handle the headers, even if it would be correct according to w3c specification.

    In "/src/Strategies/Settings.php" the following function should look like this.

    BEFORE:

    public function getResponseExposedHeaders(RequestInterface $request)
        {
            return $this->getEnabledItems($this->settings[self::KEY_EXPOSED_HEADERS]);
        }
    

    AFTER:

    public function getResponseExposedHeaders(RequestInterface $request)
        {
            return implode(', ', $this->getEnabledItems($this->settings[self::KEY_EXPOSED_HEADERS]));
        }
    

    It would be nice if you could recognize this issue and update the project. Other browsers are not be affected by changing the returned value to a comma-separated string.

    enhancement fixed 
    opened by simplicitytrade 4
  • What kind of check does `serServerOrigin` do?

    What kind of check does `serServerOrigin` do?

    What kind of test does the following code does?

         $this->settings->setServerOrigin([
            'scheme' => 'http',
            'host'   => 'example.com',
            'port'   => 123,
        ])
    

    To pass the checks, should the request be coming from http://example.com port 123 or to http://example.com:123?

    I am trying to implement the library on Silex Framework and I am trying to match the request is coming to the one I am specifying, and it is not working.

    https://github.com/neomerx/cors-psr7/blob/24944f39483d1a89f66ae9d58cca9f82b8815b35/tests/AnalyzerTest.php#L62

    question 
    opened by starx 4
  • Small misleading in init factory method

    Small misleading in init factory method

    Trying to setup package in PHP-DI container and seems like I've found small mistake in init method.

    https://github.com/neomerx/cors-psr7/blob/0f968523b0b8215540915464aed08c5c4a4bf8b5/src/Strategies/Settings.php#L158-L159

    Second setAllowedOrigins([]) overwrites areAllOriginsAllowed to false value, then enableAllOriginsAllowed() makes no sense one line before.

    opened by ybelenko 3
  • - fixes issue with merged configs

    - fixes issue with merged configs

    This PR initializes constants with string values instead of numeric values.

    Numeric values causes problems, if the config values should be overwritten in aggregated configs.

    For example:

    <?php
    // cors.global.php
    
    use Neomerx\Cors\Strategies\Settings;
    
    return [
        'cors' => [
            Settings::KEY_SERVER_ORIGIN   => '192.168.0.1',
            Settings::KEY_ALLOWED_ORIGINS => [
                'some.allowed.origin' => true,
            ],
        ],
    ];
    
    <?php
    // cors.local.php
    
    use Neomerx\Cors\Strategies\Settings;
    
    return [
        'cors' => [
            Settings::KEY_SERVER_ORIGIN   => '192.168.0.2',
            Settings::KEY_ALLOWED_ORIGINS => [
                'some.allowed.origin' => true,
            ],
        ],
    ];
    

    If the two files are merged with an aggregator (like Zend ConfigAggregator), the merged config looks like

    [
        'cors' => [
            0 => '192.168.0.1',
            1 => [
                'some.allowed.origin' => true,
            ],
            2 => '192.168.0.2',
            3 => [
                'some.allowed.origin' => true,
            ],
        ],
    ];
    

    With string values the merged config works like expected

    [
        'cors' => [
            'keyServerOrigin' => '192.168.0.2',
            'keyAllowedOrigins' => [
                'some.allowed.origin' => true,
            ],
        ],
    ];
    
    question 
    opened by okoehler 3
  • Add GitHub Actions

    Add GitHub Actions

    As suggested in https://github.com/neomerx/cors-psr7/issues/42

    @neomerx: not that you might want to enable branch protection to enforce these status checks to complete successfully, or to block the merge if they fail.

    image

    opened by sneakyvv 2
  • Can't extend Analyzer class

    Can't extend Analyzer class

    Hi

    Thanks for an awesome library

    Due to the use of static:: in

    https://github.com/neomerx/cors-psr7/blob/0f968523b0b8215540915464aed08c5c4a4bf8b5/src/Analyzer.php#L277

    Because the class constants are private they can't be accessed from the child class. And when you extend the analyzer it will automatically try to use the child scope when using static:: so it would work if you used self:: in the Analyzer class.

    Would you be open to a pull-request to fix this?

    opened by sunkan 2
Releases(3.0.2)
Owner
null
Framework agnostic package to load heavy JSON in lazy collections.

Lazy JSON Framework agnostic package to load heavy JSON in lazy collections. Under the hood, the brilliant JSON Machine by @halaxa is used as lexer an

Andrea Marco Sartori 155 Dec 1, 2022
Motan - a cross-language remote procedure call(RPC) framework for rapid development of high performance distributed services

Motan-PHP Overview Motan is a cross-language remote procedure call(RPC) framework for rapid development of high performance distributed services.

Weibo R&D Open Source Projects 81 Nov 19, 2022
A framework agnostic, developer friendly wrapper around Fractal

A developer friendly wrapper around Fractal Fractal is an amazing package to transform data before using it in an API. Unfortunately working with Frac

Spatie 356 Dec 29, 2022
A resource-oriented micro PHP framework

Bullet Bullet is a resource-oriented micro PHP framework built around HTTP URIs. Bullet takes a unique functional-style approach to URL routing by par

Vance Lucas 415 Dec 27, 2022
A resource-oriented application framework

BEAR.Sunday A resource-oriented application framework What's BEAR.Sunday This resource orientated framework has both externally and internally a REST

Akihito Koriyama 6 May 11, 2022
A resource-oriented application framework

BEAR.Sunday A resource-oriented application framework What's BEAR.Sunday This resource orientated framework has both externally and internally a REST

BEAR.Sunday 236 Dec 27, 2022
The Semaphore Component manages semaphores, a mechanism to provide exclusive access to a shared resource.

Semaphore Component The Semaphore Component manages semaphores, a mechanism to provide exclusive access to a shared resource. Resources Documentation

Symfony 29 Nov 16, 2022
Dictionary of attack patterns and primitives for black-box application fault injection and resource discovery.

FuzzDB was created to increase the likelihood of finding application security vulnerabilities through dynamic application security testing. It's the f

FuzzDB Project 7.1k Dec 27, 2022
Strict PSR-7 implementation used by the Slim Framework

Strict PSR-7 implementation used by the Slim Framework, but you may use it separately with any framework compatible with the PSR-7 standard.

Slim Framework 96 Nov 14, 2022
A server side alternative implementation of socket.io in PHP based on workerman.

phpsocket.io A server side alternative implementation of socket.io in PHP based on Workerman. Notice Only support socket.io v1.3.0 or greater. This pr

walkor 2.1k Jan 6, 2023
CleverStyle Framework is simple, scalable, fast and secure full-stack PHP framework

CleverStyle Framework is simple, scalable, fast and secure full-stack PHP framework. It is free, Open Source and is distributed under Free Public Lice

Nazar Mokrynskyi 150 Apr 12, 2022
I made my own simple php framework inspired from laravel framework.

Simple MVC About Since 2019, I started learning the php programming language and have worked on many projects using the php framework. Laravel is one

null 14 Aug 14, 2022
PHPR or PHP Array Framework is a framework highly dependent to an array structure.

this is new repository for php-framework Introduction PHPR or PHP Array Framework is a framework highly dependent to an array structure. PHPR Framewor

Agung Zon Blade 2 Feb 12, 2022
I made my own simple php framework inspired from laravel framework.

Simple MVC About Since 2019, I started learning the php programming language and have worked on many projects using the php framework. Laravel is one

Rizky Alamsyah 14 Aug 14, 2022
Framework X – the simple and fast micro framework for building reactive web applications that run anywhere.

Framework X Framework X – the simple and fast micro framework for building reactive web applications that run anywhere. Quickstart Documentation Tests

Christian Lück 620 Jan 7, 2023
Framework X is a simple and fast micro framework based on PHP

Framework X is a simple and fast micro framework based on PHP. I've created a simple CRUD application to understand how it works. I used twig and I created a custom middleware to handle PUT, DELETE methods.

Mahmut Bayri 6 Oct 14, 2022
Spiral Framework is a High-Performance PHP/Go Full-Stack framework and group of over sixty PSR-compatible components

Spiral HTTP Application Skeleton Spiral Framework is a High-Performance PHP/Go Full-Stack framework and group of over sixty PSR-compatible components.

Spiral Scout 152 Dec 18, 2022
Sunhill Framework is a simple, fast, and powerful PHP App Development Framework

Sunhill Framework is a simple, fast, and powerful PHP App Development Framework that enables you to develop more modern applications by using MVC (Model - View - Controller) pattern.

Mehmet Selcuk Batal 3 Dec 29, 2022
A PHP framework for web artisans.

About Laravel Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experie

The Laravel Framework 72k Jan 7, 2023