[
  {
    "path": ".gitattributes",
    "content": "/test export-ignore\n/examples export-ignore\n.gitattributes export-ignore\n.gitignore export-ignore\n.scrutinizer.yml export-ignore\n.travis.yml export-ignore\nphpunit.xml.dist\n"
  },
  {
    "path": ".gitignore",
    "content": "vendor\ncomposer.lock\ncomposer.phar\nclover.xml\nhumbug-log.json\nhumbug-log.txt\nhumbug"
  },
  {
    "path": ".scrutinizer.yml",
    "content": "before_commands:\n    - \"composer update --prefer-source\"\n\ntools:\n    external_code_coverage:\n        timeout: 1800\n    php_code_coverage:\n        enabled: true\n    php_code_sniffer:\n        enabled: false\n    php_cpd:\n        enabled: true\n        excluded_dirs: [\"test\", \"vendor\"]\n    php_cs_fixer:\n        enabled: true\n        config:\n            level: all\n        filter:\n            paths: [\"src/*\", \"test/*\"]\n    php_loc:\n        enabled: true\n        excluded_dirs: [\"test\", \"vendor\"]\n    php_mess_detector:\n        enabled: true\n        filter:\n            paths: [\"src/*\"]\n    php_pdepend:\n        enabled: true\n        excluded_dirs: [\"test\", \"vendor\"]\n    php_analyzer:\n        enabled: true\n        filter:\n            paths: [\"src/*\", \"test/*\"]\n    php_hhvm:\n        enabled: true\n        filter:\n            paths: [\"src/*\", \"test/*\"]\n    sensiolabs_security_checker: true\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: php\n\nphp:\n  - 7.1\n  - 7.2\n  - nightly\n\nbefore_script:\n  - composer update\n  - if [ \"$DEPENDENCIES\" = 'low' ] ; then composer update --prefer-source --prefer-lowest --prefer-stable; fi\n  - git clone https://github.com/padraic/humbug.git && cd humbug && composer install && cd ..\n\nscript:\n  - if [[ $TRAVIS_PHP_VERSION = '7.1' ]]; then PHPUNIT_FLAGS=\"--coverage-clover ./clover.xml\"; else PHPUNIT_FLAGS=\"\"; fi\n  - ./vendor/bin/phpunit $PHPUNIT_FLAGS\n  - ./vendor/bin/phpcs\n  - if [[ $TRAVIS_PHP_VERSION = '7.1' ]]; then ./vendor/bin/humbug; fi\n  - cd examples\n  - composer install\n  - php index.php > /dev/null\n\nenv:\n  matrix:\n    - DEPENDENCIES=\"high\"\n    - DEPENDENCIES=\"low\"\n\nmatrix:\n  allow_failures:\n    - php: hhvm\n\nafter_script:\n  - wget https://scrutinizer-ci.com/ocular.phar\n  - if [ -f clover.xml ]; then php ocular.phar code-coverage:upload --format=php-clover ./clover.xml; fi\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "This is a list of changes/improvements that were introduced in PSR7Csrf\n\n## 2.0.0\n\n- This release aligns the `PSR7Csrf\\CSRFCheckerMiddleware` implementation to\n  the [PSR-15 `php-fig/http-server-middleware`](https://github.com/php-fig/http-server-middleware/tree/1.0.0)\n  specification.\n   \n  This means that the signature of `PSR7Csrf\\CSRFCheckerMiddleware`\n  changed, and therefore you need to look for usages of this class and verify\n  if the new signature is compatible with your API\n\n  Specifically, `PSR7Csrf\\CSRFCheckerMiddleware#__invoke()` was removed.\n  \n- The minimum supported PHP version has been raised to `7.1.0`\n\n- the `PSR7Csrf\\Factory::createDefaultCSRFCheckerMiddleware()` method now has\n  a mandatory argument, which is the response to be produced in case of failed\n  CSRF validation. This argument is mandatory, since PSR7Csrf won't couple you\n  to a specific PSR-7 implementation.\n\n## 1.0.2\n\n### Fixed\n\n- Allow installation of [PSR7Session](https://github.com/Ocramius/PSR7Session)\n  [2.0.0](https://github.com/Ocramius/PSR7Session/releases/tag/2.0.0) [#2](https://github.com/Ocramius/PSR7Csrf/pull/1)\n\n## 1.0.1\n\n### Fixed\n\n- Minor wording issues in [`README.md`](README.md] [#1](https://github.com/Ocramius/PSR7Csrf/pull/1)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\n * Coding standard for the project is [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)\n * The project will follow [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php)\n * Any contribution must provide tests for additional/corrected scenarios\n * Any un-confirmed issue needs a failing test case before being accepted\n * Pull requests must be sent from a new hotfix/feature branch, not from `master`.\n\n## Installation\n\nTo install the project and run the tests, you need to clone it first:\n\n```sh\n$ git clone git://github.com/Ocramius/PSR7Csrf.git\n```\n\nYou will then need to run a composer installation:\n\n```sh\n$ cd PSR7Csrf\n$ curl -s https://getcomposer.org/installer | php\n$ php composer.phar update\n```\n\n## Testing\n\nThe PHPUnit version to be used is the one installed as a dev- dependency via composer:\n\n```sh\n$ ./vendor/bin/phpunit\n```\n\nAccepted coverage for new contributions is 80%. Any contribution not satisfying this requirement \nwon't be merged.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2015 Marco Pivetta\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# PSR-7 Storage-less HTTP CSRF protection\n\n[![Build Status](https://travis-ci.org/Ocramius/PSR7Csrf.svg)](https://travis-ci.org/Ocramius/PSR7Csrf)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Ocramius/PSR7Csrf/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Ocramius/PSR7Csrf/?branch=master)\n[![Code Coverage](https://scrutinizer-ci.com/g/Ocramius/PSR7Csrf/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Ocramius/PSR7Csrf/?branch=master)\n[![Packagist](https://img.shields.io/packagist/v/ocramius/psr7-csrf.svg)](https://packagist.org/packages/ocramius/psr7-csrf)\n[![Packagist](https://img.shields.io/packagist/vpre/ocramius/psr7-csrf.svg)](https://packagist.org/packages/ocramius/psr7-csrf)\n\n**PSR7Csrf** is a [PSR-7](http://www.php-fig.org/psr/psr-7/)\n[middleware](https://mwop.net/blog/2015-01-08-on-http-middleware-and-psr-7.html) that enables\n[CSRF](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)) protection for PSR-7 based applications.\n\n# DEPRECATED in favor of `psr7-sessions/storageless` 5.0.0+\n\nPlease note that this package is **DEPRECATED**.\n\nSince [`psr7-sessions/storageless` 5.0.0](https://github.com/psr7-sessions/storageless/releases/tag/5.0.0),\nthe generated cookies are CSRF-resistant by default for unsafe HTTP methods (`POST`/`PUT`/`DELETE`/`PATCH`/etc.),\nso the usage of this package is no longer needed.\nYou can still install `ocramius/psr7-csrf`, but since there is no practical need for it,\nit is not necessary to do so.\n\n### What is this about?\n\nInstead of storing tokens in the session, PSR7Csrf simply uses JWT tokens,\nwhich can be verified, signed and have a specific lifetime on their own.\n\nThis storage-less approach prevents having to load tokens from a session\nor from a database, and simplifies the entire UI workflow: tokens are\nvalid as long as their signature and expiration date holds.\n\n### Installation\n\n```sh\ncomposer require ocramius/psr7-csrf\n```\n\n### Usage\n\nThe simplest usage is based on defaults. It assumes that you have\na configured PSR-7 compatible application that supports piping\nmiddlewares, and it also requires you to run [PSR7Session](https://github.com/Ocramius/PSR7Session).\n\nIn a [`zendframework/zend-expressive`](https://github.com/zendframework/zend-expressive)\napplication, the setup would look like the following:\n\n```php\n$app = \\Zend\\Expressive\\AppFactory::create();\n\n$app->pipe(\\PSR7Session\\Http\\SessionMiddleware::fromSymmetricKeyDefaults(\n    'mBC5v1sOKVvbdEitdSBenu59nfNfhwkedkJVNabosTw=', // replace this with a key of your own (see PSR7Session docs)\n    1200 // 20 minutes session duration\n));\n\n$app->pipe(\\PSR7Csrf\\Factory::createDefaultCSRFCheckerMiddleware());\n```\n\nThis setup will require that any requests that are not `GET`, `HEAD` or\n`OPTIONS` contain a `csrf_token` in the request body parameters (JSON\nor URL-encoded).\n\nYou can generate the CSRF token for any form like following:\n\n```php\n$tokenGenerator = \\PSR7Csrf\\Factory::createDefaultTokenGenerator();\n\n$app->get('/get', function ($request, $response) use ($tokenGenerator) {\n    $response\n        ->getBody()\n        ->write(\n            '<form method=\"post\" action=\"/post\">'\n            . '<input type=\"submit\"/>'\n            . '<input type=\"hidden\" name=\"csrf_token\" value=\"'\n            . $tokenGenerator($request)\n            . '\"/>'\n            . '</form>'\n        );\n\n    return $response;\n});\n\n$app->post('/post', function ($request, $response) {\n    $response\n        ->getBody()\n        ->write('It works!');\n\n    return $response;\n});\n```\n\n### Examples\n\n```sh\ncomposer install # install at the root of this package first!\ncd examples\ncomposer install\nphp -S localhost:9999 index.php\n```\n\nThen try accessing `http://localhost:9999`: you should see a simple\nsubmission form.\n\nIf you try modifying the submitted CSRF token (which is in a hidden\nform field), then the `POST` request will fail.\n\n### Known limitations\n\nPlease refer to the [known limitations of PSR7Session](https://github.com/Ocramius/PSR7Session/blob/master/docs/limitations.md).\n\nAlso, this component does *NOT* prevent double-form-submissions: it\nmerely prevents CSRF attacks from third parties. As long as the CSRF\ntoken is valid, it can be reused over multiple requests.\n\n### Contributing\n\nPlease refer to the [contributing notes](CONTRIBUTING.md).\n\n### License\n\nThis project is made public under the [MIT LICENSE](LICENSE).\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\":     \"ocramius/psr7-csrf\",\n    \"license\":  \"MIT\",\n    \"authors\": [\n        {\n            \"name\":     \"Marco Pivetta\",\n            \"email\":    \"ocramius@gmail.com\",\n            \"homepage\": \"http://ocramius.github.io/\",\n            \"role\":     \"Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\":                             \"^7.1.0\",\n        \"psr/http-message\":                \"^1.0.1\",\n        \"lcobucci/jwt\":                    \"^3.2.2\",\n        \"psr/http-server-handler\":         \"^1.0.0\",\n        \"psr/http-server-middleware\":      \"^1.0.0\",\n        \"psr7-sessions/storageless\":       \"^4.0.0\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\":           \"^6.5.5\",\n        \"humbug/humbug\":             \"^1.0.0-rc.0\",\n        \"squizlabs/php_codesniffer\": \"^2.6.0\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"PSR7Csrf\\\\\": \"src/PSR7Csrf\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"PSR7CsrfTest\\\\\": \"test/PSR7CsrfTest\"\n        }\n    },\n    \"extra\": {\n        \"branch-alias\": {\n            \"dev-master\": \"3.0.x-dev\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/.gitignore",
    "content": "vendor\ncomposer.lock"
  },
  {
    "path": "examples/composer.json",
    "content": "{\n    \"require\": {\n        \"zendframework/zend-diactoros\": \"^1.7.0\"\n    },\n    \"autoload\": {\n        \"files\": [\n            \"../vendor/autoload.php\"\n        ]\n    }\n}\n"
  },
  {
    "path": "examples/index.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\Example;\n\nuse Dflydev\\FigCookies\\SetCookie;\nuse Lcobucci\\Clock\\SystemClock;\nuse Lcobucci\\JWT\\Parser;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Server\\MiddlewareInterface;\nuse Psr\\Http\\Server\\RequestHandlerInterface;\nuse PSR7Csrf\\Factory;\nuse PSR7Csrf\\TokenGeneratorInterface;\nuse PSR7Sessions\\Storageless\\Http\\SessionMiddleware;\nuse Zend\\Diactoros\\Response\\HtmlResponse;\nuse Zend\\Diactoros\\Response\\JsonResponse;\nuse Zend\\Diactoros\\Response\\SapiEmitter;\nuse Zend\\Diactoros\\Response\\TextResponse;\nuse Zend\\Diactoros\\ServerRequestFactory;\n\nrequire_once __DIR__ . '/vendor/autoload.php';\n\n// We use PSR7Sessions\\Storageless here - if you don't like it, you need to provide\n// an equivalent session middleware implementation.\n$sessionMiddleware = new SessionMiddleware(\n    new Sha256(),\n    'c9UA8QKLSmDEn4DhNeJIad/4JugZd/HvrjyKrS0jOes=', // signature key (important: change this to your own)\n    'c9UA8QKLSmDEn4DhNeJIad/4JugZd/HvrjyKrS0jOes=', // verification key (important: change this to your own)\n    SetCookie::create('an-example-cookie-name')\n             ->withSecure(false)// false on purpose, unless you have https locally - DO NOT DO THIS IN PRODUCTION!\n             ->withHttpOnly(true),\n    new Parser(),\n    1200,\n    new SystemClock()\n);\n\n/**\n * This is the actual component from this package. Setup assumes that\n * a `SessionMiddleware` was previously piped through. If you don't do that,\n * then all requests will fail CSRF validation!\n */\n$csrfMiddleware = Factory::createDefaultCSRFCheckerMiddleware(\n    (new JsonResponse(['error' => 'CSRF validation failed']))->withStatus(401)\n);\n\n/**\n * The token generator is needed to generate CSRF tokens to be added to your forms\n */\n$tokenGenerator = Factory::createDefaultTokenGenerator();\n\n/**\n * This is an example of how you'd generate a form with a CSRF token from this package\n */\n$action = new class ($tokenGenerator) implements RequestHandlerInterface\n{\n    /**\n     * @var TokenGeneratorInterface\n     */\n    private $tokenGenerator;\n\n    public function __construct(TokenGeneratorInterface $tokenGenerator)\n    {\n        $this->tokenGenerator = $tokenGenerator;\n    }\n\n    public function handle(ServerRequestInterface $request) : ResponseInterface\n    {\n        if ('GET' === \\strtoupper($request->getMethod())) {\n            return new HtmlResponse(\n                '<form method=\"post\" action=\"/post\">'\n                . '<input type=\"submit\" value=\"Submit with CSRF token\"/>'\n                . '<input type=\"hidden\" name=\"csrf_token\" value=\"'\n                . $this->tokenGenerator->__invoke($request)\n                . '\"/>'\n                . '</form>'\n                . '<form method=\"post\" action=\"/post\">'\n                . '<input type=\"submit\" value=\"Submit without CSRF token\"/>'\n                . '</form>'\n            );\n        }\n\n        return new TextResponse('It works!');\n    }\n};\n\n/**\n * Don't panic! This just emulates what a typical middleware-based HTTP framework does internally\n */\n$pipe = new class ($action, $sessionMiddleware, $csrfMiddleware) implements RequestHandlerInterface\n{\n    /**\n     * @var RequestHandlerInterface\n     */\n    private $action;\n\n    /**\n     * @var MiddlewareInterface[]\n     */\n    private $pipedMiddleware;\n\n    public function __construct(RequestHandlerInterface $action, MiddlewareInterface ...$pipedMiddleware)\n    {\n        $this->action = $action;\n        $this->pipedMiddleware = $pipedMiddleware;\n    }\n\n    public function handle(ServerRequestInterface $request) : ResponseInterface\n    {\n        if (! $this->pipedMiddleware) {\n            return $this->action->handle($request);\n        }\n\n        return $this->pipedMiddleware[0]->process(\n            $request,\n            new self($this->action, ...\\array_values(\\array_slice($this->pipedMiddleware, 1)))\n        );\n    }\n};\n\n// produce the response\n(new SapiEmitter())\n    ->emit($pipe->handle(ServerRequestFactory::fromGlobals()));\n"
  },
  {
    "path": "humbug.json.dist",
    "content": "{\n    \"timeout\": 30,\n    \"source\": {\n        \"directories\": [\n            \"src\"\n        ]\n    },\n    \"logs\": {\n        \"text\": \"humbug-log.txt\",\n        \"json\": \"humbug-log.json\"\n    }\n}\n"
  },
  {
    "path": "phpcs.xml.dist",
    "content": "<?xml version=\"1.0\"?>\n<ruleset name=\"Custom\">\n    <description>code-reviews.io code-style</description>\n    <file>./src</file>\n    <file>./test</file>\n    <exclude-pattern>./test/PSR7CsrfTest/RequestParameter/ExtractCSRFParameterTest.php</exclude-pattern>\n    <rule ref=\"PSR2\">\n        <exclude name=\"Generic.Files.LineLength\"/>\n    </rule>\n</ruleset>\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\"?>\n<phpunit\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n    bootstrap=\"./vendor/autoload.php\"\n    colors=\"true\"\n    convertErrorsToExceptions=\"true\"\n    convertNoticesToExceptions=\"true\"\n    convertWarningsToExceptions=\"true\"\n    verbose=\"true\"\n    stopOnFailure=\"false\"\n    processIsolation=\"false\"\n    backupGlobals=\"false\"\n    beStrictAboutChangesToGlobalState=\"true\"\n    beStrictAboutOutputDuringTests=\"true\"\n    beStrictAboutResourceUsageDuringSmallTests=\"true\"\n    beStrictAboutTodoAnnotatedTests=\"true\"\n    beStrictAboutTestsThatDoNotTestAnything=\"true\"\n>\n    <testsuite name=\"PSR7Csrf tests\">\n        <directory>./test/PSR7CsrfTest</directory>\n    </testsuite>\n    <filter>\n        <whitelist addUncoveredFilesFromWhitelist=\"true\">\n            <directory suffix=\".php\">./src</directory>\n        </whitelist>\n    </filter>\n</phpunit>\n"
  },
  {
    "path": "src/PSR7Csrf/CSRFCheckerMiddleware.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf;\n\nuse BadMethodCallException;\nuse InvalidArgumentException;\nuse Lcobucci\\JWT\\Parser;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\ValidationData;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Server\\RequestHandlerInterface;\nuse PSR7Csrf\\Exception\\SessionAttributeNotFoundException;\nuse PSR7Csrf\\HttpMethod\\IsSafeHttpRequestInterface;\nuse PSR7Csrf\\RequestParameter\\ExtractCSRFParameterInterface;\nuse PSR7Csrf\\Session\\ExtractUniqueKeyFromSessionInterface;\nuse PSR7Sessions\\Storageless\\Session\\SessionInterface;\n\nfinal class CSRFCheckerMiddleware implements \\Psr\\Http\\Server\\MiddlewareInterface\n{\n    /**\n     * @var Signer\n     */\n    private $signer;\n\n    /**\n     * @var Parser\n     */\n    private $tokenParser;\n\n    /**\n     * @var IsSafeHttpRequestInterface\n     */\n    private $isSafeHttpRequest;\n\n    /**\n     * @var ExtractUniqueKeyFromSessionInterface\n     */\n    private $extractUniqueKeyFromSession;\n\n    /**\n     * @var ExtractCSRFParameterInterface\n     */\n    private $extractCSRFParameter;\n\n    /**\n     * @var string\n     */\n    private $sessionAttribute;\n\n    /**\n     * @var ResponseInterface\n     */\n    private $faultyResponse;\n\n    public function __construct(\n        IsSafeHttpRequestInterface $isSafeHttpRequest,\n        ExtractUniqueKeyFromSessionInterface $extractUniqueKeyFromSession,\n        ExtractCSRFParameterInterface $extractCSRFParameter,\n        Parser $tokenParser,\n        Signer $signer,\n        string $sessionAttribute,\n        ResponseInterface $faultyResponse\n    ) {\n        $this->isSafeHttpRequest           = $isSafeHttpRequest;\n        $this->extractUniqueKeyFromSession = $extractUniqueKeyFromSession;\n        $this->extractCSRFParameter        = $extractCSRFParameter;\n        $this->tokenParser                 = $tokenParser;\n        $this->signer                      = $signer;\n        $this->sessionAttribute            = $sessionAttribute;\n        $this->faultyResponse              = $faultyResponse;\n    }\n\n    public function process(\n        ServerRequestInterface $request,\n        RequestHandlerInterface $handler\n    ) : ResponseInterface {\n        if ($this->isSafeHttpRequest->__invoke($request)) {\n            return $handler->handle($request);\n        }\n\n        try {\n            $token = $this->tokenParser->parse($this->extractCSRFParameter->__invoke($request));\n\n            if ($token->validate(new ValidationData())\n                && $token->verify(\n                    $this->signer,\n                    $this->extractUniqueKeyFromSession->__invoke($this->getSession($request))\n                )\n            ) {\n                return $handler->handle($request);\n            }\n        } catch (BadMethodCallException $invalidToken) {\n            return $this->faultyResponse;\n        } catch (InvalidArgumentException $invalidToken) {\n            return $this->faultyResponse;\n        }\n\n        return $this->faultyResponse;\n    }\n\n    private function getSession(ServerRequestInterface $request) : SessionInterface\n    {\n        $session = $request->getAttribute($this->sessionAttribute);\n\n        if (! $session instanceof SessionInterface) {\n            throw SessionAttributeNotFoundException::fromAttributeNameAndRequest($this->sessionAttribute, $request);\n        }\n\n        return $session;\n    }\n}\n"
  },
  {
    "path": "src/PSR7Csrf/Exception/ExceptionInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\Exception;\n\nuse Throwable;\n\ninterface ExceptionInterface extends Throwable\n{\n}\n"
  },
  {
    "path": "src/PSR7Csrf/Exception/InvalidExpirationTimeException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\Exception;\n\nuse InvalidArgumentException;\n\nclass InvalidExpirationTimeException extends InvalidArgumentException implements ExceptionInterface\n{\n    public static function fromInvalidExpirationTime(int $expirationTime) : self\n    {\n        return new self(sprintf('The provided expiration time %s is invalid: expected a >0 integer', $expirationTime));\n    }\n}\n"
  },
  {
    "path": "src/PSR7Csrf/Exception/InvalidRequestParameterNameException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\Exception;\n\nuse InvalidArgumentException;\n\nclass InvalidRequestParameterNameException extends InvalidArgumentException implements ExceptionInterface\n{\n    public static function fromEmptyRequestParameterName() : self\n    {\n        return new self('The given request parameter must be a non-empty string');\n    }\n}\n"
  },
  {
    "path": "src/PSR7Csrf/Exception/SessionAttributeNotFoundException.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\Exception;\n\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse UnexpectedValueException;\n\nclass SessionAttributeNotFoundException extends UnexpectedValueException implements ExceptionInterface\n{\n    public static function fromAttributeNameAndRequest(string $attributeName, ServerRequestInterface $request) : self\n    {\n        return new self(sprintf(\n            'Provided request contains no matching session attribute \"%s\", attributes %s exist',\n            $attributeName,\n            json_encode(array_keys($request->getAttributes()))\n        ));\n    }\n}\n"
  },
  {
    "path": "src/PSR7Csrf/Factory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf;\n\nuse Lcobucci\\JWT\\Parser;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse PSR7Csrf\\HttpMethod\\IsSafeHttpRequest;\nuse PSR7Csrf\\RequestParameter\\ExtractCSRFParameter;\nuse PSR7Csrf\\Session\\ExtractUniqueKeyFromSession;\nuse PSR7Sessions\\Storageless\\Http\\SessionMiddleware;\n\nfinal class Factory\n{\n    public const DEFAULT_SIGNATURE_KEY_NAME = 'csrf_signature_key';\n\n    public const DEFAULT_CSRF_DATA_KEY = 'csrf_token';\n\n    public const DEFAULT_EXPIRATION_TIME = 60 * 24;\n\n    public static function createDefaultCSRFCheckerMiddleware(\n        ResponseInterface $failedCsrfValidationResponse\n    ) : CSRFCheckerMiddleware {\n        return new CSRFCheckerMiddleware(\n            IsSafeHttpRequest::fromDefaultSafeMethods(),\n            new ExtractUniqueKeyFromSession(self::DEFAULT_SIGNATURE_KEY_NAME),\n            new ExtractCSRFParameter(self::DEFAULT_CSRF_DATA_KEY),\n            new Parser(),\n            new Sha256(),\n            SessionMiddleware::SESSION_ATTRIBUTE,\n            $failedCsrfValidationResponse\n        );\n    }\n\n    public static function createDefaultTokenGenerator() : TokenGeneratorInterface\n    {\n        return new TokenGenerator(\n            new Sha256(),\n            new ExtractUniqueKeyFromSession(self::DEFAULT_SIGNATURE_KEY_NAME),\n            self::DEFAULT_EXPIRATION_TIME,\n            SessionMiddleware::SESSION_ATTRIBUTE\n        );\n    }\n}\n"
  },
  {
    "path": "src/PSR7Csrf/HttpMethod/IsSafeHttpRequest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\HttpMethod;\n\nuse Lcobucci\\JWT\\Signer;\nuse Psr\\Http\\Message\\RequestInterface;\n\nfinal class IsSafeHttpRequest implements IsSafeHttpRequestInterface\n{\n    const STRICT_CHECKING = true;\n\n    /**\n     * @var \\string[]\n     */\n    private $safeMethods;\n\n    public function __construct(string ...$safeMethods)\n    {\n        $this->safeMethods = array_map('strtoupper', $safeMethods);\n    }\n\n    public static function fromDefaultSafeMethods() : self\n    {\n        return new self('GET', 'HEAD', 'OPTIONS');\n    }\n\n    public function __invoke(RequestInterface $request) : bool\n    {\n        return in_array(strtoupper($request->getMethod()), $this->safeMethods, self::STRICT_CHECKING);\n    }\n}\n"
  },
  {
    "path": "src/PSR7Csrf/HttpMethod/IsSafeHttpRequestInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\HttpMethod;\n\nuse Lcobucci\\JWT\\Signer;\nuse Psr\\Http\\Message\\RequestInterface;\n\ninterface IsSafeHttpRequestInterface\n{\n    public function __invoke(RequestInterface $request) : bool;\n}\n"
  },
  {
    "path": "src/PSR7Csrf/RequestParameter/ExtractCSRFParameter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\RequestParameter;\n\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse PSR7Csrf\\Exception\\InvalidRequestParameterNameException;\n\nfinal class ExtractCSRFParameter implements ExtractCSRFParameterInterface\n{\n    /**\n     * @var string\n     */\n    private $csrfDataKey;\n\n    public function __construct(string $csrfDataKey)\n    {\n        if ('' === $csrfDataKey) {\n            throw InvalidRequestParameterNameException::fromEmptyRequestParameterName();\n        }\n\n        $this->csrfDataKey = $csrfDataKey;\n    }\n\n    public function __invoke(ServerRequestInterface $request) : string\n    {\n        /* @var $requestBody array */\n        $requestBody = $request->getParsedBody();\n\n        if (is_object($requestBody) && array_key_exists($this->csrfDataKey, (array) $requestBody)) {\n            $arrayBody = (array) $requestBody;\n\n            return $this->ensureThatTheValueIsAString($arrayBody[$this->csrfDataKey]);\n        }\n\n        if (is_array($requestBody) && array_key_exists($this->csrfDataKey, $requestBody)) {\n            return $this->ensureThatTheValueIsAString($requestBody[$this->csrfDataKey]);\n        }\n\n        return '';\n    }\n\n    private function ensureThatTheValueIsAString($value) : string\n    {\n        if (! is_string($value)) {\n            return '';\n        }\n\n        return $value;\n    }\n}\n"
  },
  {
    "path": "src/PSR7Csrf/RequestParameter/ExtractCSRFParameterInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\RequestParameter;\n\nuse Psr\\Http\\Message\\ServerRequestInterface;\n\ninterface ExtractCSRFParameterInterface\n{\n    public function __invoke(ServerRequestInterface $request) : string;\n}\n"
  },
  {
    "path": "src/PSR7Csrf/Session/ExtractUniqueKeyFromSession.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\Session;\n\nuse PSR7Sessions\\Storageless\\Session\\SessionInterface;\n\nfinal class ExtractUniqueKeyFromSession implements ExtractUniqueKeyFromSessionInterface\n{\n    const ENTROPY = 32;\n\n    /**\n     * @var string\n     */\n    private $uniqueIdKey;\n\n    public function __construct(string $uniqueIdKey)\n    {\n        $this->uniqueIdKey = $uniqueIdKey;\n    }\n\n    public function __invoke(SessionInterface $session) : string\n    {\n        $uniqueKey = $session->get($this->uniqueIdKey, '');\n\n        if ('' === $uniqueKey || ! is_string($uniqueKey)) {\n            $generatedKey = bin2hex(random_bytes(self::ENTROPY));\n\n            $session->set($this->uniqueIdKey, $generatedKey);\n\n            return $generatedKey;\n        }\n\n        return $uniqueKey;\n    }\n}\n"
  },
  {
    "path": "src/PSR7Csrf/Session/ExtractUniqueKeyFromSessionInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf\\Session;\n\nuse PSR7Sessions\\Storageless\\Session\\SessionInterface;\n\ninterface ExtractUniqueKeyFromSessionInterface\n{\n    public function __invoke(SessionInterface $session) : string;\n}\n"
  },
  {
    "path": "src/PSR7Csrf/TokenGenerator.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf;\n\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Token;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse PSR7Csrf\\Exception\\InvalidExpirationTimeException;\nuse PSR7Csrf\\Exception\\SessionAttributeNotFoundException;\nuse PSR7Csrf\\Session\\ExtractUniqueKeyFromSessionInterface;\nuse PSR7Sessions\\Storageless\\Session\\SessionInterface;\n\nfinal class TokenGenerator implements TokenGeneratorInterface\n{\n    /**\n     * @var Signer\n     */\n    private $signer;\n\n    /**\n     * @var ExtractUniqueKeyFromSessionInterface\n     */\n    private $extractUniqueKeyFromSession;\n\n    /**\n     * @var int\n     */\n    private $expirationTime;\n\n    /**\n     * @var string\n     */\n    private $sessionAttribute;\n\n    /**\n     * @param Signer                               $signer\n     * @param ExtractUniqueKeyFromSessionInterface $extractUniqueKeyFromSession\n     * @param int                                  $expirationTime\n     * @param string                               $sessionAttribute\n     *\n     * @throws InvalidExpirationTimeException\n     */\n    public function __construct(\n        Signer $signer,\n        ExtractUniqueKeyFromSessionInterface $extractUniqueKeyFromSession,\n        int $expirationTime,\n        string $sessionAttribute\n    ) {\n        if ($expirationTime <= 0) {\n            throw InvalidExpirationTimeException::fromInvalidExpirationTime($expirationTime);\n        }\n\n        $this->signer                      = $signer;\n        $this->extractUniqueKeyFromSession = $extractUniqueKeyFromSession;\n        $this->expirationTime              = $expirationTime;\n        $this->sessionAttribute            = $sessionAttribute;\n    }\n\n    public function __invoke(ServerRequestInterface $request) : Token\n    {\n        $session = $request->getAttribute($this->sessionAttribute);\n\n        if (! $session instanceof SessionInterface) {\n            throw SessionAttributeNotFoundException::fromAttributeNameAndRequest($this->sessionAttribute, $request);\n        }\n\n        $timestamp = (new \\DateTime())->getTimestamp();\n\n        return (new Builder())\n            ->setIssuedAt($timestamp)\n            ->setExpiration($timestamp + $this->expirationTime)\n            ->sign($this->signer, $this->extractUniqueKeyFromSession->__invoke($session))\n            ->getToken();\n    }\n}\n"
  },
  {
    "path": "src/PSR7Csrf/TokenGeneratorInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7Csrf;\n\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Token;\nuse Psr\\Http\\Message\\ServerRequestInterface;\n\ninterface TokenGeneratorInterface\n{\n    public function __invoke(ServerRequestInterface $request) : Token;\n}\n"
  },
  {
    "path": "test/PSR7CsrfTest/CSRFCheckerMiddlewareTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7CsrfTest;\n\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\Parser;\nuse Lcobucci\\JWT\\Signer;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Server\\RequestHandlerInterface;\nuse PSR7Csrf\\CSRFCheckerMiddleware;\nuse PSR7Csrf\\Exception\\SessionAttributeNotFoundException;\nuse PSR7Csrf\\HttpMethod\\IsSafeHttpRequestInterface;\nuse PSR7Csrf\\RequestParameter\\ExtractCSRFParameterInterface;\nuse PSR7Csrf\\Session\\ExtractUniqueKeyFromSessionInterface;\nuse PSR7Sessions\\Storageless\\Session\\SessionInterface;\nuse stdClass;\n\n/**\n * @covers \\PSR7Csrf\\CSRFCheckerMiddleware\n */\nfinal class CSRFCheckerMiddlewareTest extends TestCase\n{\n    /**\n     * @var Signer\n     */\n    private $signer;\n\n    /**\n     * @var Parser\n     */\n    private $tokenParser;\n\n    /**\n     * @var IsSafeHttpRequestInterface|\\PHPUnit_Framework_MockObject_MockObject\n     */\n    private $isSafeHttpRequest;\n\n    /**\n     * @var ExtractUniqueKeyFromSessionInterface|\\PHPUnit_Framework_MockObject_MockObject\n     */\n    private $extractUniqueKeyFromSession;\n\n    /**\n     * @var ExtractCSRFParameterInterface|\\PHPUnit_Framework_MockObject_MockObject\n     */\n    private $extractCSRFParameter;\n\n    /**\n     * @var ServerRequestInterface|\\PHPUnit_Framework_MockObject_MockObject\n     */\n    private $request;\n\n    /**\n     * @var ResponseInterface|\\PHPUnit_Framework_MockObject_MockObject\n     */\n    private $response;\n\n    /**\n     * @var SessionInterface|\\PHPUnit_Framework_MockObject_MockObject\n     */\n    private $session;\n\n    /**\n     * @var string\n     */\n    private $sessionAttribute;\n\n    /**\n     * @var RequestHandlerInterface|\\PHPUnit_Framework_MockObject_MockObject\n     */\n    private $nextMiddleware;\n\n    /**\n     * @var CSRFCheckerMiddleware\n     */\n    private $middleware;\n\n    /**\n     * @var ResponseInterface\n     */\n    private $faultyResponse;\n\n    /**\n     * {@inheritDoc}\n     */\n    protected function setUp()\n    {\n        parent::setUp();\n\n        $this->signer                      = new Signer\\Hmac\\Sha256();\n        $this->tokenParser                 = new Parser();\n        $this->isSafeHttpRequest           = $this->createMock(IsSafeHttpRequestInterface::class);\n        $this->extractUniqueKeyFromSession = $this->createMock(ExtractUniqueKeyFromSessionInterface::class);\n        $this->extractCSRFParameter        = $this->createMock(ExtractCSRFParameterInterface::class);\n        $this->request                     = $this->createMock(ServerRequestInterface::class);\n        $this->response                    = $this->createMock(ResponseInterface::class);\n        $this->session                     = $this->createMock(SessionInterface::class);\n        $this->sessionAttribute            = uniqid('session', true);\n        $this->nextMiddleware              = $this->createMock(RequestHandlerInterface::class);\n        $this->faultyResponse              = $this->createMock(ResponseInterface::class);\n        $this->middleware                  = new CSRFCheckerMiddleware(\n            $this->isSafeHttpRequest,\n            $this->extractUniqueKeyFromSession,\n            $this->extractCSRFParameter,\n            $this->tokenParser,\n            $this->signer,\n            $this->sessionAttribute,\n            $this->faultyResponse\n        );\n    }\n\n    public function testWillIgnoreSafeRequestsWithNoNextMiddleware()\n    {\n        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(true);\n\n        $this\n            ->nextMiddleware\n            ->expects(self::once())\n            ->method('handle')\n            ->with($this->request)\n            ->willReturn($this->response);\n\n        self::assertSame($this->response, $this->middleware->process($this->request, $this->nextMiddleware));\n    }\n\n    public function testWillSucceedIfANonSafeRequestIsProvidedWithAValidTokenWithNextMiddleware()\n    {\n        $secret          = uniqid('secret', true);\n        $validToken      = (new Builder())\n            ->sign($this->signer, $secret)\n            ->getToken();\n\n        $this\n            ->nextMiddleware\n            ->expects(self::once())\n            ->method('handle')\n            ->with($this->request)\n            ->willReturn($this->response);\n        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);\n        $this\n            ->extractUniqueKeyFromSession\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->session)\n            ->willReturn($secret);\n        $this\n            ->extractCSRFParameter\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->request)\n            ->willReturn((string) $validToken);\n        $this\n            ->request\n            ->expects(self::any())\n            ->method('getAttribute')\n            ->with($this->sessionAttribute)\n            ->willReturn($this->session);\n\n        self::assertSame(\n            $this->response,\n            $this->middleware->process($this->request, $this->nextMiddleware)\n        );\n    }\n\n    public function testNonMatchingSignedTokensAreRejected()\n    {\n        $secret          = uniqid('secret', true);\n        $validToken      = (new Builder())\n            ->sign($this->signer, uniqid('wrongSecret', true))\n            ->getToken();\n\n        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);\n        $this\n            ->extractUniqueKeyFromSession\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->session)\n            ->willReturn($secret);\n        $this\n            ->extractCSRFParameter\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->request)\n            ->willReturn((string) $validToken);\n        $this\n            ->request\n            ->expects(self::any())\n            ->method('getAttribute')\n            ->with($this->sessionAttribute)\n            ->willReturn($this->session);\n\n        $this->assertFaultyResponse();\n    }\n\n    public function testUnsignedTokensAreRejected()\n    {\n        $secret     = uniqid('secret', true);\n        $validToken = (new Builder())->getToken();\n\n        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);\n        $this\n            ->extractUniqueKeyFromSession\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->session)\n            ->willReturn($secret);\n        $this\n            ->extractCSRFParameter\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->request)\n            ->willReturn((string) $validToken);\n        $this\n            ->request\n            ->expects(self::any())\n            ->method('getAttribute')\n            ->with($this->sessionAttribute)\n            ->willReturn($this->session);\n\n        $this->assertFaultyResponse();\n    }\n\n    public function testExpiredSignedTokensAreRejected()\n    {\n        $secret          = uniqid('secret', true);\n        $validToken      = (new Builder())\n            ->setExpiration(time() - 3600)\n            ->sign($this->signer, $secret)\n            ->getToken();\n\n        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);\n        $this\n            ->extractUniqueKeyFromSession\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->session)\n            ->willReturn($secret);\n        $this\n            ->extractCSRFParameter\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->request)\n            ->willReturn((string) $validToken);\n        $this\n            ->request\n            ->expects(self::any())\n            ->method('getAttribute')\n            ->with($this->sessionAttribute)\n            ->willReturn($this->session);\n\n        $this->assertFaultyResponse();\n    }\n\n    public function testMalformedTokensShouldBeRejected()\n    {\n        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);\n        $this\n            ->extractUniqueKeyFromSession\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->session)\n            ->willReturn(uniqid('secret', true));\n        $this\n            ->extractCSRFParameter\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->request)\n            ->willReturn('yadda yadda invalid bs');\n        $this\n            ->request\n            ->expects(self::any())\n            ->method('getAttribute')\n            ->with($this->sessionAttribute)\n            ->willReturn($this->session);\n\n        $this->assertFaultyResponse();\n    }\n\n    public function testWillFailIfARequestDoesNotIncludeASession()\n    {\n        $this->isSafeHttpRequest->expects(self::any())->method('__invoke')->with($this->request)->willReturn(false);\n        $this\n            ->extractCSRFParameter\n            ->expects(self::any())\n            ->method('__invoke')\n            ->with($this->request)\n            ->willReturn((new Builder())->getToken());\n        $this\n            ->request\n            ->expects(self::any())\n            ->method('getAttribute')\n            ->with($this->sessionAttribute)\n            ->willReturn(new stdClass());\n        $this\n            ->request\n            ->expects(self::any())\n            ->method('getAttributes')\n            ->willReturn([]);\n\n        $this->expectException(SessionAttributeNotFoundException::class);\n\n        $this->middleware->process($this->request, $this->nextMiddleware);\n    }\n\n    private function assertFaultyResponse() : void\n    {\n        $this->nextMiddleware->expects(self::never())->method('handle');\n\n        self::assertSame($this->faultyResponse, $this->middleware->process($this->request, $this->nextMiddleware));\n    }\n}\n"
  },
  {
    "path": "test/PSR7CsrfTest/Exception/InvalidExpirationTimeExceptionTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7CsrfTest\\Exception;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\TestCase;\nuse PSR7Csrf\\Exception\\ExceptionInterface;\nuse PSR7Csrf\\Exception\\InvalidExpirationTimeException;\n\n/**\n * @covers \\PSR7Csrf\\Exception\\InvalidExpirationTimeException\n */\nfinal class InvalidExpirationTimeExceptionTest extends TestCase\n{\n    public function testFromInvalidExpirationTime()\n    {\n        $exception = InvalidExpirationTimeException::fromInvalidExpirationTime(-4);\n\n        self::assertInstanceOf(InvalidExpirationTimeException::class, $exception);\n        self::assertInstanceOf(InvalidArgumentException::class, $exception);\n        self::assertInstanceOf(ExceptionInterface::class, $exception);\n        self::assertSame('The provided expiration time -4 is invalid: expected a >0 integer', $exception->getMessage());\n    }\n}\n"
  },
  {
    "path": "test/PSR7CsrfTest/Exception/InvalidRequestParameterNameExceptionTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7CsrfTest\\Exception;\n\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\TestCase;\nuse PSR7Csrf\\Exception\\ExceptionInterface;\nuse PSR7Csrf\\Exception\\InvalidRequestParameterNameException;\n\n/**\n * @covers \\PSR7Csrf\\Exception\\InvalidRequestParameterNameException\n */\nfinal class InvalidRequestParameterNameExceptionTest extends TestCase\n{\n    public function testFromEmptyRequestParameterName()\n    {\n        $exception = InvalidRequestParameterNameException::fromEmptyRequestParameterName();\n\n        self::assertInstanceOf(InvalidRequestParameterNameException::class, $exception);\n        self::assertInstanceOf(InvalidArgumentException::class, $exception);\n        self::assertInstanceOf(ExceptionInterface::class, $exception);\n        self::assertSame('The given request parameter must be a non-empty string', $exception->getMessage());\n    }\n}\n"
  },
  {
    "path": "test/PSR7CsrfTest/Exception/SessionAttributeNotFoundExceptionTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7CsrfTest\\Exception;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse PSR7Csrf\\Exception\\ExceptionInterface;\nuse PSR7Csrf\\Exception\\SessionAttributeNotFoundException;\nuse UnexpectedValueException;\n\n/**\n * @covers \\PSR7Csrf\\Exception\\SessionAttributeNotFoundException\n */\nfinal class SessionAttributeNotFoundExceptionTest extends TestCase\n{\n    public function testFromInvalidExpirationTime()\n    {\n        /* @var $request ServerRequestInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $request = $this->createMock(ServerRequestInterface::class);\n\n        $request->expects(self::any())->method('getAttributes')->willReturn(['foo' => 'bar', 'baz' => 'tab']);\n\n        $exception = SessionAttributeNotFoundException::fromAttributeNameAndRequest('foo', $request);\n\n        self::assertInstanceOf(SessionAttributeNotFoundException::class, $exception);\n        self::assertInstanceOf(UnexpectedValueException::class, $exception);\n        self::assertInstanceOf(ExceptionInterface::class, $exception);\n        self::assertSame(\n            'Provided request contains no matching session attribute \"foo\", attributes [\"foo\",\"baz\"] exist',\n            $exception->getMessage()\n        );\n    }\n}\n"
  },
  {
    "path": "test/PSR7CsrfTest/FactoryTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7CsrfTest;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Server\\RequestHandlerInterface;\nuse PSR7Csrf\\CSRFCheckerMiddleware;\nuse PSR7Csrf\\Factory;\nuse PSR7Csrf\\TokenGeneratorInterface;\n\n/**\n * @covers \\PSR7Csrf\\Factory\n */\nfinal class FactoryTest extends TestCase\n{\n    public function testCreateDefaultCSRFCheckerMiddleware()\n    {\n        $faultyResponse = $this->createMock(ResponseInterface::class);\n\n        $middleware = Factory::createDefaultCSRFCheckerMiddleware($faultyResponse);\n\n        self::assertInstanceOf(CSRFCheckerMiddleware::class, $middleware);\n\n        $request = $this->createMock(ServerRequestInterface::class);\n\n        $request\n            ->expects(self::any())\n            ->method('getMethod')\n            ->willReturn('POST');\n\n        self::assertSame(\n            $faultyResponse,\n            $middleware->process(\n                $request,\n                $this->createMock(RequestHandlerInterface::class)\n            ),\n            'Faulty http response passed to the factory is returned as part of a failed CSRF validation'\n        );\n    }\n\n    public function testCreateDefaultTokenGenerator()\n    {\n        self::assertInstanceOf(TokenGeneratorInterface::class, Factory::createDefaultTokenGenerator());\n    }\n}\n"
  },
  {
    "path": "test/PSR7CsrfTest/HttpMethod/IsSafeHttpRequestTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7CsrfTest\\HttpMethod;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Http\\Message\\RequestInterface;\nuse PSR7Csrf\\HttpMethod\\IsSafeHttpRequest;\n\n/**\n * @covers \\PSR7Csrf\\HttpMethod\\IsSafeHttpRequest\n */\nfinal class IsSafeHttpRequestTest extends TestCase\n{\n    /**\n     * @dataProvider httpMethodsProvider\n     *\n     * @param array  $safeMethods\n     * @param string $httpMethod\n     * @param bool   $expectedResult\n     */\n    public function testSafeMethods(array $safeMethods, string $httpMethod, bool $expectedResult)\n    {\n        /* @var $request RequestInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $request = $this->createMock(RequestInterface::class);\n\n        $request->expects(self::any())->method('getMethod')->willReturn($httpMethod);\n\n        self::assertSame($expectedResult, (new IsSafeHttpRequest(...$safeMethods))->__invoke($request));\n    }\n\n    public function httpMethodsProvider() : array\n    {\n        return [\n            'empty' => [\n                [],\n                'GET',\n                false,\n            ],\n            'GET only' => [\n                ['GET'],\n                'GET',\n                true,\n            ],\n            'get only' => [\n                ['get'],\n                'GET',\n                true,\n            ],\n            'GET only, matching lowercase get' => [\n                ['GET'],\n                'get',\n                true,\n            ],\n            'GET only, non-matching method' => [\n                ['GET'],\n                'PUT',\n                false,\n            ],\n            'GET, PUT only, matching method' => [\n                ['GET', 'PUT'],\n                'PUT',\n                true,\n            ],\n        ];\n    }\n\n    /**\n     * @dataProvider safeDefaultsMatchingProvider\n     *\n     * @param string $httpMethod\n     * @param bool   $expectedResult\n     */\n    public function testSafeMethodsWithDefaults(string $httpMethod, bool $expectedResult)\n    {\n        /* @var $request RequestInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $request = $this->createMock(RequestInterface::class);\n\n        $request->expects(self::any())->method('getMethod')->willReturn($httpMethod);\n\n        self::assertSame($expectedResult, IsSafeHttpRequest::fromDefaultSafeMethods()->__invoke($request));\n    }\n\n    public function safeDefaultsMatchingProvider() : array\n    {\n        return [\n            'empty' => [\n                '',\n                false,\n            ],\n            'GET' => [\n                'GET',\n                true,\n            ],\n            'get' => [\n                'get',\n                true,\n            ],\n            'HEAD' => [\n                'HEAD',\n                true,\n            ],\n            'head' => [\n                'head',\n                true,\n            ],\n            'OPTIONS' => [\n                'OPTIONS',\n                true,\n            ],\n            'options' => [\n                'options',\n                true,\n            ],\n            'DELETE' => [\n                'DELETE',\n                false,\n            ],\n            'delete' => [\n                'delete',\n                false,\n            ],\n            'POST' => [\n                'POST',\n                false,\n            ],\n            'post' => [\n                'post',\n                false,\n            ],\n            'PUT' => [\n                'PUT',\n                false,\n            ],\n            'put' => [\n                'put',\n                false,\n            ],\n            'UNKNOWN' => [\n                'UNKNOWN',\n                false,\n            ],\n            'unknown' => [\n                'unknown',\n                false,\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "test/PSR7CsrfTest/RequestParameter/ExtractCSRFParameterTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7CsrfTest\\RequestParameter;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse PSR7Csrf\\Exception\\InvalidRequestParameterNameException;\nuse PSR7Csrf\\RequestParameter\\ExtractCSRFParameter;\n\n/**\n * @covers \\PSR7Csrf\\RequestParameter\\ExtractCSRFParameter\n */\nfinal class ExtractCSRFParameterTest extends TestCase\n{\n    public function testRejectsEmptyRequestParameterName()\n    {\n        $this->expectException(InvalidRequestParameterNameException::class);\n\n        new ExtractCSRFParameter('');\n    }\n\n    /**\n     * @dataProvider requestBodyProvider\n     *\n     * @param string            $requestParameter\n     * @param null|object|array $body\n     * @param string            $expectedExtractedValue\n     *\n     * @return void\n     */\n    public function testExtraction(string $requestParameter, $body, string $expectedExtractedValue)\n    {\n        /* @var $request ServerRequestInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $request = $this->createMock(ServerRequestInterface::class);\n\n        $request->expects(self::any())->method('getParsedBody')->willReturn($body);\n\n        self::assertSame($expectedExtractedValue, (new ExtractCSRFParameter($requestParameter))->__invoke($request));\n    }\n\n    public function requestBodyProvider()\n    {\n        /** @noinspection PhpUnusedPrivateFieldInspection */\n        return [\n            'null body' => [\n                'request parameter name',\n                null,\n                '',\n            ],\n            'empty array' => [\n                'request parameter name',\n                [],\n                '',\n            ],\n            'empty object' => [\n                'request parameter name',\n                (object) [],\n                '',\n            ],\n            'array with matching parameter' => [\n                'request parameter name',\n                ['request parameter name' => 'foo'],\n                'foo',\n            ],\n            'array with matching non-string parameter' => [\n                'request parameter name',\n                ['request parameter name' => 123],\n                '',\n            ],\n            'object with matching parameter' => [\n                'request parameter name',\n                (object) ['request parameter name' => 'foo'],\n                'foo',\n            ],\n            'object with matching non-string parameter' => [\n                'request parameter name',\n                (object) ['request parameter name' => 123],\n                '',\n            ],\n            'class with private matching property' => [\n                'field',\n                new class {\n                    private $field = 'bar';\n                },\n                '',\n            ],\n            'class with protected matching property' => [\n                'field',\n                new class {\n                    protected $field = 'bar';\n                },\n                '',\n            ],\n            'class with public matching property' => [\n                'field',\n                new class {\n                    public $field = 'bar';\n                },\n                'bar',\n            ],\n            'class with public matching non-string property' => [\n                'field',\n                new class {\n                    public $field = 123;\n                },\n                '',\n            ],\n        ];\n    }\n}\n"
  },
  {
    "path": "test/PSR7CsrfTest/Session/ExtractUniqueKeyFromSessionTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7CsrfTest\\HttpMethod;\n\nuse PHPUnit\\Framework\\TestCase;\nuse PSR7Csrf\\Session\\ExtractUniqueKeyFromSession;\nuse PSR7Sessions\\Storageless\\Session\\SessionInterface;\n\n/**\n * @covers \\PSR7Csrf\\Session\\ExtractUniqueKeyFromSession\n */\nfinal class ExtractUniqueKeyFromSessionTest extends TestCase\n{\n    /**\n     * @dataProvider keysProvider\n     *\n     * @param string $key\n     */\n    public function testExtractionWithExistingKey(string $key)\n    {\n        /* @var $session SessionInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $session     = $this->createMock(SessionInterface::class);\n        $superSecret = uniqid('', true);\n\n        $session->expects(self::any())->method('get')->with($key, '')->willReturn($superSecret);\n        $session->expects(self::never())->method('set');\n\n        self::assertSame($superSecret, (new ExtractUniqueKeyFromSession($key))->__invoke($session));\n    }\n\n    /**\n     * @dataProvider keysProvider\n     *\n     * @param string $key\n     */\n    public function testExtractionWithEmptyExistingKey(string $key)\n    {\n        $extractKey = new ExtractUniqueKeyFromSession($key);\n\n        /* @var $session SessionInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $session = $this->createMock(SessionInterface::class);\n\n        $session->expects(self::any())->method('get')->with($key, '')->willReturn('');\n        $session->expects(self::exactly(2))->method('set')->with(\n            $key,\n            self::callback(function (string $secret) {\n                self::assertNotEmpty($secret);\n\n                return true;\n            })\n        );\n\n        $secretUniqueKey = $extractKey->__invoke($session);\n\n        self::assertInternalType('string', $secretUniqueKey);\n        self::assertNotEmpty($secretUniqueKey);\n\n        $anotherSecretKey = $extractKey->__invoke($session);\n\n        self::assertInternalType('string', $anotherSecretKey);\n        self::assertNotEmpty($anotherSecretKey);\n        self::assertNotEquals($secretUniqueKey, $anotherSecretKey);\n    }\n\n    /**\n     * @dataProvider keysProvider\n     *\n     * @param string $key\n     */\n    public function testExtractionWithNonStringExistingKey(string $key)\n    {\n        $extractKey = new ExtractUniqueKeyFromSession($key);\n\n        /* @var $session SessionInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $session = $this->createMock(SessionInterface::class);\n\n        $session->expects(self::any())->method('get')->with($key, '')->willReturn(123);\n        $session->expects(self::exactly(2))->method('set')->with(\n            $key,\n            self::callback(function (string $secret) {\n                self::assertNotEmpty($secret);\n\n                return true;\n            })\n        );\n\n        $secretUniqueKey = $extractKey->__invoke($session);\n\n        self::assertInternalType('string', $secretUniqueKey);\n        self::assertNotEmpty($secretUniqueKey);\n\n        $anotherSecretKey = $extractKey->__invoke($session);\n\n        self::assertInternalType('string', $anotherSecretKey);\n        self::assertNotEmpty($anotherSecretKey);\n        self::assertNotEquals($secretUniqueKey, $anotherSecretKey);\n    }\n\n    public function keysProvider() : array\n    {\n        return [\n            [''],\n            ['key'],\n            ['123'],\n            ['123 456'],\n        ];\n    }\n}\n"
  },
  {
    "path": "test/PSR7CsrfTest/TokenGeneratorTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace PSR7CsrfTest;\n\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse PSR7Csrf\\Exception\\InvalidExpirationTimeException;\nuse PSR7Csrf\\Exception\\SessionAttributeNotFoundException;\nuse PSR7Csrf\\Session\\ExtractUniqueKeyFromSessionInterface;\nuse PSR7Csrf\\TokenGenerator;\nuse PSR7Sessions\\Storageless\\Session\\SessionInterface;\nuse stdClass;\n\n/**\n * @covers \\PSR7Csrf\\TokenGenerator\n */\nfinal class TokenGeneratorTest extends TestCase\n{\n    /**\n     * @dataProvider invalidExpirationTimeProvider\n     *\n     * @param int  $invalidExpirationTime\n     * @param bool $valid\n     */\n    public function testWillRejectInvalidExpirationTime(int $invalidExpirationTime, bool $valid)\n    {\n        /* @var $signer Signer */\n        $signer                      = $this->createMock(Signer::class);\n        /* @var $extractUniqueKeyFromSession ExtractUniqueKeyFromSessionInterface */\n        $extractUniqueKeyFromSession = $this->createMock(ExtractUniqueKeyFromSessionInterface::class);\n\n        if (! $valid) {\n            $this->expectException(InvalidExpirationTimeException::class);\n        }\n\n        self::assertInstanceOf(\n            TokenGenerator::class,\n            new TokenGenerator($signer, $extractUniqueKeyFromSession, $invalidExpirationTime, 'session')\n        );\n    }\n\n    public function invalidExpirationTimeProvider() : array\n    {\n        return [\n            [100, true],\n            [1, true],\n            [0, false],\n            [-1, false],\n            [-200, false],\n        ];\n    }\n\n    /**\n     * @dataProvider validExpirationTimeProvider\n     *\n     * @param int $validExpirationTime\n     */\n    public function testWillGenerateAValidJWTToken(int $validExpirationTime)\n    {\n        $signer = new Sha256();\n        /* @var $extractUniqueKeyFromSession ExtractUniqueKeyFromSessionInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $extractUniqueKeyFromSession = $this->createMock(ExtractUniqueKeyFromSessionInterface::class);\n        /* @var $session SessionInterface */\n        $session = $this->createMock(SessionInterface::class);\n        /* @var $request ServerRequestInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $request = $this->createMock(ServerRequestInterface::class);\n        $sessionAttribute = uniqid('session', true);\n\n        $generator = new TokenGenerator($signer, $extractUniqueKeyFromSession, $validExpirationTime, $sessionAttribute);\n        $secretKey = uniqid('secretKey', true);\n\n        $request->expects(self::any())->method('getAttribute')->with($sessionAttribute)->willReturn($session);\n        $extractUniqueKeyFromSession->expects(self::any())->method('__invoke')->with($session)->willReturn($secretKey);\n\n        $token = $generator->__invoke($request);\n\n        self::assertTrue($token->verify($signer, $secretKey));\n        self::assertLessThanOrEqual(time(), $token->getClaim('iat'));\n        self::assertGreaterThan(time(), $token->getClaim('exp'));\n    }\n\n    public function validExpirationTimeProvider() : array\n    {\n        return [\n            [10],\n            [100],\n        ];\n    }\n\n    public function testWillFailIfTheSessionAttributeIsNotASession()\n    {\n        /* @var $extractUniqueKeyFromSession ExtractUniqueKeyFromSessionInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $extractUniqueKeyFromSession = $this->createMock(ExtractUniqueKeyFromSessionInterface::class);\n        /* @var $request ServerRequestInterface|\\PHPUnit_Framework_MockObject_MockObject */\n        $request = $this->createMock(ServerRequestInterface::class);\n        $sessionAttribute = uniqid('session', true);\n\n        $generator = new TokenGenerator(new Sha256(), $extractUniqueKeyFromSession, 10, $sessionAttribute);\n\n        $request->expects(self::any())->method('getAttribute')->with($sessionAttribute)->willReturn(new stdClass());\n        $request->expects(self::any())->method('getAttributes')->willReturn([]);\n\n        $this->expectException(SessionAttributeNotFoundException::class);\n\n        $generator->__invoke($request);\n    }\n}\n"
  }
]