[
  {
    "path": ".editorconfig",
    "content": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at http://editorconfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.yml]\nindent_size = 2\n\n[*.neon]\nindent_style = tab\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Remove files for archives generated using `git archive`\n.editorconfig export-ignore\n.gitattributes export-ignore\n.gitignore export-ignore\nphpunit.xml export-ignore\n.travis.yml export-ignore\ntests export-ignore\n.phpstan.neon export-ignore\n.psalm.xml export-ignore\n.github export-ignore\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non: [push, pull_request]\n\njobs:\n  testsuite:\n    runs-on: ubuntu-18.04\n    strategy:\n      fail-fast: false\n      matrix:\n        php-version: ['7.4', '8.0']\n        composer-opts: ['']\n        include:\n          - php-version: '7.2'\n            composer-opts: '--prefer-lowest'\n\n    steps:\n    - uses: actions/checkout@v1\n      with:\n        fetch-depth: 1\n\n    - name: Setup PHP\n      uses: shivammathur/setup-php@v2\n      with:\n        php-version: ${{ matrix.php-version }}\n        extension-csv: mbstring, intl\n        coverage: pcov\n\n    - name: Composer install\n      run: |\n        composer update ${{ matrix.composer-opts }}\n\n    - name: Run PHPUnit\n      run: |\n        if [[ ${{ matrix.php-version }} == '7.4' ]]; then\n          vendor/bin/phpunit --coverage-clover=coverage.xml\n        else\n          vendor/bin/phpunit\n        fi\n\n    - name: Code Coverage Report\n      if: matrix.php-version == '7.4'\n      uses: codecov/codecov-action@v1\n\n  cs-stan:\n      name: Coding Standard & Static Analysis\n      runs-on: ubuntu-18.04\n\n      steps:\n      - uses: actions/checkout@v1\n        with:\n          fetch-depth: 1\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: '7.4'\n          extension-csv: mbstring, intl\n          coverage: none\n          tools: cs2pr, psalm, phpstan\n\n      - name: Composer Install\n        run: composer require cakephp/cakephp-codesniffer:^4.1\n\n      - name: Run phpcs\n        run: vendor/bin/phpcs --report=checkstyle -q --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/ | cs2pr\n\n      - name: Run psalm\n        if: success() || failure()\n        run: psalm --output-format=github\n\n      - name: Run phpstan\n        if: success() || failure()\n        run: phpstan analyse\n"
  },
  {
    "path": ".gitignore",
    "content": "/composer.lock\n/phpunit.xml\n/vendor\n.phpunit.result.cache\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-Present ADmad\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies 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\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# CakePHP JWT Authenticate plugin\n\n[![Build Status](https://img.shields.io/github/workflow/status/ADmad/cakephp-jwt-auth/CI/master?style=flat-square)](https://github.com/ADmad/cakephp-jwt-auth/actions?query=workflow%3ACI+branch%3Amaster)\n[![Coverage Status](https://img.shields.io/codecov/c/github/ADmad/cakephp-jwt-auth.svg?style=flat-square)](https://codecov.io/github/ADmad/cakephp-jwt-auth)\n[![Total Downloads](https://img.shields.io/packagist/dt/ADmad/cakephp-jwt-auth.svg?style=flat-square)](https://packagist.org/packages/ADmad/cakephp-jwt-auth)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.txt)\n\nPlugin containing AuthComponent's authenticate class for authenticating using\n[JSON Web Tokens](http://jwt.io/). You can read about JSON Web Token\nspecification in detail [here](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-27).\n\n## Installation\n\n```sh\ncomposer require admad/cakephp-jwt-auth\n```\n\n## Usage\n\nLoad the plugin using Cake's console:\n\n```sh\n./bin/cake plugin load ADmad/JwtAuth\n```\n\n## Configuration:\n\nSetup `AuthComponent`:\n\n```php\n    // In your controller, for e.g. src/Api/AppController.php\n    public function initialize(): void\n    {\n        parent::initialize();\n\n        $this->loadComponent('Auth', [\n            'storage' => 'Memory',\n            'authenticate' => [\n                'ADmad/JwtAuth.Jwt' => [\n                    'userModel' => 'Users',\n                    'fields' => [\n                        'username' => 'id'\n                    ],\n\n                    'parameter' => 'token',\n\n                    // Boolean indicating whether the \"sub\" claim of JWT payload\n                    // should be used to query the Users model and get user info.\n                    // If set to `false` JWT's payload is directly returned.\n                    'queryDatasource' => true,\n                ]\n            ],\n\n            'unauthorizedRedirect' => false,\n            'checkAuthIn' => 'Controller.initialize',\n\n            // If you don't have a login action in your application, set\n            // 'loginAction' to empty string to prevent getting a MissingRouteException.\n            'loginAction' => '',\n        ]);\n    }\n```\n\n## Working\n\nThe authentication class checks for the token in two locations:\n\n- `HTTP_AUTHORIZATION` environment variable:\n\n  It first checks if token is passed using `Authorization` request header.\n  The value should be of form `Bearer <token>`. The `Authorization` header name\n  and token prefix `Bearer` can be customized using options `header` and `prefix`\n  respectively.\n\n- The query string variable specified using `parameter` config:\n\n  Next it checks if the token is present in query string. The default variable\n  name is `token` and can be customzied by using the `parameter` config shown\n  above.\n\n### Known Issue\n  Some servers don't populate `$_SERVER['HTTP_AUTHORIZATION']` when\n  `Authorization` header is set. So it's up to you to ensure that either\n  `$_SERVER['HTTP_AUTHORIZATION']` or `$_ENV['HTTP_AUTHORIZATION']` is set.\n\n  For e.g. for apache you could use the following:\n\n  ```\n  RewriteEngine On\n  RewriteCond %{HTTP:Authorization} ^(.*)\n  RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]\n  ```\n\n  or\n\n  ```\n  SetEnvIf Authorization \"(.*)\" HTTP_AUTHORIZATION=$1\n  ```\n\n## Token Generation\n\nYou can use `\\Firebase\\JWT\\JWT::encode()` of the [firebase/php-jwt](https://github.com/firebase/php-jwt)\nlib, which this plugin depends on, to generate tokens.\n\n**The payload must have the \"sub\" (subject) claim whose value is used to query the\nUsers model and find record matching the \"id\" field.**\n\nIdeally you should also specify the token expiry time using `exp` claim.\n\nYou can set the `queryDatasource` option to `false` to directly return the token's\npayload as user info without querying datasource for matching user record.\n\n## Further reading\n\nFor an end to end usage example check out [this](http://www.bravo-kernel.com/2015/04/how-to-add-jwt-authentication-to-a-cakephp-3-rest-api/) blog post by Bravo Kernel.\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"admad/cakephp-jwt-auth\",\n    \"type\": \"cakephp-plugin\",\n    \"description\": \"CakePHP plugin for authenticating using JSON Web Tokens\",\n    \"keywords\": [\n        \"cakephp\",\n        \"authenticate\",\n        \"authentication\",\n        \"jwt\"\n    ],\n    \"homepage\": \"http://github.com/ADmad/cakephp-jwt-auth\",\n    \"authors\": [\n        {\n            \"name\":\"ADmad\",\n            \"role\":\"Author\",\n            \"homepage\":\"https://github.com/ADmad\"\n        }\n    ],\n    \"license\": \"MIT\",\n    \"support\": {\n        \"source\":\"https://github.com/ADmad/cakephp-jwt-auth\",\n        \"issues\":\"https://github.com/ADmad/cakephp-jwt-auth/issues\"\n    },\n    \"require\": {\n        \"cakephp/cakephp\": \"^4.0\",\n        \"firebase/php-jwt\": \"^5.0\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"~8.5.0\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"ADmad\\\\JwtAuth\\\\\": \"src\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"ADmad\\\\JwtAuth\\\\Test\\\\\": \"tests\"\n        }\n    }\n}\n"
  },
  {
    "path": "phpstan.neon",
    "content": "parameters:\n\tlevel: 6\n\tcheckMissingIterableValueType: false\n\tpaths:\n\t\t- src/\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    colors=\"true\"\n    processIsolation=\"false\"\n    stopOnFailure=\"false\"\n    bootstrap=\"./tests/bootstrap.php\"\n    >\n    <php>\n        <ini name=\"memory_limit\" value=\"-1\"/>\n    </php>\n\n    <!-- Add any additional test suites you want to run here -->\n    <testsuites>\n        <testsuite name=\"CakePHP JWT Auth Tests\">\n            <directory>./tests/TestCase</directory>\n        </testsuite>\n        <!-- Add plugin test suites here. -->\n    </testsuites>\n\n    <!-- Setup a listener for fixtures -->\n    <listeners>\n        <listener\n        class=\"\\Cake\\TestSuite\\Fixture\\FixtureInjector\"\n        file=\"./vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php\">\n            <arguments>\n                <object class=\"\\Cake\\TestSuite\\Fixture\\FixtureManager\" />\n            </arguments>\n        </listener>\n    </listeners>\n\n    <filter>\n        <whitelist>\n            <directory suffix=\".php\">./src/</directory>\n        </whitelist>\n    </filter>\n</phpunit>\n"
  },
  {
    "path": "psalm.xml",
    "content": "<?xml version=\"1.0\"?>\n<psalm\n    errorLevel=\"2\"\n    resolveFromConfigFile=\"true\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns=\"https://getpsalm.org/schema/config\"\n    xsi:schemaLocation=\"https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd\"\n>\n    <projectFiles>\n        <directory name=\"src\" />\n        <ignoreFiles>\n            <directory name=\"vendor\" />\n        </ignoreFiles>\n    </projectFiles>\n    <issueHandlers>\n        <PropertyNotSetInConstructor errorLevel=\"suppress\"/>\n    </issueHandlers>\n</psalm>\n"
  },
  {
    "path": "src/Auth/JwtAuthenticate.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace ADmad\\JwtAuth\\Auth;\n\nuse Cake\\Auth\\BaseAuthenticate;\nuse Cake\\Controller\\ComponentRegistry;\nuse Cake\\Core\\Configure;\nuse Cake\\Http\\Exception\\UnauthorizedException;\nuse Cake\\Http\\Response;\nuse Cake\\Http\\ServerRequest;\nuse Cake\\Utility\\Security;\nuse Exception;\nuse Firebase\\JWT\\JWT;\n\n/**\n * An authentication adapter for authenticating using JSON Web Tokens.\n *\n * ```\n *  $this->Auth->config('authenticate', [\n *      'ADmad/JwtAuth.Jwt' => [\n *          'parameter' => 'token',\n *          'userModel' => 'Users',\n *          'fields' => [\n *              'username' => 'id'\n *          ],\n *      ]\n *  ]);\n * ```\n *\n * @copyright 2015-Present ADmad\n * @license MIT\n * @see http://jwt.io\n * @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token\n */\nclass JwtAuthenticate extends BaseAuthenticate\n{\n    /**\n     * Parsed token.\n     *\n     * @var string|null\n     */\n    protected $_token;\n\n    /**\n     * Payload data.\n     *\n     * @var object|null\n     */\n    protected $_payload;\n\n    /**\n     * Exception.\n     *\n     * @var \\Throwable|null\n     */\n    protected $_error;\n\n    /**\n     * Constructor.\n     *\n     * Settings for this object.\n     *\n     * - `cookie` - Cookie name to check. Defaults to `false`.\n     * - `header` - Header name to check. Defaults to `'authorization'`.\n     * - `prefix` - Token prefix. Defaults to `'bearer'`.\n     * - `parameter` - The url parameter name of the token. Defaults to `token`.\n     *   First $_SERVER['HTTP_AUTHORIZATION'] is checked for token value.\n     *   Its value should be of form \"Bearer <token>\". If empty this query string\n     *   paramater is checked.\n     * - `allowedAlgs` - List of supported verification algorithms.\n     *   Defaults to ['HS256']. See API of JWT::decode() for more info.\n     * - `queryDatasource` - Boolean indicating whether the `sub` claim of JWT\n     *   token should be used to query the user model and get user record. If\n     *   set to `false` JWT's payload is directly retured. Defaults to `true`.\n     * - `userModel` - The model name of users, defaults to `Users`.\n     * - `fields` - Key `username` denotes the identifier field for fetching user\n     *   record. The `sub` claim of JWT must contain identifier value.\n     *   Defaults to ['username' => 'id'].\n     * - `finder` - Finder method.\n     * - `unauthenticatedException` - Fully namespaced exception name. Exception to\n     *   throw if authentication fails. Set to false to do nothing.\n     *   Defaults to '\\Cake\\Http\\Exception\\UnauthorizedException'.\n     * - `key` - The key, or map of keys used to decode JWT. If not set, value\n     *   of Security::salt() will be used.\n     *\n     * @param \\Cake\\Controller\\ComponentRegistry $registry The Component registry\n     *   used on this request.\n     * @param array $config Array of config to use.\n     */\n    public function __construct(ComponentRegistry $registry, array $config)\n    {\n        $defaultConfig = [\n            'cookie' => false,\n            'header' => 'authorization',\n            'prefix' => 'bearer',\n            'parameter' => 'token',\n            'queryDatasource' => true,\n            'fields' => ['username' => 'id'],\n            'unauthenticatedException' => UnauthorizedException::class,\n            'key' => null,\n        ];\n\n        $this->setConfig($defaultConfig);\n\n        if (empty($config['allowedAlgs'])) {\n            $config['allowedAlgs'] = ['HS256'];\n        }\n\n        parent::__construct($registry, $config);\n    }\n\n    /**\n     * Get user record based on info available in JWT.\n     *\n     * @param \\Cake\\Http\\ServerRequest $request The request object.\n     * @param \\Cake\\Http\\Response $response Response object.\n     * @return false|array User record array or false on failure.\n     */\n    public function authenticate(ServerRequest $request, Response $response)\n    {\n        return $this->getUser($request);\n    }\n\n    /**\n     * Get user record based on info available in JWT.\n     *\n     * @param \\Cake\\Http\\ServerRequest $request Request object.\n     * @return false|array User record array or false on failure.\n     */\n    public function getUser(ServerRequest $request)\n    {\n        $payload = $this->getPayload($request);\n\n        if (empty($payload)) {\n            return false;\n        }\n\n        if (!$this->_config['queryDatasource']) {\n            return json_decode(json_encode($payload), true);\n        }\n\n        if (!isset($payload->sub)) {\n            return false;\n        }\n\n        $user = $this->_findUser((string)$payload->sub);\n        if (!$user) {\n            return false;\n        }\n\n        unset($user[$this->_config['fields']['password']]);\n\n        return $user;\n    }\n\n    /**\n     * Get payload data.\n     *\n     * @param \\Cake\\Http\\ServerRequest|null $request Request instance or null\n     * @return object|null Payload object on success, null on failurec\n     */\n    public function getPayload(?ServerRequest $request = null)\n    {\n        if (!$request) {\n            return $this->_payload;\n        }\n\n        $payload = null;\n\n        $token = $this->getToken($request);\n        if ($token) {\n            $payload = $this->_decode($token);\n        }\n\n        return $this->_payload = $payload;\n    }\n\n    /**\n     * Get token from header or query string.\n     *\n     * @param \\Cake\\Http\\ServerRequest|null $request Request object.\n     * @return string|null Token string if found else null.\n     */\n    public function getToken(?ServerRequest $request = null)\n    {\n        $config = $this->_config;\n\n        if ($request === null) {\n            return $this->_token;\n        }\n\n        $header = $request->getHeaderLine($config['header']);\n        if ($header && stripos($header, $config['prefix']) === 0) {\n            return $this->_token = str_ireplace($config['prefix'] . ' ', '', $header);\n        }\n\n        if (!empty($this->_config['cookie'])) {\n            $token = $request->getCookie($this->_config['cookie']);\n            if ($token !== null) {\n                /** @psalm-suppress PossiblyInvalidCast */\n                $token = (string)$token;\n            }\n\n            return $this->_token = $token;\n        }\n\n        if (!empty($this->_config['parameter'])) {\n            $token = $request->getQuery($this->_config['parameter']);\n            if ($token !== null) {\n                /** @psalm-suppress PossiblyInvalidCast */\n                $token = (string)$token;\n            }\n\n            return $this->_token = $token;\n        }\n\n        return $this->_token;\n    }\n\n    /**\n     * Decode JWT token.\n     *\n     * @param string $token JWT token to decode.\n     * @return object|null The JWT's payload as a PHP object, null on failure.\n     */\n    protected function _decode(string $token)\n    {\n        $config = $this->_config;\n        try {\n            $payload = JWT::decode(\n                $token,\n                $config['key'] ?: Security::getSalt(),\n                $config['allowedAlgs']\n            );\n\n            return $payload;\n        } catch (Exception $e) {\n            if (Configure::read('debug')) {\n                throw $e;\n            }\n            $this->_error = $e;\n        }\n\n        return null;\n    }\n\n    /**\n     * Handles an unauthenticated access attempt. Depending on value of config\n     * `unauthenticatedException` either throws the specified exception or returns\n     * null.\n     *\n     * @param \\Cake\\Http\\ServerRequest $request A request object.\n     * @param \\Cake\\Http\\Response $response A response object.\n     * @throws \\Cake\\Http\\Exception\\UnauthorizedException Or any other\n     *   configured exception.\n     * @return void\n     */\n    public function unauthenticated(ServerRequest $request, Response $response)\n    {\n        if (!$this->_config['unauthenticatedException']) {\n            return;\n        }\n\n        $message = $this->_error\n            ? $this->_error->getMessage()\n            : $this->_registry->get('Auth')->getConfig('authError');\n\n        /** @var \\Throwable $exception */\n        $exception = new $this->_config['unauthenticatedException']($message);\n        throw $exception;\n    }\n}\n"
  },
  {
    "path": "tests/Fixture/GroupsFixture.php",
    "content": "<?php\nnamespace ADmad\\JwtAuth\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\n/**\n * Class GroupsFixture.\n */\nclass GroupsFixture extends TestFixture\n{\n    /**\n     * fields property.\n     *\n     * @var array\n     */\n    public $fields = [\n        'id' => ['type' => 'integer'],\n        'title' => ['type' => 'string'],\n        '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],\n    ];\n\n    /**\n     * records property.\n     *\n     * @var array\n     */\n    public $records = [\n        ['title' => 'admin'],\n        ['title' => 'moderator'],\n    ];\n}\n"
  },
  {
    "path": "tests/Fixture/UsersFixture.php",
    "content": "<?php\nnamespace ADmad\\JwtAuth\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\nclass UsersFixture extends TestFixture\n{\n    /**\n     * fields property.\n     *\n     * @var array\n     */\n    public $fields = [\n        'id' => ['type' => 'integer'],\n        'group_id' => ['type' => 'integer', 'null' => false],\n        'user_name' => ['type' => 'string', 'null' => false],\n        'email' => ['type' => 'string', 'null' => false],\n        'password' => ['type' => 'string', 'null' => false],\n        'created' => 'datetime',\n        'updated' => 'datetime',\n        '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],\n    ];\n\n    /**\n     * records property.\n     *\n     * @var array\n     */\n    public $records = [\n        [\n            'group_id' => 1, 'user_name' => 'admad',\n            'email' => 'admad@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99',\n            'created' => '2014-03-17 01:18:23', 'updated' => '2014-03-17 01:20:31',\n        ],\n        [\n            'group_id' => 2, 'user_name' => 'mark',\n            'email' => 'mark@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99',\n            'created' => '2014-03-17 01:16:23', 'updated' => '2014-03-17 01:18:31',\n        ],\n        [\n            'group_id' => 2, 'user_name' => 'jose',\n            'email' => 'jose@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99',\n            'created' => '2014-03-17 01:20:23', 'updated' => '2014-03-17 01:22:31',\n        ],\n    ];\n}\n"
  },
  {
    "path": "tests/TestCase/Auth/JwtAuthenticateTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace ADmad\\JwtAuth\\Auth\\Test\\TestCase\\Auth;\n\nuse ADmad\\JwtAuth\\Auth\\JwtAuthenticate;\nuse Cake\\Controller\\ComponentRegistry;\nuse Cake\\Core\\Configure;\nuse Cake\\Http\\Exception\\UnauthorizedException;\nuse Cake\\Http\\Response;\nuse Cake\\Http\\ServerRequest;\nuse Cake\\I18n\\FrozenTime;\nuse Cake\\I18n\\Time;\nuse Cake\\TestSuite\\TestCase;\nuse Cake\\Utility\\Security;\nuse DomainException;\nuse Firebase\\JWT\\JWT;\n\n/**\n * Test case for JwtAuthentication.\n */\nclass JwtAuthenticateTest extends TestCase\n{\n    protected $fixtures = [\n        'plugin.ADmad\\JwtAuth.Users',\n        'plugin.ADmad\\JwtAuth.Groups',\n    ];\n\n    /**\n     * setup.\n     *\n     * @return void\n     */\n    public function setUp(): void\n    {\n        parent::setUp();\n\n        Security::setSalt('secret-key');\n\n        $this->Registry = new ComponentRegistry();\n        $this->auth = new JwtAuthenticate($this->Registry, [\n            'userModel' => 'Users',\n        ]);\n        $this->Registry->Auth = $this->auth;\n\n        $this->token = JWT::encode(['sub' => 1], Security::getSalt());\n\n        $this->response = $this->getMockBuilder(Response::class)\n            ->getMock();\n    }\n\n    /**\n     * testConfig.\n     *\n     * @return void\n     */\n    public function testConfig()\n    {\n        $auth = new JwtAuthenticate($this->Registry, []);\n        $this->assertEquals(['HS256'], $auth->getConfig('allowedAlgs'));\n\n        $auth = new JwtAuthenticate($this->Registry, [\n            'allowedAlgs' => ['RS256'],\n        ]);\n        $this->assertEquals(['RS256'], $auth->getConfig('allowedAlgs'));\n    }\n\n    /**\n     * test authenticate token as query parameter.\n     *\n     * @return void\n     */\n    public function testAuthenticateTokenParameter()\n    {\n        $request = new ServerRequest();\n\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n\n        $expected = [\n            'id' => 1,\n            'group_id' => 1,\n            'user_name' => 'admad',\n            'email' => 'admad@example.com',\n            'created' => new FrozenTime('2014-03-17 01:18:23'),\n            'updated' => new FrozenTime('2014-03-17 01:20:31'),\n        ];\n        $request = new ServerRequest(['url' => 'posts/index?token=' . $this->token]);\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertEquals($expected, $result);\n\n        $this->auth->setConfig('parameter', 'tokenname');\n        $request = new ServerRequest(['url' => 'posts/index?tokenname=' . $this->token]);\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertEquals($expected, $result);\n\n        $request = new ServerRequest(['url' => 'posts/index?wrongtoken=' . $this->token]);\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n    }\n\n    /**\n     * test authenticate token as request header.\n     *\n     * @return void\n     */\n    public function testAuthenticateTokenHeader()\n    {\n        $request = new ServerRequest();\n\n        $expected = [\n            'id' => 1,\n            'group_id' => 1,\n            'user_name' => 'admad',\n            'email' => 'admad@example.com',\n            'created' => new FrozenTime('2014-03-17 01:18:23'),\n            'updated' => new FrozenTime('2014-03-17 01:20:31'),\n        ];\n        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer ' . $this->token);\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertEquals($expected, $result);\n\n        $request = $request->withEnv('HTTP_AUTHORIZATION', 'WrongBearer ' . $this->token);\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n\n        $this->expectException('UnexpectedValueException');\n        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer foobar');\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n    }\n\n    /**\n     * test authenticate no token present in header \"parameter\" option disabled.\n     *\n     * @return void\n     */\n    public function testAuthenticateNoHeaderWithParameterDisabled()\n    {\n        $request = new ServerRequest();\n\n        $this->auth = new JwtAuthenticate($this->Registry, [\n            'userModel' => 'Users',\n            'parameter' => false,\n        ]);\n\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n\n        $request = new ServerRequest(['url' => 'posts/index?token=' . $this->token]);\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n    }\n\n    /**\n     * test returning payload without querying database.\n     *\n     * @return void\n     */\n    public function testQueryDatasourceFalse()\n    {\n        $expected = [\n                'id' => 99,\n                'username' => 'ADmad',\n                'group' => ['name' => 'admin'],\n        ];\n        $token = JWT::encode($expected, Security::getSalt());\n        $this->auth->setConfig('queryDatasource', false);\n\n        $request = new ServerRequest();\n        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer ' . $token);\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertEquals($expected, $result);\n\n        $request = new ServerRequest(['url' => 'posts/index?token=' . $token]);\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertEquals($expected, $result);\n    }\n\n    /**\n     * test for valid token but no matching user found in db.\n     *\n     * @return void\n     */\n    public function testWithValidTokenButNoUserInDb()\n    {\n        $token = JWT::encode(['id' => 4], Security::getSalt());\n\n        $request = new ServerRequest();\n        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer ' . $token);\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n\n        $request = new ServerRequest(['url' => 'posts/index?token=' . $token]);\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n    }\n\n    /**\n     * Test that authenticated() always returns false.\n     *\n     * @return void\n     */\n    public function testAuthenticated()\n    {\n        $this->assertFalse($this->auth->authenticate(new ServerRequest(), $this->response));\n    }\n\n    /**\n     * test that with debug off for invalid token exception from JWT::decode()\n     * is re-thrown.\n     */\n    public function testExceptionForInvalidToken()\n    {\n        $this->expectException(DomainException::class);\n\n        $request = new ServerRequest();\n        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer this.is.invalid');\n\n        $this->auth->getUser($request, $this->response);\n    }\n\n    /**\n     * testUnauthenticated\n     */\n    public function testUnauthenticated()\n    {\n        $this->Registry->Auth->setConfig('authError', 'Auth error');\n\n        $exceptionClass = UnauthorizedException::class;\n        if (!class_exists(UnauthorizedException::class)) {\n            $exceptionClass = 'Cake\\Network\\Exception\\UnauthorizedException';\n        }\n        $this->expectException($exceptionClass);\n        $this->expectExceptionMessage('Auth error');\n\n        $this->auth->unauthenticated(new ServerRequest(), $this->response);\n    }\n\n    /**\n     * test unauthenticated() doesn't throw exception is config `unauthenticatedException`\n     * is set to falsey value.\n     */\n    public function testUnauthenticatedNoException()\n    {\n        $this->auth->setConfig('unauthenticatedException', false);\n        $this->assertNull($this->auth->unauthenticated(new ServerRequest(), $this->response));\n    }\n\n    /**\n     * test that getUser() returns false instead of throwing exception with\n     * invalid token when debug is off.\n     *\n     * @return void\n     */\n    public function testWithInvalidToken()\n    {\n        Configure::write('debug', false);\n        $request = new ServerRequest();\n\n        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer this.is.invalid');\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n    }\n\n    /**\n     * test using custom key for decoding jwt.\n     *\n     * @return void\n     */\n    public function testCustomKey()\n    {\n        $key = 'my-custom-key';\n        $auth = new JwtAuthenticate($this->Registry, [\n            'key' => $key,\n            'queryDatasource' => false,\n        ]);\n\n        $payload = ['sub' => 100];\n        $token = JWT::encode($payload, $key);\n\n        $request = new ServerRequest();\n        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer ' . $token);\n        $result = $auth->getUser($request, $this->response);\n        $this->assertEquals($payload, $result);\n\n        $request = new ServerRequest(['url' => '/posts/index?token=' . $token]);\n        $result = $auth->getUser($request, $this->response);\n        $this->assertEquals($payload, $result);\n    }\n\n    /**\n     * test authenticate token as cookie.\n     *\n     * @return void\n     */\n    public function testAuthenticateCookie()\n    {\n        $request = new ServerRequest();\n\n        $this->auth = new JwtAuthenticate($this->Registry, [\n            'userModel' => 'Users',\n            'cookie' => 'jwt',\n        ]);\n\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n\n        $expected = [\n            'id' => 1,\n            'group_id' => 1,\n            'user_name' => 'admad',\n            'email' => 'admad@example.com',\n            'created' => new Time('2014-03-17 01:18:23'),\n            'updated' => new Time('2014-03-17 01:20:31'),\n        ];\n\n        $request = new ServerRequest([\n            'url' => 'posts/index',\n            'cookies' => ['jwt' => $this->token],\n        ]);\n\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertEquals($expected, $result);\n\n        $this->auth->setConfig('cookie', 'tokenname');\n        $request = new ServerRequest([\n            'url' => 'posts/index',\n            'cookies' => ['tokenname' => $this->token],\n        ]);\n\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertEquals($expected, $result);\n\n        $request = new ServerRequest([\n            'url' => 'posts/index',\n            'cookies' => ['wrongtoken' => $this->token],\n        ]);\n\n        $result = $this->auth->getUser($request, $this->response);\n        $this->assertFalse($result);\n    }\n}\n"
  },
  {
    "path": "tests/bootstrap.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/*\n * Test suite bootstrap\n *\n * This function is used to find the location of CakePHP whether CakePHP\n * has been installed as a dependency of the plugin, or the plugin is itself\n * installed as a dependency of an application.\n */\n$findRoot = function ($root) {\n    do {\n        $lastRoot = $root;\n        $root = dirname($root);\n        if (is_dir($root . '/vendor/cakephp/cakephp')) {\n            return $root;\n        }\n    } while ($root !== $lastRoot);\n    throw new Exception('Cannot find the root of the application, unable to run tests');\n};\n$root = $findRoot(__FILE__);\nunset($findRoot);\nchdir($root);\nrequire $root . '/vendor/cakephp/cakephp/tests/bootstrap.php';\n"
  }
]