[
  {
    "path": ".composer-require-checker.config.json",
    "content": "{\n  \"symbol-whitelist\" : [\n    \"NoDiscard\"\n  ],\n  \"php-core-extensions\" : [\n    \"Core\",\n    \"date\",\n    \"json\",\n    \"hash\",\n    \"SPL\",\n    \"standard\"\n  ]\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "/docs                                     export-ignore\n/tests                                    export-ignore\n/.gitattributes                           export-ignore\n/.github                                  export-ignore\n/.gitignore                               export-ignore\n/.composer-require-checker.config.json    export-ignore\n/*.yml                                    export-ignore\n/CONTRIBUTING.md                          export-ignore\n/*.dist                                   export-ignore\n/phpbench.json                            export-ignore\n/composer.lock                            export-ignore\n/README.md                                export-ignore\n/Makefile                                 export-ignore\n/.roave-backward-compatibility-check.json export-ignore\n/.readthedocs.yaml                        export-ignore\n/renovate.json                            export-ignore\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to lcobucci/jwt\n\nFirst off, thanks for taking the time to contribute!\n\n## Reporting issues\n\nWe accept bug and feature requests via issues created [here](https://github.com/lcobucci/jwt/issues).\n\n### Prior to submitting a bug report\n\n- **Always search the issue or pull request list first** - The odds are good that if you've found a problem, someone else has found it, too;\n- **Always try the [master](https://github.com/lcobucci/jwt) branch** - to see if the reported bug has not already been fixed.\n\n## Pull Requests\n\nWe accept contributions via pull requests [here](https://github.com/lcobucci/jwt/pulls).\n\n- Follow [PSR-2 coding standards](http://www.php-fig.org/psr/psr-2);\n- Follow [PSR-4 autoloading standards](http://www.php-fig.org/psr/psr-4);\n- Follow [semver](http://semver.org);\n- Add tests (everything MUST be well tested);\n- Improve documentation (don't forget to update README.md);\n- Create topic branches (don't send a PR from your master);\n- One pull request per feature;\n- Send coherent history by rebasing your work before submitting;\n\n### Running tests locally\n\nWe provide a GNU-Make configuration that allows you to run the CI checks locally with a single command: `make`.\n\n### Branches\n\n- **5.0.x**: used to the next major release (new features that breaks BC)\n- **4.4.x**: used to develop migration path for the next version\n- **4.3.x**: used to fix bugs\n- **4.2.x**: unmaintained\n- **4.1.x**: unmaintained\n- **4.0.x**: unmaintained\n- **3.4.x**: security issues only\n- **3.3**: unmaintained\n- **3.2**: unmaintained\n- **3.1**: unmaintained\n- **3.0**: unmaintained\n- **2.1**: unmaintained\n\n**Thank you and happy coding!**\n\n@lcobucci\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: lcobucci\npatreon: lcobucci\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version     | Supported          |\n| ----------- | ------------------ |\n| 4.2.x-dev   | :white_check_mark: |\n| 4.1.x       | :white_check_mark: |\n| 4.0.x       | :white_check_mark: |\n| 3.4         | :white_check_mark: |\n| < 3.4       | :x:                |\n\n## Reporting a Vulnerability\n\nIn case of a security vulnerability, please file an issue. It's shall be\nprioritised and ported back/forth to all supported versions.\n\n"
  },
  {
    "path": ".github/workflows/backwards-compatibility.yml",
    "content": "name: \"Backwards compatibility check\"\n\non:\n  pull_request:\n\njobs:\n  bc-check:\n    name: \"Backwards compatibility check\"\n\n    runs-on: \"ubuntu-latest\"\n\n    steps:\n      - name: \"Checkout\"\n        uses: \"actions/checkout@v6.0.2\"\n        with:\n          fetch-depth: 0\n\n      - name: \"Install PHP\"\n        uses: \"shivammathur/setup-php@2.37.0\"\n        with:\n          php-version: \"8.3\"\n          ini-values: memory_limit=-1\n          tools: composer:v2, cs2pr\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Get composer cache directory\n        id: composer-cache\n        run: echo \"composer_cache_dir=$(composer global config cache-files-dir)\" >> $GITHUB_OUTPUT\n\n      - name: \"Cache dependencies\"\n        uses: \"actions/cache@v5.0.4\"\n        with:\n          path: ${{ steps.composer-cache.outputs.composer_cache_dir }}\n          key: \"php-8.3-bc-break-check-${{ hashFiles('.github/workflows/backwards-compatibility.yml') }}\"\n          restore-keys: \"php-8.3-bc-break-check-\"\n      - name: \"Install dependencies\"\n        run: composer global require roave/backward-compatibility-check\n\n      - name: \"BC Check\"\n        run: |\n          ~/.composer/vendor/bin/roave-backward-compatibility-check --from=${{ github.event.pull_request.base.sha }} --format=github-actions\n"
  },
  {
    "path": ".github/workflows/benchmarks.yml",
    "content": "name: \"Benchmarks\"\n\non:\n  pull_request:\n  push:\n\njobs:\n  benchmarks:\n    name: \"Run benchmarks\"\n\n    runs-on: ${{ matrix.operating-system }}\n\n    strategy:\n      matrix:\n        dependencies:\n          - \"locked\"\n        php-version:\n          - \"8.3\"\n        operating-system:\n          - \"ubuntu-latest\"\n\n    steps:\n      - name: \"Checkout\"\n        uses: \"actions/checkout@v6.0.2\"\n\n      - name: \"Install PHP\"\n        uses: \"shivammathur/setup-php@2.37.0\"\n        with:\n          coverage: \"none\"\n          php-version: \"${{ matrix.php-version }}\"\n          ini-values: memory_limit=-1\n          tools: composer:v2, cs2pr\n\n      - name: \"Install dependencies\"\n        uses: \"ramsey/composer-install@4.0.0\"\n        with:\n          dependency-versions: \"${{ matrix.dependencies }}\"\n\n      - name: \"PhpBench\"\n        run: \"make phpbench\"\n"
  },
  {
    "path": ".github/workflows/coding-standards.yml",
    "content": "name: \"Check Coding Standards\"\n\non:\n  pull_request:\n  push:\n\njobs:\n  coding-standards:\n    name: \"Check Coding Standards\"\n\n    runs-on: ${{ matrix.operating-system }}\n\n    strategy:\n      matrix:\n        dependencies:\n          - \"locked\"\n        php-version:\n          - \"8.3\"\n        operating-system:\n          - \"ubuntu-latest\"\n\n    steps:\n      - name: \"Checkout\"\n        uses: \"actions/checkout@v6.0.2\"\n\n      - name: \"Install PHP\"\n        uses: \"shivammathur/setup-php@2.37.0\"\n        with:\n          coverage: \"none\"\n          php-version: \"${{ matrix.php-version }}\"\n          ini-values: memory_limit=-1\n          tools: composer:v2, cs2pr\n\n      - name: \"Install dependencies\"\n        uses: \"ramsey/composer-install@4.0.0\"\n        with:\n          dependency-versions: \"${{ matrix.dependencies }}\"\n\n      - name: \"Coding Standard\"\n        run: \"make phpcs PHPCS_FLAGS='-q --report=checkstyle | cs2pr'\"\n"
  },
  {
    "path": ".github/workflows/composer-json-lint.yml",
    "content": "name: \"Lint composer.json\"\n\non:\n  pull_request:\n  push:\n\njobs:\n  coding-standards:\n    name: \"Lint composer.json\"\n\n    runs-on: ${{ matrix.operating-system }}\n\n    strategy:\n      matrix:\n        dependencies:\n          - \"highest\"\n        php-version:\n          - \"8.3\"\n        operating-system:\n          - \"ubuntu-latest\"\n\n    steps:\n      - name: \"Checkout\"\n        uses: \"actions/checkout@v6.0.2\"\n\n      - name: \"Install PHP\"\n        uses: \"shivammathur/setup-php@2.37.0\"\n        with:\n          coverage: \"none\"\n          php-version: \"${{ matrix.php-version }}\"\n          ini-values: memory_limit=-1\n          tools: composer:v2, composer-normalize, composer-require-checker, composer-unused\n\n      - name: \"Install dependencies\"\n        uses: \"ramsey/composer-install@4.0.0\"\n        with:\n          dependency-versions: \"${{ matrix.dependencies }}\"\n\n      - name: \"Validate composer.json\"\n        run: \"composer validate --strict\"\n\n      - name: \"Normalize composer.json\"\n        run: \"composer-normalize --dry-run\"\n\n      - name: \"Check composer.json explicit dependencies\"\n        run: \"composer-require-checker check --config-file=.composer-require-checker.config.json\"\n\n#      - name: \"Check composer.json unused dependencies\"\n#        run: \"composer-unused\"\n"
  },
  {
    "path": ".github/workflows/mutation-tests.yml",
    "content": "name: \"Mutation tests\"\n\non:\n  pull_request:\n  push:\n\njobs:\n  mutation-tests:\n    name: \"Mutation tests\"\n\n    runs-on: ${{ matrix.operating-system }}\n\n    strategy:\n      matrix:\n        dependencies:\n          - \"locked\"\n        php-version:\n          - \"8.3\"\n        operating-system:\n          - \"ubuntu-latest\"\n\n    steps:\n      - name: \"Checkout\"\n        uses: \"actions/checkout@v6.0.2\"\n\n      - name: \"Install PHP\"\n        uses: \"shivammathur/setup-php@2.37.0\"\n        with:\n          coverage: \"xdebug\"\n          php-version: \"${{ matrix.php-version }}\"\n          ini-values: memory_limit=-1\n          tools: composer:v2, cs2pr\n\n      - name: \"Install dependencies\"\n        uses: \"ramsey/composer-install@4.0.0\"\n        with:\n          dependency-versions: \"${{ matrix.dependencies }}\"\n\n      - name: \"Infection\"\n        run: \"make infection PHPUNIT_FLAGS=--coverage-clover=coverage.xml INFECTION_FLAGS=--logger-github\"\n\n      - name: \"Upload Code Coverage\"\n        uses: \"codecov/codecov-action@v5.5.3\"\n"
  },
  {
    "path": ".github/workflows/phpunit.yml",
    "content": "name: \"PHPUnit tests\"\n\non:\n  pull_request:\n  push:\n\njobs:\n  phpunit:\n    name: \"PHPUnit tests\"\n\n    runs-on: ${{ matrix.operating-system }}\n\n    strategy:\n      matrix:\n        dependencies:\n          - \"lowest\"\n          - \"highest\"\n          - \"locked\"\n        php-version:\n          - \"8.3\"\n          - \"8.4\"\n          - \"8.5\"\n        operating-system:\n          - \"ubuntu-latest\"\n\n    steps:\n      - name: \"Checkout\"\n        uses: \"actions/checkout@v6.0.2\"\n\n      - name: \"Install PHP\"\n        uses: \"shivammathur/setup-php@2.37.0\"\n        with:\n          coverage: \"none\"\n          php-version: \"${{ matrix.php-version }}\"\n          ini-values: memory_limit=-1\n          tools: composer:v2, cs2pr\n\n      - name: \"Install dependencies\"\n        uses: \"ramsey/composer-install@4.0.0\"\n        with:\n          dependency-versions: \"${{ matrix.dependencies }}\"\n\n      - name: \"Tests\"\n        run: \"make phpunit\"\n"
  },
  {
    "path": ".github/workflows/release-on-milestone-closed.yml",
    "content": "# https://help.github.com/en/categories/automating-your-workflow-with-github-actions\n\nname: \"Automatic Releases\"\n\non:\n  milestone:\n    types:\n      - \"closed\"\n\njobs:\n  release:\n    name: \"GIT tag, release & create merge-up PR\"\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: \"Checkout\"\n        uses: \"actions/checkout@v6.0.2\"\n\n      - name: \"Release\"\n        uses: \"laminas/automatic-releases@1.26.2\"\n        with:\n          command-name: \"laminas:automatic-releases:release\"\n        env:\n          \"SHELL_VERBOSITY\": \"3\"\n          \"GITHUB_TOKEN\": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}\n          \"SIGNING_SECRET_KEY\": ${{ secrets.SIGNING_SECRET_KEY }}\n          \"GIT_AUTHOR_NAME\": ${{ secrets.GIT_AUTHOR_NAME }}\n          \"GIT_AUTHOR_EMAIL\": ${{ secrets.GIT_AUTHOR_EMAIL }}\n\n      - name: \"Create Merge-Up Pull Request\"\n        uses: \"laminas/automatic-releases@1.26.2\"\n        with:\n          command-name: \"laminas:automatic-releases:create-merge-up-pull-request\"\n        env:\n          \"SHELL_VERBOSITY\": \"3\"\n          \"GITHUB_TOKEN\": ${{ secrets.GITHUB_TOKEN }}\n          \"SIGNING_SECRET_KEY\": ${{ secrets.SIGNING_SECRET_KEY }}\n          \"GIT_AUTHOR_NAME\": ${{ secrets.GIT_AUTHOR_NAME }}\n          \"GIT_AUTHOR_EMAIL\": ${{ secrets.GIT_AUTHOR_EMAIL }}\n\n      - name: \"Create and/or Switch to new Release Branch\"\n        uses: \"laminas/automatic-releases@1.26.2\"\n        with:\n          command-name: \"laminas:automatic-releases:switch-default-branch-to-next-minor\"\n        env:\n          \"SHELL_VERBOSITY\": \"3\"\n          \"GITHUB_TOKEN\": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}\n          \"SIGNING_SECRET_KEY\": ${{ secrets.SIGNING_SECRET_KEY }}\n          \"GIT_AUTHOR_NAME\": ${{ secrets.GIT_AUTHOR_NAME }}\n          \"GIT_AUTHOR_EMAIL\": ${{ secrets.GIT_AUTHOR_EMAIL }}\n\n      - name: \"Bump Changelog Version On Originating Release Branch\"\n        uses: \"laminas/automatic-releases@1.26.2\"\n        with:\n          command-name: \"laminas:automatic-releases:bump-changelog\"\n        env:\n          \"SHELL_VERBOSITY\": \"3\"\n          \"GITHUB_TOKEN\": ${{ secrets.GITHUB_TOKEN }}\n          \"SIGNING_SECRET_KEY\": ${{ secrets.SIGNING_SECRET_KEY }}\n          \"GIT_AUTHOR_NAME\": ${{ secrets.GIT_AUTHOR_NAME }}\n          \"GIT_AUTHOR_EMAIL\": ${{ secrets.GIT_AUTHOR_EMAIL }}\n\n      - name: \"Create new milestones\"\n        uses: \"laminas/automatic-releases@1.26.2\"\n        with:\n          command-name: \"laminas:automatic-releases:create-milestones\"\n        env:\n          \"SHELL_VERBOSITY\": \"3\"\n          \"GITHUB_TOKEN\": ${{ secrets.GITHUB_TOKEN }}\n          \"SIGNING_SECRET_KEY\": ${{ secrets.SIGNING_SECRET_KEY }}\n          \"GIT_AUTHOR_NAME\": ${{ secrets.GIT_AUTHOR_NAME }}\n          \"GIT_AUTHOR_EMAIL\": ${{ secrets.GIT_AUTHOR_EMAIL }}\n"
  },
  {
    "path": ".github/workflows/static-analysis.yml",
    "content": "name: \"Static Analysis\"\n\non:\n  pull_request:\n  push:\n\njobs:\n  static-analysis:\n    name: \"Static Analysis\"\n\n    runs-on: ${{ matrix.operating-system }}\n\n    strategy:\n      matrix:\n        dependencies:\n          - \"locked\"\n        php-version:\n          - \"8.3\"\n        operating-system:\n          - \"ubuntu-latest\"\n\n    steps:\n      - name: \"Checkout\"\n        uses: \"actions/checkout@v6.0.2\"\n\n      - name: \"Install PHP\"\n        uses: \"shivammathur/setup-php@2.37.0\"\n        with:\n          coverage: \"none\"\n          php-version: \"${{ matrix.php-version }}\"\n          ini-values: memory_limit=-1\n          tools: composer:v2, cs2pr\n\n      - name: \"Install dependencies\"\n        uses: \"ramsey/composer-install@4.0.0\"\n        with:\n          dependency-versions: \"${{ matrix.dependencies }}\"\n\n      - name: \"PHPStan\"\n        run: \"make phpstan\"\n"
  },
  {
    "path": ".gitignore",
    "content": "vendor\nphpunit.xml\ninfection.txt\ncoverage\nphpcs.xml\n/.phpcs.cache\n/.phpunit.result.cache\n/.phpunit.cache\n/build\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "version: 2\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3\"\n\nmkdocs:\n  configuration: mkdocs.yml\n"
  },
  {
    "path": ".roave-backward-compatibility-check.json",
    "content": "{\n  \"baseline\": [\n    \"#\\\\[BC\\\\] SKIPPED: Unable to compile initializer in method Lcobucci\\\\\\\\.+#\"\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2014, Luís Cobucci\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the {organization} nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Makefile",
    "content": "PARALLELISM := $(shell nproc)\n\n.PHONY: all\nall: install phpcbf phpcs phpstan phpunit infection phpbench\n\n.PHONY: install\ninstall: vendor/composer/installed.json\n\nvendor/composer/installed.json: composer.json composer.lock\n\t@composer install $(INSTALL_FLAGS)\n\t@touch -c composer.json composer.lock vendor/composer/installed.json\n\n.PHONY: phpunit\nphpunit:\n\t@php -d assert.exception=1 -d zend.assertions=1 vendor/bin/phpunit $(PHPUNIT_FLAGS)\n\n.PHONY: infection\ninfection:\n\t@php -d assert.exception=1 -d zend.assertions=1 -d xdebug.mode=coverage vendor/bin/phpunit --coverage-xml=build/coverage-xml --log-junit=build/junit.xml $(PHPUNIT_FLAGS)\n\t@php -d assert.exception=1 -d zend.assertions=1 vendor/bin/infection -v -s --threads=$(PARALLELISM) --coverage=build --skip-initial-tests $(INFECTION_FLAGS)\n\n.PHONY: phpcbf\nphpcbf:\n\t@vendor/bin/phpcbf --parallel=$(PARALLELISM) || true\n\n.PHONY: phpcs\nphpcs:\n\t@vendor/bin/phpcs --parallel=$(PARALLELISM) $(PHPCS_FLAGS)\n\n.PHONY: phpstan\nphpstan:\n\t@php -d xdebug.mode=off vendor/bin/phpstan analyse --memory-limit=-1\n\nifndef PHPBENCH_REPORT\noverride PHPBENCH_REPORT = aggregate\nendif\n\n.PHONY: phpbench\nphpbench:\n\t@vendor/bin/phpbench run -l dots --retry-threshold=5 --report=$(PHPBENCH_REPORT) $(PHPBENCH_FLAGS)\n"
  },
  {
    "path": "README.md",
    "content": "# JWT\n[![Gitter]](https://gitter.im/lcobucci/jwt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n[![Total Downloads]](https://packagist.org/packages/lcobucci/jwt)\n[![Latest Stable Version]](https://packagist.org/packages/lcobucci/jwt)\n[![Unstable Version]](https://packagist.org/packages/lcobucci/jwt)\n\n[![Build Status]](https://github.com/lcobucci/jwt/actions?query=workflow%3A%22PHPUnit%20Tests%22+branch%3A4.1.x)\n[![Code Coverage]](https://codecov.io/gh/lcobucci/jwt)\n\nA simple library to work with JSON Web Token and JSON Web Signature based on the [RFC 7519](https://tools.ietf.org/html/rfc7519).\n\n## Installation\n\nPackage is available on [Packagist](https://packagist.org/packages/lcobucci/jwt),\nyou can install it using [Composer](https://getcomposer.org).\n\n```shell\ncomposer require lcobucci/jwt\n```\n\n## Documentation\n\nThe documentation is available at <https://lcobucci-jwt.readthedocs.io/en/latest/>.\n\n[Gitter]: https://img.shields.io/badge/GITTER-JOIN%20CHAT%20%E2%86%92-brightgreen.svg?style=flat-square\n[Total Downloads]: https://img.shields.io/packagist/dt/lcobucci/jwt.svg?style=flat-square\n[Latest Stable Version]: https://img.shields.io/packagist/v/lcobucci/jwt.svg?style=flat-square\n[Unstable Version]: https://img.shields.io/packagist/vpre/lcobucci/jwt.svg?style=flat-square\n[Build Status]: https://img.shields.io/github/actions/workflow/status/lcobucci/jwt/phpunit.yml?branch=5.1.x&style=flat-square\n[Code Coverage]: https://codecov.io/gh/lcobucci/jwt/branch/5.1.x/graph/badge.svg\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"lcobucci/jwt\",\n    \"description\": \"A simple library to work with JSON Web Token and JSON Web Signature\",\n    \"license\": [\n        \"BSD-3-Clause\"\n    ],\n    \"type\": \"library\",\n    \"keywords\": [\n        \"JWT\",\n        \"JWS\"\n    ],\n    \"authors\": [\n        {\n            \"name\": \"Luís Cobucci\",\n            \"email\": \"lcobucci@gmail.com\",\n            \"role\": \"Developer\"\n        }\n    ],\n    \"require\": {\n        \"php\": \"~8.3.0 || ~8.4.0 || ~8.5.0\",\n        \"ext-openssl\": \"*\",\n        \"ext-sodium\": \"*\",\n        \"psr/clock\": \"^1.0\"\n    },\n    \"require-dev\": {\n        \"infection/infection\": \"^0.32.6\",\n        \"lcobucci/clock\": \"^3.5.0\",\n        \"lcobucci/coding-standard\": \"^11.2\",\n        \"phpbench/phpbench\": \"^1.4.3\",\n        \"phpstan/extension-installer\": \"^1.4.3\",\n        \"phpstan/phpstan\": \"^2.1.40\",\n        \"phpstan/phpstan-deprecation-rules\": \"^2.0.4\",\n        \"phpstan/phpstan-phpunit\": \"^2.0.16\",\n        \"phpstan/phpstan-strict-rules\": \"^2.0.10\",\n        \"phpunit/phpunit\": \"^12.5.14 || ^13.0.5\"\n    },\n    \"suggest\": {\n        \"lcobucci/clock\": \">= 3.2\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"Lcobucci\\\\JWT\\\\\": \"src\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Lcobucci\\\\JWT\\\\Tests\\\\\": \"tests\"\n        }\n    },\n    \"config\": {\n        \"allow-plugins\": {\n            \"dealerdirect/phpcodesniffer-composer-installer\": true,\n            \"infection/extension-installer\": true,\n            \"ocramius/package-versions\": true,\n            \"phpstan/extension-installer\": true\n        },\n        \"preferred-install\": \"dist\",\n        \"sort-packages\": true\n    }\n}\n"
  },
  {
    "path": "docs/configuration.md",
    "content": "# Configuration\n\nIn order to simplify the setup of the library, we provide the class `Lcobucci\\JWT\\Configuration`.\n\nIt's meant for:\n\n* Configuring the default algorithm (signer) and key(s) to be used\n* Configuring the default set of validation constraints\n* Providing custom implementation for the [extension points](extending-the-library.md)\n* Retrieving components (encoder, decoder, parser, validator, and builder)\n\n## Initialisation\n\nThe `Lcobucci\\JWT\\Signer\\Key\\InMemory` object is used for symmetric/asymmetric signature.\n\nTo initialise it, you can pass the key content as a plain text:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\nrequire 'vendor/autoload.php';\n\n$key = InMemory::plainText('my-key-as-plaintext');\n```\n\nProvide a base64 encoded string:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\nrequire 'vendor/autoload.php';\n\n$key = InMemory::base64Encoded('mBC5v1sOKVvbdEitdSBenu59nfNfhwkedkJVNabosTw=');\n```\n\nOr provide a file path:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\nrequire 'vendor/autoload.php';\n\n// this reads the file and keeps its contents in memory\n$key = InMemory::file(__DIR__ . '/path-to-my-key-stored-in-a-file.pem');\n```\n\n### For symmetric algorithms\n\n[Symmetric algorithms](supported-algorithms.md#symmetric-algorithms) use the same key for both signature creation and verification.\nThis means that it's really important that your key **remains secret**.\n\n!!! Tip\n    It is recommended that you use a key with lots of entropy, preferably generated using a cryptographically secure pseudo-random number generator (CSPRNG).\n    You can use the [CryptoKey](https://github.com/AndrewCarterUK/CryptoKey) tool to do this for you.\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\nrequire 'vendor/autoload.php';\n\n$configuration = Configuration::forSymmetricSigner(\n    new Signer\\Hmac\\Sha256(),\n    // replace the value below with a key of your own!\n    InMemory::base64Encoded('mBC5v1sOKVvbdEitdSBenu59nfNfhwkedkJVNabosTw=')\n    // You may also override the JOSE encoder/decoder if needed\n    // by providing extra arguments here\n);\n```\n\n### For asymmetric algorithms\n\n[Asymmetric algorithms](supported-algorithms.md#asymmetric-algorithms) use a **private key** for signature creation and a **public key** for verification.\nThis means that it's fine to distribute your **public key**. However, the **private key** should **remain secret**.\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\nrequire 'vendor/autoload.php';\n\n$configuration = Configuration::forAsymmetricSigner(\n    new Signer\\Rsa\\Sha256(),\n    InMemory::file(__DIR__ . '/my-private-key.pem'),\n    InMemory::base64Encoded('mBC5v1sOKVvbdEitdSBenu59nfNfhwkedkJVNabosTw=')\n    // You may also override the JOSE encoder/decoder if needed\n    // by providing extra arguments here\n);\n```\n\n## Customisation\n\nBy using the setters of the `Lcobucci\\JWT\\Configuration` you may customise the setup of this library.\n\n!!! Important\n    If you want to use a customised configuration, please make sure you call the setters before of invoking any getter.\n    Otherwise, the default implementations will be used.\n\n### Builder factory\n\nIt configures how the token builder should be created.\nIt's useful when you want to provide a [custom Builder](extending-the-library.md#builder).\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\ClaimsFormatter;\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\nrequire 'vendor/autoload.php';\n\n$configuration = Configuration::forSymmetricSigner(\n    new Signer\\Hmac\\Sha256(),\n    InMemory::base64Encoded('mBC5v1sOKVvbdEitdSBenu59nfNfhwkedkJVNabosTw=')\n);\n\n$configuration = $configuration->withBuilderFactory(\n    static function (ClaimsFormatter $formatter): Builder {\n        // This assumes `MyCustomBuilder` is an existing class \n        return new MyCustomBuilder(new JoseEncoder(), $formatter);\n    }\n);\n```\n\n### Parser\n\nIt configures how the token parser should be created.\nIt's useful when you want to provide a [custom Parser](extending-the-library.md#parser).\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\nrequire 'vendor/autoload.php';\n\n$configuration = Configuration::forSymmetricSigner(\n    new Signer\\Hmac\\Sha256(),\n    InMemory::base64Encoded('mBC5v1sOKVvbdEitdSBenu59nfNfhwkedkJVNabosTw=')\n);\n\n// This assumes `MyParser` is an existing class \n$configuration = $configuration->withParser(new MyParser());\n```\n\n### Validator\n\nIt configures how the token validator should be created.\nIt's useful when you want to provide a [custom Validator](extending-the-library.md#validator).\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\nrequire 'vendor/autoload.php';\n\n$configuration = Configuration::forSymmetricSigner(\n    new Signer\\Hmac\\Sha256(),\n    InMemory::base64Encoded('mBC5v1sOKVvbdEitdSBenu59nfNfhwkedkJVNabosTw=')\n);\n\n// This assumes `MyValidator` is an existing class\n$configuration = $configuration->withValidator(new MyValidator());\n```\n\n### Validation constraints\n\nIt configures which are the base constraints to be used during validation.\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\Clock\\SystemClock; // If you prefer, other PSR-20 implementations may also be used\n                                // (https://packagist.org/providers/psr/clock-implementation)\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Validation\\Constraint\\IssuedBy;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\Constraint\\StrictValidAt;\n\nrequire 'vendor/autoload.php';\n\n$configuration = Configuration::forSymmetricSigner(\n    new Signer\\Hmac\\Sha256(),\n    InMemory::base64Encoded('mBC5v1sOKVvbdEitdSBenu59nfNfhwkedkJVNabosTw=')\n);\n\n$configuration = $configuration->withValidationConstraints(\n    new SignedWith($configuration->signer(), $configuration->signingKey()),\n    new StrictValidAt(SystemClock::fromUTC()),\n    new IssuedBy('https://api.my-awesome-company.com')\n);\n```\n\n## Retrieve components\n\nOnce you've made all the necessary configuration you can pass the configuration object around your code and use the getters to retrieve the components:\n\nThese are the available getters:\n\n* `Lcobucci\\JWT\\Configuration#builder()`: retrieves the token builder (always creating a new instance)\n* `Lcobucci\\JWT\\Configuration#parser()`: retrieves the token parser\n* `Lcobucci\\JWT\\Configuration#signer()`: retrieves the signer\n* `Lcobucci\\JWT\\Configuration#signingKey()`: retrieves the key for signature creation\n* `Lcobucci\\JWT\\Configuration#verificationKey()`: retrieves the key for signature verification\n* `Lcobucci\\JWT\\Configuration#validator()`: retrieves the token validator\n* `Lcobucci\\JWT\\Configuration#validationConstraints()`: retrieves the default set of validation constraints\n"
  },
  {
    "path": "docs/extending-the-library.md",
    "content": "# Extending the library\n\n!!! Note\n    The examples here fetch the configuration object from a hypothetical dependency injection container.\n    You can create it in the same script or require it from a different file. It basically depends on how your system is bootstrapped.\n\nWe've designed a few extension points in this library.\nThese should enable people to easily customise our core components if they want to.\n\n## Builder\n\nThe token builder defines a fluent interface for plain token creation.\n\nTo create your own builder of it you must implement the `Lcobucci\\JWT\\Builder` interface:\n\n```php\nuse Lcobucci\\JWT\\Builder;\n\nfinal class MyCustomTokenBuilder implements Builder\n{\n    // implement all methods\n}\n```\n\nThen, register a custom factory in the [configuration object]:\n\n```php\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\ClaimsFormatter;\nuse Lcobucci\\JWT\\Configuration;\n\n$config = $container->get(Configuration::class);\nassert($config instanceof Configuration);\n\n$configuration = $configuration->withBuilderFactory(\n    static function (ClaimsFormatter $formatter): Builder {\n        return new MyCustomTokenBuilder($formatter);\n    }\n);\n```\n\n## Claims formatter\n\nBy default, we provide formatters that:\n\n- unify the audience claim, making sure we use strings when there's only one item in that claim\n- format date based claims using microseconds (float)\n\nYou may customise and even create your own formatters:\n\n```php\nuse Lcobucci\\JWT\\ClaimsFormatter;\nuse Lcobucci\\JWT\\Configuration;\nuse Serializable;\n\nfinal class ClaimSerializer implements ClaimsFormatter\n{\n    /** @inheritdoc  */\n    public function formatClaims(array $claims): array\n    {\n        foreach ($claims as $claim => $claimValue) {\n            if ($claimValue instanceof Serializable) {\n                $claims[$claim] = $claimValue->serialize();\n            }\n        }\n\n        return $claims;\n    }\n}\n\n$config = $container->get(Configuration::class);\nassert($config instanceof Configuration);\n\n$builder = $config->builder(new ClaimSerializer());\n```\n\nThe class `Lcobucci\\JWT\\Encoding\\ChainedFormatter` allows for users to combine multiple formatters. \n\n## Parser\n\nThe token parser defines how a JWT string should be converted into token objects.\n\nTo create your own parser of it you must implement the `Lcobucci\\JWT\\Parser` interface:\n\n```php\nuse Lcobucci\\JWT\\Parser;\n\nfinal class MyCustomTokenParser implements Parser\n{\n    // implement all methods\n}\n```\n\nThen register an instance in the [configuration object]:\n\n```php\nuse Lcobucci\\JWT\\Configuration;\n\n$config = $container->get(Configuration::class);\nassert($config instanceof Configuration);\n\n$configuration = $configuration->withParser(new MyCustomTokenParser());\n```\n\n## Signer\n\nThe signer defines how to create and verify signatures.\n\nTo create your own signer of it you must implement the `Lcobucci\\JWT\\Signer` interface:\n\n```php\nuse Lcobucci\\JWT\\Signer;\n\nfinal class SignerForAVeryCustomizedAlgorithm implements Signer\n{\n    // implement all methods\n}\n```\n\nThen pass an instance of it while creating an instance of the [configuration object], [issuing a token](issuing-tokens.md), or [validating a token].\n\n## Key\n\nThe key object is passed down to signers and provide the necessary information to create and verify signatures.\n\nTo create your own signer of it you must implement the `Lcobucci\\JWT\\Signer\\Key` interface:\n\n```php\nuse Lcobucci\\JWT\\Signer\\Key;\n\nfinal class KeyWithSomeMagicalProperties implements Key\n{\n    // implement all methods\n}\n```\n\n## Validator\n\nThe token validator defines how to apply validation constraint to either validate or assert tokens.\n\nTo create your own validator of it you must implement the `Lcobucci\\JWT\\Validator` interface:\n\n```php\nuse Lcobucci\\JWT\\Validator;\n\nfinal class MyCustomTokenValidator implements Validator\n{\n    // implement all methods\n}\n```\n\nThen register an instance in the [configuration object]:\n\n```php\nuse Lcobucci\\JWT\\Configuration;\n\n$config = $container->get(Configuration::class);\nassert($config instanceof Configuration);\n\n$configuration = $configuration->withValidator(new MyCustomTokenValidator());\n```\n\n## Validation constraints\n\nA validation constraint define how one or more claims/headers should be validated.\nCustom validation constraints are handy to provide advanced rules for the registered claims or to validate private claims.\n\nTo create your own implementation of constraint you must implement the `Lcobucci\\JWT\\Validation\\Constraint` interface:\n\n```php\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\UnencryptedToken;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\n\nfinal class SubjectMustBeAValidUser implements Constraint\n{\n    public function assert(Token $token): void\n    {\n        if (! $token instanceof UnencryptedToken) {\n            throw new ConstraintViolation('You should pass a plain token');\n        }\n\n        if (! $this->existsInDatabase($token->claims()->get('sub'))) {\n            throw new ConstraintViolation('Token related to an unknown user');\n        }\n    }\n\n    private function existsInDatabase(string $userId): bool\n    {\n        // ...\n    }\n}\n```\n\nThen use it while [validating a token].\n\n[configuration object]: configuration.md\n[validating a token]: validating-tokens.md\n"
  },
  {
    "path": "docs/index.md",
    "content": "# Overview\n\n`lcobucci/jwt` is a framework-agnostic PHP library that allows you to issue, parse, and validate JSON Web Tokens based on the [RFC 7519].\n\n## Support\n\nIf you're having any issue to use the library, please [create a GH issue].\nYou can also reach us and other users of this library via our [Gitter channel].\n\n## License\n\nThe project is licensed under the MIT license, see [LICENSE file].\n\n[RFC 7519]: https://tools.ietf.org/html/rfc7519\n[create a GH issue]: https://github.com/lcobucci/jwt/issues/new\n[Gitter channel]: https://gitter.im/lcobucci/jwt\n[LICENSE file]: https://github.com/lcobucci/jwt/blob/master/LICENSE\n"
  },
  {
    "path": "docs/installation.md",
    "content": "# Installation\n\nThis package is available on [Packagist] and you can install it using [Composer].\n\nBy running the following command you'll add `lcobucci/jwt` as a dependency to your project:\n\n```sh\ncomposer require lcobucci/jwt\n```\n\n## Autoloading\n\n!!! Note\n    We'll be omitting the autoloader from the code samples to simplify the documentation.\n\nIn order to be able to use the classes provided by this library you're also required to include [Composer]'s autoloader in your application:\n\n```php\nrequire 'vendor/autoload.php';\n```\n\n!!! Tip\n    If you're not familiar with how [composer] works, we highly recommend you to take some time to read it's documentation - especially the [autoloading section].\n\n[Packagist]: https://packagist.org/packages/lcobucci/jwt\n[Composer]: https://getcomposer.org\n[autoloading section]: https://getcomposer.org/doc/01-basic-usage.md#autoloading\n"
  },
  {
    "path": "docs/issuing-tokens.md",
    "content": "# Issuing tokens\n\nTo issue new tokens you must create a new token builder, customise it, and ask it to build the token:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse Lcobucci\\JWT\\Token\\Builder;\n\nrequire 'vendor/autoload.php';\n\n$tokenBuilder = Builder::new(new JoseEncoder(), ChainedFormatter::default());\n$algorithm    = new Sha256();\n$signingKey   = InMemory::plainText(random_bytes(32));\n\n$now   = new DateTimeImmutable();\n$token = $tokenBuilder\n    // Configures the issuer (iss claim)\n    ->issuedBy('http://example.com')\n    // Configures the audience (aud claim)\n    ->permittedFor('http://example.org')\n    // Configures the subject of the token (sub claim)\n    ->relatedTo('component1')\n    // Configures the id (jti claim)\n    ->identifiedBy('4f1g23a12aa')\n    // Configures the time that the token was issue (iat claim)\n    ->issuedAt($now)\n    // Configures the time that the token can be used (nbf claim)\n    ->canOnlyBeUsedAfter($now->modify('+1 minute'))\n    // Configures the expiration time of the token (exp claim)\n    ->expiresAt($now->modify('+1 hour'))\n    // Configures a new claim, called \"uid\"\n    ->withClaim('uid', 1)\n    // Configures a new header, called \"foo\"\n    ->withHeader('foo', 'bar')\n    // Builds a new token\n    ->getToken($algorithm, $signingKey);\n\necho $token->toString();\n```\n\nOnce you've created a token, you're able to retrieve its data and convert it to its string representation:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse Lcobucci\\JWT\\Token\\Builder;\n\nrequire 'vendor/autoload.php';\n\n$tokenBuilder = Builder::new(new JoseEncoder(), ChainedFormatter::default());\n$algorithm    = new Sha256();\n$signingKey   = InMemory::plainText(random_bytes(32));\n\n$token = $tokenBuilder\n    ->issuedBy('http://example.com')\n    ->withClaim('uid', 1)\n    ->withHeader('foo', 'bar')\n    ->getToken($algorithm, $signingKey);\n\n$token->headers(); // Retrieves the token headers\n$token->claims(); // Retrieves the token claims\n\necho $token->headers()->get('foo'), PHP_EOL; // will print \"bar\"\necho $token->claims()->get('iss'), PHP_EOL; // will print \"http://example.com\"\necho $token->claims()->get('uid'), PHP_EOL; // will print \"1\"\n\necho $token->toString(), PHP_EOL; // The string representation of the object is a JWT string\n\n```\n\n!!! Note\n    Some systems make use of components to handle dependency injection.\n    If your application follows that practice, using a [configuration object](configuration.md) might simplify the wiring of this library.\n"
  },
  {
    "path": "docs/parsing-tokens.md",
    "content": "# Parsing tokens\n\nTo parse a token you must create a new parser and ask it to parse a string:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Encoding\\CannotDecodeContent;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Token\\InvalidTokenStructure;\nuse Lcobucci\\JWT\\Token\\Parser;\nuse Lcobucci\\JWT\\Token\\UnsupportedHeaderFound;\nuse Lcobucci\\JWT\\UnencryptedToken;\n\nrequire 'vendor/autoload.php';\n\n$parser = new Parser(new JoseEncoder());\n\ntry {\n    $token = $parser->parse(\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'\n        . 'eyJzdWIiOiIxMjM0NTY3ODkwIn0.'\n        . '2gSBz9EOsQRN9I-3iSxJoFt7NtgV6Rm0IL6a8CAwl3Q'\n    );\n} catch (CannotDecodeContent | InvalidTokenStructure | UnsupportedHeaderFound $e) {\n    echo 'Oh no, an error: ' . $e->getMessage();\n}\nassert($token instanceof UnencryptedToken);\n\necho $token->claims()->get('sub'), PHP_EOL; // will print \"1234567890\"\n\n```\n\n!!! Note\n    Some systems make use of components to handle dependency injection.\n    If your application follows that practice, using a [configuration object](configuration.md) might simplify the wiring of this library.\n"
  },
  {
    "path": "docs/quick-start.md",
    "content": "# Quick start\n\nOnce the library has been [installed](installation.md), you are able to issue and parse JWTs.\nThe class `Lcobucci\\JWT\\JwtFacade` is the quickest way to perform these operations.\n\nUsing that facade we also aim to make sure that every token is properly signed and has the recommended claims for date control.\n\n## Issuing tokens\n\nThe method `Lcobucci\\JWT\\JwtFacade#issue()` is available for quickly creating tokens.\nIt uses the current time to generate the date claims (default expiration is **5 minutes**).\n\nTo issue a token, call the method passing: an algorithm, a key, and a customisation function:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nnamespace MyApp;\n\nrequire 'vendor/autoload.php';\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\nuse function var_dump;\n\n$key = InMemory::base64Encoded(\n    'hiG8DlOKvtih6AxlZn5XKImZ06yu8I3mkOzaJrEuW8yAv8Jnkw330uMt8AEqQ5LB'\n);\n\n$token = (new JwtFacade())->issue(\n    new Sha256(),\n    $key,\n    static fn (\n        Builder $builder,\n        DateTimeImmutable $issuedAt\n    ): Builder => $builder\n        ->issuedBy('https://api.my-awesome-app.io')\n        ->permittedFor('https://client-app.io')\n        ->expiresAt($issuedAt->modify('+10 minutes'))\n);\n\nvar_dump($token->claims()->all());\necho $token->toString();\n```\n\n### Creating tokens during tests\n\nTo reduce the chance of having flaky tests on your test suite, the facade supports the usage of a clock object.\nThat allows passing an implementation that always returns the same point in time.\n\nYou can achieve that by specifying the `clock` constructor parameter:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nnamespace MyApp;\n\nrequire 'vendor/autoload.php';\n\nuse DateTimeImmutable;\nuse Lcobucci\\Clock\\FrozenClock; // If you prefer, other PSR-20 implementations may also be used\n                                // (https://packagist.org/providers/psr/clock-implementation)\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\n\n$clock = new FrozenClock(new DateTimeImmutable('2022-06-24 22:51:10'));\n$key   = InMemory::base64Encoded(\n    'hiG8DlOKvtih6AxlZn5XKImZ06yu8I3mkOzaJrEuW8yAv8Jnkw330uMt8AEqQ5LB'\n);\n\n$token = (new JwtFacade(null, $clock))->issue(\n    new Sha256(),\n    $key,\n    static fn (\n        Builder $builder,\n        DateTimeImmutable $issuedAt\n    ): Builder => $builder\n);\n\necho $token->claims()->get(\n    RegisteredClaims::ISSUED_AT\n)->format(DateTimeImmutable::RFC3339); // 2022-06-24 22:51:10\n```\n\n## Parsing tokens\n\nThe method `Lcobucci\\JWT\\JwtFacade#parse()` is the one for quickly parsing tokens.\nIt also verifies the signature and date claims, throwing an exception in case of tokens in unexpected state.\n\n```php\n<?php\ndeclare(strict_types=1);\n\nnamespace MyApp;\n\nrequire 'vendor/autoload.php';\n\nuse DateTimeImmutable;\nuse Lcobucci\\Clock\\FrozenClock; // If you prefer, other PSR-20 implementations may also be used\n                                // (https://packagist.org/providers/psr/clock-implementation)\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Validation\\Constraint;\n\nuse function var_dump;\n\n$jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NTg2OTYwNTIsIm5iZiI6MT'\n    . 'Y1ODY5NjA1MiwiZXhwIjoxNjU4Njk2NjUyLCJpc3MiOiJodHRwczovL2FwaS5teS1hd2Vzb'\n    . '21lLWFwcC5pbyIsImF1ZCI6Imh0dHBzOi8vY2xpZW50LWFwcC5pbyJ9.yzxpjyq8lXqMgaN'\n    . 'rMEOLUr7R0brvhwXx0gp56uWEIfc';\n\n$key = InMemory::base64Encoded(\n    'hiG8DlOKvtih6AxlZn5XKImZ06yu8I3mkOzaJrEuW8yAv8Jnkw330uMt8AEqQ5LB'\n);\n\n$token = (new JwtFacade())->parse(\n    $jwt,\n    new Constraint\\SignedWith(new Sha256(), $key),\n    new Constraint\\StrictValidAt(\n        new FrozenClock(new DateTimeImmutable('2022-07-24 20:55:10+00:00'))\n    )\n);\n\nvar_dump($token->claims()->all());\n```\n\n!!! Warning\n    The example above uses `FrozenClock` as clock implementation to make sure that code will always work.\n    Use `SystemClock` on the production code of your application, allowing the parser to correctly verify the date claims.\n"
  },
  {
    "path": "docs/rotating-keys.md",
    "content": "# Rotating Keys\n\nKey rotation consists in retiring and replacing cryptographic keys with new ones.\nPerforming that operation on a regular basis is an industry standard.\n\n## Why should I rotate my keys?\n\nRotating keys allows us to:\n\n1. Limit the number of tokens signed with the same key, helping the prevention of attacks enabled by cryptanalysis\n2. Adopt other algorithms or stronger keys\n3. Limit the impact of eventual compromised keys\n\n## The challenges\n\nAfter rotating keys, apps will likely receive requests with tokens issued with the previous key.\nIf the key rotation of an app is done with a \"hard cut\", requests with non-expired tokens issued with the old key **will fail**!\n\nImagine if you were the user who logged in just before a key rotation on that kind of app, you'd probably have to log in again!\n\nThat's rather frustrating, right!?\n\n## Preventing issues\n\nIt's possible to handle key rotation in a smoother way by leveraging the `SignedWithOneInSet` validation constraint!\n\nSay your application uses the symmetric algorithm `HS256` with a not so secure key to issue tokens:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nnamespace MyApp;\n\nrequire 'vendor/autoload.php';\n\nuse DateTimeImmutable;\nuse Lcobucci\\Clock\\FrozenClock;\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\n// `FrozenClock` is used here to fix to a point in time that allows our validation to pass\n$clock = new FrozenClock(new DateTimeImmutable('2023-11-04 21:06:01+00:00')); \n$token = (new JwtFacade(clock: $clock))->issue(\n    new Signer\\Hmac\\Sha256(),\n    InMemory::plainText(\n        'a-very-long-and-secure-key-that-should-actually-be-something-else'\n    ),\n    static fn (Builder $builder): Builder => $builder\n        ->issuedBy('https://api.my-awesome-app.io')\n        ->permittedFor('https://client-app.io')\n);\n```\n\n!!! Sample\n    Here's a token issued with the code above, if you want to test the script locally:\n\n    <details>\n        <summary>Sample token</summary>\n        \n        // line breaks added for readability\n        eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9\n        .eyJpYXQiOjE2OTkxMzE5NjEsIm5iZiI6MTY5OTEzMTk2MSwiZXhwIjoxNjk5MTMyMjYxLCJpc3MiOiJ\n        odHRwczovL2FwaS5teS1hd2Vzb21lLWFwcC5pbyIsImF1ZCI6Imh0dHBzOi8vY2xpZW50LWFwcC5pbyJ9\n        .IA9S0n8Q2O97lyR8KczVE8g-hxbbH6_TfJS-JWTQR4c\n    </details>\n\nYour parsing logic (with validations) look like:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nnamespace MyApp;\n\nrequire 'vendor/autoload.php';\n\nuse DateTimeImmutable;\nuse Lcobucci\\Clock\\FrozenClock;\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Validation\\Constraint;\n\n// `FrozenClock` is used here to fix to a point in time that allows our\n// validation to pass\n$clock = new FrozenClock(new DateTimeImmutable('2023-11-04 21:06:35+00:00'))\n\n$validationConstraints = [\n    new Constraint\\SignedWith(\n        new Signer\\Hmac\\Sha256(),\n        InMemory::plainText(\n            'a-very-long-and-secure-key-that-should-actually-be-something-else'\n        ),\n    ),\n    new Constraint\\StrictValidAt($clock),\n];\n\n$jwt = ''; // Fetched from, for example, a request header\n\n$token = (new JwtFacade())->parse($jwt, ...$validationConstraints);\n```\n\n### Performing a backwards compatible rotation\n\nNow Imagine that you want to adopt the new `BLAKE2B` symmetric algorithm.\n\nThese are the changes to your issuing logic:\n\n```diff\n <?php\n declare(strict_types=1);\n \n namespace MyApp;\n \n require 'vendor/autoload.php';\n \n use DateTimeImmutable;\n use Lcobucci\\Clock\\FrozenClock;\n use Lcobucci\\JWT\\Builder;\n use Lcobucci\\JWT\\JwtFacade;\n use Lcobucci\\JWT\\Signer;\n use Lcobucci\\JWT\\Signer\\Key\\InMemory;\n \n // `FrozenClock` is used here to fix to a point in time that allows our validation to pass\n $clock = new FrozenClock(new DateTimeImmutable('2023-11-04 21:06:01+00:00')); \n $token = (new JwtFacade(clock: $clock))->issue(\n-    new Signer\\Hmac\\Sha256(),\n+    new Signer\\Blake2b(),\n-    InMemory::plainText(\n-        'a-very-long-and-secure-key-that-should-actually-be-something-else'\n+    InMemory::base64Encoded(\n+        'GOu4rLyVCBxmxP+sbniU68ojAja5PkRdvv7vNvBCqDQ='\n     ),\n     static fn (Builder $builder): Builder => $builder\n         ->issuedBy('https://api.my-awesome-app.io')\n         ->permittedFor('https://client-app.io')\n );\n```\n\n!!! Sample\n    Here's a token issued with the code above, if you want to test the script locally:\n\n    <details>\n        <summary>Sample token</summary>\n        \n        // line breaks added for readability\n        eyJ0eXAiOiJKV1QiLCJhbGciOiJCTEFLRTJCIn0\n        .eyJpYXQiOjE2OTkxMzE5NjEsIm5iZiI6MTY5OTEzMTk2MSwiZXhwIjoxNjk5MTMyMjYxLCJpc3Mi\n        OiJodHRwczovL2FwaS5teS1hd2Vzb21lLWFwcC5pbyIsImF1ZCI6Imh0dHBzOi8vY2xpZW50LWFwc\n        C5pbyJ9.bD67s8IXpAJiBTIZn1et_M5WSS7kfmuNiacNRz5lArQ\n    </details>\n\nSo far, nothing different that a normal rotation.\n\nNow check the changes on the parsing and validation logic:\n\n```diff\n <?php\n declare(strict_types=1);\n \n namespace MyApp;\n \n require 'vendor/autoload.php';\n \n use DateTimeImmutable;\n use Lcobucci\\Clock\\FrozenClock;\n use Lcobucci\\JWT\\JwtFacade;\n use Lcobucci\\JWT\\Signer;\n use Lcobucci\\JWT\\Signer\\Key\\InMemory;\n use Lcobucci\\JWT\\Validation\\Constraint;\n\n // `FrozenClock` is used here to fix to a point in time that allows our\n // validation to pass\n $clock = new FrozenClock(new DateTimeImmutable('2023-11-04 21:06:35+00:00'));\n \n $validationConstraints = [\n-    new Constraint\\SignedWith(\n-        new Signer\\Hmac\\Sha256(),\n-        InMemory::plainText(\n-            'a-very-long-and-secure-key-that-should-actually-be-something-else'\n-        ),\n-    ),\n+    new Constraint\\SignedWithOneInSet(\n+       new Constraint\\SignedWithUntilDate(\n+           new Signer\\Blake2b(),\n+           InMemory::base64Encoded(\n+               'GOu4rLyVCBxmxP+sbniU68ojAja5PkRdvv7vNvBCqDQ='\n+           ),\n+           new DateTimeImmutable('2025-12-31 23:59:59+00:00'),\n+           $clock,\n+       ),\n+       new Constraint\\SignedWithUntilDate(\n+            new Signer\\Hmac\\Sha256(),\n+           InMemory::plainText(\n+                'a-very-long-and-secure-key-that-should-actually-be-something-else'\n+           ),\n+           new DateTimeImmutable('2023-12-31 23:59:59+00:00'),\n+           $clock,\n+       ),\n+    ),\n     new Constraint\\StrictValidAt($clock),\n ];\n \n $jwt = ''; // Fetched from, for example, a request header\n \n $token = (new JwtFacade())->parse($jwt, ...$validationConstraints);\n```\n\nNow the application is able to accept non-expired tokens issued with either old and new keys!\nIn this case, the old key would automatically only be accepted until `2023-12-31 23:59:59+00:00`, even if engineers forget to remove it from the list.\n\n!!! Important\n    The order of `SignedWithUntilDate` constraints given to `SignedWithOneInSet` does matter, and it's recommended to leave older keys at the end of the list.\n"
  },
  {
    "path": "docs/supported-algorithms.md",
    "content": "# Supported Algorithms\n\nThis library supports signing and verifying tokens with both symmetric and asymmetric algorithms.\nEncryption is not yet supported.\n\nEach algorithm will produce signature with different length.\nIf you have constraints regarding the length of the issued tokens, choose the algorithms that generate shorter output (`HS256`, `RS256`, `ES256`, and `BLAKE2B`).\n\n## Symmetric algorithms\n\nSymmetric algorithms perform signature creation and verification using the very same key/secret.\nThey're usually recommended for scenarios where these operations are handled by the very same component.\n\n| Name      | Description        | Class                              | Key length req. |\n|-----------|--------------------|------------------------------------|-----------------|\n| `HS256`   | HMAC using SHA-256 | `\\Lcobucci\\JWT\\Signer\\Hmac\\Sha256` | `>= 256 bits`   |\n| `HS384`   | HMAC using SHA-384 | `\\Lcobucci\\JWT\\Signer\\Hmac\\Sha384` | `>= 384 bits`   |\n| `HS512`   | HMAC using SHA-512 | `\\Lcobucci\\JWT\\Signer\\Hmac\\Sha512` | `>= 512 bits`   |\n| `BLAKE2B` | Blake2b keyed Hash | `\\Lcobucci\\JWT\\Signer\\Blake2b`     | `>= 256 bits`   |\n\n!!! Warning\n    Although `BLAKE2B` is fantastic due to its performance, it's not [JWT standard] and won't necessarily be offered by other libraries.\n\n## Asymmetric algorithms\n\nAsymmetric algorithms perform signature creation with private/secret keys and verification with public keys.\nThey're usually recommended for scenarios where creation is handled by a component and verification by many others.\n\n| Name    | Description                     | Class                               | Key length req. |\n|---------|---------------------------------|-------------------------------------|-----------------|\n| `ES256` | ECDSA using P-256 and SHA-256   | `\\Lcobucci\\JWT\\Signer\\Ecdsa\\Sha256` | `== 256 bits`   |\n| `ES384` | ECDSA using P-384 and SHA-384   | `\\Lcobucci\\JWT\\Signer\\Ecdsa\\Sha384` | `== 384 bits`   |\n| `ES512` | ECDSA using P-521 and SHA-512   | `\\Lcobucci\\JWT\\Signer\\Ecdsa\\Sha512` | `== 521 bits`   |\n| `RS256` | RSASSA-PKCS1-v1_5 using SHA-256 | `\\Lcobucci\\JWT\\Signer\\Rsa\\Sha256`   | `>= 2048 bits`  |\n| `RS384` | RSASSA-PKCS1-v1_5 using SHA-384 | `\\Lcobucci\\JWT\\Signer\\Rsa\\Sha384`   | `>= 2048 bits`  |\n| `RS512` | RSASSA-PKCS1-v1_5 using SHA-512 | `\\Lcobucci\\JWT\\Signer\\Rsa\\Sha512`   | `>= 2048 bits`  |\n| `EdDSA` | EdDSA signature algorithms      | `\\Lcobucci\\JWT\\Signer\\Eddsa`        | `>= 256 bits`   |\n\nThe following algorithms are implemented in a separate package `lcobucci/jwt-rsassa-pss` in order to keep dependencies low in the main package.\nPlease see the installation instructions in the [RSASSA-PSS readme].\n\n| Name    | Description                     | Class                                | Key length req. |\n|---------|---------------------------------|--------------------------------------|-----------------|\n| `PS256` | RSASSA-PSS using SHA-256        | `\\Lcobucci\\JWT\\Signer\\RsaPss\\Sha256` | `>= 2048 bits`  |\n| `PS384` | RSASSA-PSS using SHA-384        | `\\Lcobucci\\JWT\\Signer\\RsaPss\\Sha384` | `>= 2048 bits`  |\n| `PS512` | RSASSA-PSS using SHA-512        | `\\Lcobucci\\JWT\\Signer\\RsaPss\\Sha512` | `>= 2048 bits`  |\n\n## `none` algorithm\n\nThe `none` algorithm as described by [JWT standard] is intentionally not implemented and not supported.\nThe risk of misusing it is too high, and even where other means guarantee the token validity a symmetric algorithm\nshouldn't represent a computational bottleneck with modern hardware.\n\n[JWT standard]: https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms\n[RSASSA-PSS readme]: https://github.com/lcobucci/jwt-rsassa-pss\n"
  },
  {
    "path": "docs/upgrading.md",
    "content": "# Upgrading steps\n\nHere we'll keep a list of all steps you need to take to make sure your code is compatible with newer versions.\n\n## v4.x to v5.x\n\nThe release `v5.0.0` is a modernised version of the library, which requires PHP 8.1+ and drops all the deprecated components.\n\nWe're adding a few deprecation annotations on the version `v4.3.0`.\nSo, before going to `v5.0.0` please update to the latest 4.3.x version using composer:\n\n```sh\ncomposer require lcobucci/jwt ^4.3\n```\n\nThen run your tests and change all calls to deprecated methods, even if they are not triggering any notices.\nTools like [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules) can help finding them.\n\n### Removal of `Ecdsa::create()`\n\nTo promote symmetry on the instantiation of all algorithms (signers), we're dropping the named constructor in favour of the constructor.\n\nIf you are using any variant of ECDSA, please change your code following this example:\n\n```diff\n <?php\n declare(strict_types=1);\n\n use Lcobucci\\JWT\\Configuration;\n use Lcobucci\\JWT\\Signer;\n use Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\n require 'vendor/autoload.php';\n\n $configuration = Configuration::forAsymmetricSigner(\n-    Signer\\Ecdsa\\Sha256::create(),\n+    new Signer\\Ecdsa\\Sha256(),\n     InMemory::file(__DIR__ . '/my-private-key.pem'),\n     InMemory::base64Encoded('mBC5v1sOKVvbdEitdSBenu59nfNfhwkedkJVNabosTw=')\n     // You may also override the JOSE encoder/decoder if needed\n     // by providing extra arguments here\n );\n```\n\n### Removal of `none` algorithm\n\nTo promote a more secure usage of the library and prevent misuse we decided to deviate from the RFC and drop `none`, which means that the following components are being removed:\n\n* `Lcobucci\\JWT\\Configuration::forUnsecuredSigner()`\n* `Lcobucci\\JWT\\Signer\\Key\\InMemory::empty()`\n* `Lcobucci\\JWT\\Signer\\None`\n* `Lcobucci\\JWT\\Token\\Signature::fromEmptyData()`\n\nIf you're relying on it and still want to have that on your system, please create your own implementation.\n\nIf you're using it because it's \"fast\", please look into adoption the non-standard Blake2b implementation:\n\n```diff\n <?php\n declare(strict_types=1);\n\n use Lcobucci\\JWT\\Configuration;\n use Lcobucci\\JWT\\Signer;\n use Lcobucci\\JWT\\Signer\\Key\\InMemory;\n\n require 'vendor/autoload.php';\n\n-$configuration = Configuration::forUnsecuredSigner();\n+$configuration = Configuration::forSymmetricSigner(\n+    new Signer\\Blake2b(),\n+    InMemory::base64Encoded('MpQd6dDPiqnzFSWmpUfLy4+Rdls90Ca4C8e0QD0IxqY=')\n+);\n```\n\n### `Builder` API is now `@immutable`\n\n`\\Lcobucci\\JWT\\Builder` interface alongside with its default implementation `\\Lcobucci\\JWT\\Token\\Builder` are now marked `@immutable`.\n\nIf you are using it for example with the `JwtFacade` ensure now to use the returned new `Builder` instance:\n\n```diff\n $token = (new JwtFacade())->issue(\n     new Sha256(),\n     $key,\n     static function (\n         Builder $builder,\n         DateTimeImmutable $now\n     ): Builder {\n-        $builder->issuedBy('https://api.my-awesome-app.io');\n-        $builder->permittedFor('https://client-app.io');\n-        $builder->expiresAt($now->modify('+10 minutes'));\n+        $builder = $builder->issuedBy('https://api.my-awesome-app.io');\n+        $builder = $builder->permittedFor('https://client-app.io');\n+        $builder = $builder->expiresAt($now->modify('+10 minutes'));\n\n         return $builder;\n     }\n );\n```\n\nOr:\n\n```diff\n $token = (new JwtFacade())->issue(\n     new Sha256(),\n     $key,\n-    static function (\n+    static fn (\n         Builder $builder,\n         DateTimeImmutable $now\n-    ): Builder {\n-        $builder->issuedBy('https://api.my-awesome-app.io');\n-        $builder->permittedFor('https://client-app.io');\n-        $builder->expiresAt($now->modify('+10 minutes'));\n-\n-         return $builder;\n-     }\n+    ): Builder => $builder->issuedBy('https://api.my-awesome-app.io')\n+        ->permittedFor('https://client-app.io')\n+        ->expiresAt($now->modify('+10 minutes'))\n );\n```\n\n### `lcobucci/clock` is not installed by default anymore\n\nThanks to [PSR-20](https://www.php-fig.org/psr/psr-20/), users can more easily plug-in other [clock implementations](https://packagist.org/providers/psr/clock-implementation) if they choose to do so.\n\nIf you like and were already using `lcobucci/clock` on your system, you're required to explicitly add it as a production dependency:\n\n```sh\ncomposer require lcobucci/clock\n```\n\n## v3.x to v4.x\n\nThe `v4.0.0` aggregates about 5 years of work and contains **several BC-breaks**.\nWe're building on the version `v3.4.0` a forward compatibility layer to help users to migrate to `v4.0.0`.\n\nTo help on the migration process, all deprecated components are being marked with `@deprecated` and deprecated behaviour will trigger a `E_USER_DEPRECATED` error.\nHowever, you can also find here the instructions on how to make your code compatible with both versions.\n\n### General migration strategy\n\nUpdate your existing software to the latest 3.4.x version using composer:\n\n```sh\ncomposer require lcobucci/jwt ^3.4\n```\n\nThen run your tests and fix all deprecation notices.\nAlso change all calls to deprecated methods, even if they are not triggering any notices.\nTools like [`phpstan/phpstan-deprecation-rules`](https://github.com/phpstan/phpstan-deprecation-rules) can help finding them.\n\nNote that PHPUnit tests will only fail if you have the `E_USER_DEPRECATED` error level activated - it is a good practice to run tests using `E_ALL`.\nData providers that trigger deprecation messages will not fail tests at all, only print the message to the console.\nMake sure you do not see any of them before you continue.\n\nNow you can upgrade to the latest 4.x version:\n\n```sh\ncomposer require lcobucci/jwt ^4.0\n```\n\nRemember that some deprecation messages from the 3.4 version may have notified you that things still are different in 4.0, so you may find you need to adapt your own code once more at this stage.\n\nWhile you are at it, consider using the new configuration object.\nThe details are listed below.\n\n### Inject the configuration object instead of builder/parser/key\n\nThis object serves as a small service locator that centralises the JWT-related dependencies.\nThe main goal is to simplify the injection of our components into downstream code.\n\nThis step is quite important to at least have a single way to initialise the JWT components, even if the configuration object is thrown away.\n\nCheck an example of how to migrate the injection of builder+signer+key to configuration object below: \n\n```diff\n <?php\n declare(strict_types=1);\n \n namespace Me\\MyApp\\Authentication;\n \n-use Lcobucci\\JWT\\Builder;\n use Lcobucci\\JWT\\Configuration;\n-use Lcobucci\\JWT\\Signer;\n-use Lcobucci\\JWT\\Signer\\Key;\n use Lcobucci\\JWT\\Token;\n \n use function bin2hex;\n use function random_bytes;\n \n final class JwtIssuer\n {\n-    private Builder $builder;\n-    private Signer $signer;\n-    private Key $key;\n- \n-    public function __construct(Builder $builder, Signer $signer, Key $key)\n-    {\n-        $this->builder = $builder;\n-        $this->signer  = $signer;\n-        $this->key     = $key;\n-    }   \n+    private Configuration $config;\n+    \n+    public function __construct(Configuration $config)\n+    {\n+        $this->config = $config;\n+    }\n    \n     public function issueToken(): Token\n     {\n-        return $this->builder\n+        return $this->config->builder()\n             ->identifiedBy(bin2hex(random_bytes(16)))\n-            ->getToken($this->signer, $this->key);\n+            ->getToken($this->config->signer(), $this->config->signingKey());\n     }\n }\n```\n\nYou can find more information on how to use the configuration object, [here](configuration.md).\n\n### Use new `Key` objects\n\n`Lcobucci\\JWT\\Signer\\Key` has been converted to an interface in `v4.0`.\n\nWe provide `Lcobucci\\JWT\\Signer\\Key\\InMemory`, a drop-in replacement of the behaviour for `Lcobucci\\JWT\\Signer\\Key` in `v3.x`.\nYou will need to pick the appropriated named constructor to migrate your code:\n\n```diff\n <?php\n declare(strict_types=1);\n \n namespace Me\\MyApp\\Authentication;\n \n-use Lcobucci\\JWT\\Signer\\Key;\n+use Lcobucci\\JWT\\Signer\\Key\\InMemory;\n-\n-use function base64_decode;\n \n // Key from plain text\n-$key = new Key('a-very-secure-key');\n+$key = InMemory::plainText('a-very-secure-key');\n \n // Key from base64 encoded string\n-$key = new Key(base64_decode('YS12ZXJ5LXNlY3VyZS1rZXk=', true));\n+$key = InMemory::base64Encoded('YS12ZXJ5LXNlY3VyZS1rZXk=');\n \n // Key from file contents\n-$key = new Key('file:///var/secrets/my-private-key.pem');\n+$key = InMemory::file('/var/secrets/my-private-key.pem');\n```\n\n### Use the new `Builder` API\n\nThere are 4 main differences on the new API:\n\n1. Token configuration methods were renamed\n1. Signature is created via `Builder#getToken()` (instead of `Builder#sign()`)\n1. `DateTimeImmutable` objects are now used for the registered claims with dates, which are by default encoded as floats with microseconds as precision\n1. Headers should be replicated manually - whenever necessary\n\nHere's the migration:\n\n```diff\n <?php\n declare(strict_types=1);\n \n namespace Me\\MyApp\\Authentication;\n\n+use DateTimeImmutable; \n-use Lcobucci\\JWT\\Builder;\n+use Lcobucci\\JWT\\Configuration;\n+use Lcobucci\\JWT\\Signer\\Key\\InMemory;\n use Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\n-\n-use function time;\n\n-$now = time();\n+$now = new DateTimeImmutable();\n+$config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText('testing'));\n\n-$token = (new Builder())\n+$token = $config->builder()\n-    ->setIssuer('http://example.com', true)\n+    ->issuedBy('http://example.com')\n+    ->withHeader('iss', 'http://example.com')\n-    ->setAudience('http://example.org')\n+    ->permittedFor('http://example.org')\n-    ->setId('4f1g23a12aa')\n+    ->identifiedBy('4f1g23a12aa')\n-    ->setSubject('user123')\n+    ->relatedTo('user123')\n-    ->setIssuedAt($now)\n+    ->issuedAt($now)\n-    ->setNotBefore($now + 60)\n+    ->canOnlyBeUsedAfter($now->modify('+1 minute'))\n-    ->setExpiration($now + 3600)\n+    ->expiresAt($now->modify('+1 hour'))\n-    ->set('uid', 1)\n+    ->withClaim('uid', 1)\n-    ->sign(new Sha256(), 'testing')\n-    ->getToken();\n+    ->getToken($config->signer(), $config->signingKey());\n```\n\n#### Date precision\n\nIf you want to continue using Unix timestamps, you can use the `withUnixTimestampDates()`-formatter:\n\n```diff\n<?php\n\n-$builder = new Builder());\n+$builder = $config->builder(ChainedFormatter::withUnixTimestampDates());\n```\n\n#### Support for multiple audiences\n\nEven though we didn't officially support multiple audiences, it was technically possible to achieve that by manually setting the `aud` claim to an array with multiple strings.\n\nIf you parse a token with 3.4, and read its contents with `\\Lcobucci\\JWT\\Token#getClaim()` or`\\Lcobucci\\JWT\\Token#getClaims()`, you will only get the first element of such an array back.\nIf the audience claim does only contain a string, or only contains one string in the array, nothing changes.\nPlease [upgrade to the new Token API](#use-the-new-token-api) for accessing claims in order to get the full audience array again (e.g. call `Token#claims()->get('aud')`).\n\nWhen creating a token, use the new method `Builder#permittedFor()` as detailed below.\n\n##### Multiple calls to `Builder#permittedFor()`\n\n```diff\n <?php\n declare(strict_types=1);\n \n namespace Me\\MyApp\\Authentication;\n\n-use Lcobucci\\JWT\\Builder;\n+use Lcobucci\\JWT\\Configuration;\n+use Lcobucci\\JWT\\Signer\\Key\\InMemory;\n use Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\n\n+$config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText('testing'));\n\n-$token = (new Builder())\n+$token = $config->builder()\n-    ->withClaim('aud', ['one', 'two', 'three'])\n+    ->permittedFor('one')\n+    ->permittedFor('two')\n+    ->permittedFor('three')\n-    ->sign(new Sha256(), 'testing')\n-    ->getToken();\n+    ->getToken($config->signer(), $config->signingKey());\n```\n\n##### Single call to `Builder#permittedFor()` with multiple arguments\n\n```diff\n <?php\n declare(strict_types=1);\n \n namespace Me\\MyApp\\Authentication;\n\n-use Lcobucci\\JWT\\Builder;\n+use Lcobucci\\JWT\\Configuration;\n+use Lcobucci\\JWT\\Signer\\Key\\InMemory;\n use Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\n\n+$config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText('testing'));\n\n-$token = (new Builder())\n+$token = $config->builder()\n-    ->withClaim('aud', ['one', 'two', 'three'])\n+    ->permittedFor('one', 'two', 'three')\n-    ->sign(new Sha256(), 'testing')\n-    ->getToken();\n+    ->getToken($config->signer(), $config->signingKey());\n```\n\n##### Single call to `Builder#permittedFor()` with argument unpacking\n\n```diff\n <?php\n declare(strict_types=1);\n \n namespace Me\\MyApp\\Authentication;\n\n-use Lcobucci\\JWT\\Builder;\n+use Lcobucci\\JWT\\Configuration;\n+use Lcobucci\\JWT\\Signer\\Key\\InMemory;\n use Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\n\n+$config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText('testing'));\n\n-$token = (new Builder())\n+$token = $config->builder()\n-    ->withClaim('aud', ['one', 'two', 'three'])\n+    ->permittedFor(...['one', 'two', 'three'])\n-    ->sign(new Sha256(), 'testing')\n-    ->getToken();\n+    ->getToken($config->signer(), $config->signingKey());\n```\n\n### Replace `Token#verify()` and `Token#validate()` with Validation API\n\nThese methods were quite limited and brings multiple responsibilities to the `Token` class.\nOn `v4.0` we provide another component to validate tokens, including their signature.\n\nHere's an example of how to modify that logic (considering [constraints have been configured](configuration.md#customisation)):\n\n```diff\n <?php\n declare(strict_types=1);\n \n namespace Me\\MyApp\\Authentication;\n \n use InvalidArgumentException;\n+use Lcobucci\\JWT\\Configuration;\n-use Lcobucci\\JWT\\Signer;\n-use Lcobucci\\JWT\\Signer\\Key;\n-use Lcobucci\\JWT\\Parser;\n-use Lcobucci\\JWT\\ValidationData;\n \n final class AuthenticateJwt\n {\n-    private Parser $parser;\n-    private Signer $signer;\n-    private Key $key;\n+    private Configuration $config;\n     \n-    public function __construct(Parser $parser, Signer $signer, Key $key)\n+    public function __construct(Configuration $config)\n     {\n-        $this->parser = $parser;\n-        $this->signer = $signer;\n-        $this->key    = $key;\n+        $this->config = $config;\n     }\n     \n     public function authenticate(string $jwt): void\n     {\n-        $token = $this->parser->parse($jwt);\n+        $token = $this->config->parser()->parse($jwt);\n         \n-        if (! $token->validate(new ValidationData()) || $token->verify($this->signer, $this->key)) {\n+        if (! $this->config->validator()->validate($token, ...$this->config->validationConstraints())) {\n             throw new InvalidArgumentException('Invalid token provided');\n         }\n     }\n }\n```\n\nCheck [here](validating-tokens.md) for more information on how to validate tokens and what are the built-in constraints.\n\n### Use the new `Token` API\n\nThere some important differences on this new API:\n\n1. We no longer use the `Lcobucci\\JWT\\Claim` objects\n1. Headers and claims are now represented as `Lcobucci\\JWT\\Token\\DataSet`\n1. Different methods should be used to retrieve a header/claim\n1. No exception is thrown when accessing missing header/claim, the default argument is always used\n1. Tokens should be explicitly casted to string via method\n\nYour code should be adapted to manipulate tokens like this:\n\n```diff\n <?php\n declare(strict_types=1);\n \n namespace Me\\MyApp\\Authentication;\n \n // we assume here that $token is a valid parsed/created token\n \n-$token->getHeaders() \n+$token->headers()->all()\n-$token->hasHeader('typ')\n+$token->headers()->has('typ')\n-$token->getHeader('typ')\n+$token->headers()->get('typ')\n\n-$token->getClaims() \n+$token->claims()->all()\n-$token->hasClaim('iss')\n+$token->claims()->has('iss')\n-$token->getClaim('iss')\n+$token->claims()->get('iss')\n\n-echo (string) $token;\n+echo $token->toString();\n```\n"
  },
  {
    "path": "docs/validating-tokens.md",
    "content": "# Validating tokens\n\nTo validate a token you must create a new validator and assert or validate a token.\n\n## Using `Lcobucci\\JWT\\Validator#assert()`\n\nThis method goes through every single constraint in the set, groups all the violations, and throws an exception with the grouped violations:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Token\\Parser;\nuse Lcobucci\\JWT\\Validation\\Constraint\\RelatedTo;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse Lcobucci\\JWT\\Validation\\Validator;\n\nrequire 'vendor/autoload.php';\n\n$parser = new Parser(new JoseEncoder());\n\n$token = $parser->parse(\n    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'\n    . 'eyJzdWIiOiIxMjM0NTY3ODkwIn0.'\n    . '2gSBz9EOsQRN9I-3iSxJoFt7NtgV6Rm0IL6a8CAwl3Q'\n);\n\n$validator = new Validator();\n\ntry {\n    $validator->assert($token, new RelatedTo('1234567891')); // doesn't throw an exception\n    $validator->assert($token, new RelatedTo('1234567890'));\n} catch (RequiredConstraintsViolated $e) {\n    // list of constraints violation exceptions:\n    var_dump($e->violations());\n}\n```\n\n## Using `Lcobucci\\JWT\\Validator#validate()`\n\nThe difference here is that we'll always get a `boolean` result and stop in the very first violation:\n\n```php\n<?php\ndeclare(strict_types=1);\n\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Token\\Parser;\nuse Lcobucci\\JWT\\Validation\\Constraint\\RelatedTo;\nuse Lcobucci\\JWT\\Validation\\Validator;\n\nrequire 'vendor/autoload.php';\n\n$parser = new Parser(new JoseEncoder());\n\n$token = $parser->parse(\n    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.'\n    . 'eyJzdWIiOiIxMjM0NTY3ODkwIn0.'\n    . '2gSBz9EOsQRN9I-3iSxJoFt7NtgV6Rm0IL6a8CAwl3Q'\n);\n\n$validator = new Validator();\n\nif (! $validator->validate($token, new RelatedTo('1234567891'))) {\n    echo 'Invalid token (1)!', PHP_EOL; // will print this\n}\n\nif (! $validator->validate($token, new RelatedTo('1234567890'))) {\n    echo 'Invalid token (2)!', PHP_EOL; // will not print this\n}\n```\n\n!!! Note\n    Some systems make use of components to handle dependency injection.\n    If your application follows that practice, using a [configuration object](configuration.md) might simplify the wiring of this library.\n\n\n## Available constraints\n\nThis library provides the following constraints:\n\n* `Lcobucci\\JWT\\Validation\\Constraint\\IdentifiedBy`: verifies if the claim `jti` matches the expected value\n* `Lcobucci\\JWT\\Validation\\Constraint\\IssuedBy`: verifies if the claim `iss` is listed as expected values\n* `Lcobucci\\JWT\\Validation\\Constraint\\PermittedFor`: verifies if the claim `aud` contains the expected value\n* `Lcobucci\\JWT\\Validation\\Constraint\\RelatedTo`: verifies if the claim `sub` matches the expected value\n* `Lcobucci\\JWT\\Validation\\Constraint\\SignedWith`: verifies if the token was signed with the expected signer and key\n* `Lcobucci\\JWT\\Validation\\Constraint\\SignedWithOneInSet`: verifies the token signature against multiple `SignedWithUntilDate` constraints\n* `Lcobucci\\JWT\\Validation\\Constraint\\SignedWithUntilDate`: verifies if the token was signed with the expected signer and key (until a certain date)\n* `Lcobucci\\JWT\\Validation\\Constraint\\StrictValidAt`: verifies presence and validity of the claims `iat`, `nbf`, and `exp` (supports leeway configuration)\n* `Lcobucci\\JWT\\Validation\\Constraint\\LooseValidAt`: verifies the claims `iat`, `nbf`, and `exp`, when present (supports leeway configuration)\n* `Lcobucci\\JWT\\Validation\\Constraint\\HasClaimWithValue`: verifies that a **custom claim** has the expected value (not recommended when comparing cryptographic hashes)\n* `Lcobucci\\JWT\\Validation\\Constraint\\HasClaim`: verifies that a **custom claim** is present\n\nYou may also create your [own validation constraints](extending-the-library.md#validation-constraints).\n"
  },
  {
    "path": "infection.json.dist",
    "content": "{\n    \"source\": {\n        \"directories\": [\"src\"]\n    },\n    \"timeout\": 3,\n    \"logs\": {\n        \"text\": \"infection.txt\"\n    },\n    \"mutators\": {\n        \"@default\": true,\n        \"@function_signature\": true,\n        \"CastInt\": {\n            \"ignore\": [\n                \"Lcobucci\\\\JWT\\\\Signer\\\\Ecdsa\\\\MultibyteStringConverter::octetLength\",\n                \"Lcobucci\\\\JWT\\\\Signer\\\\Ecdsa\\\\MultibyteStringConverter::readAsn1Integer\"\n            ]\n        },\n        \"UnwrapSubstr\": {\n            \"ignore\": [\n                \"Lcobucci\\\\JWT\\\\Signer\\\\Ecdsa\\\\MultibyteStringConverter::preparePositiveInteger\"\n            ]\n        },\n        \"GreaterThan\": {\n            \"ignore\": [\n                \"Lcobucci\\\\JWT\\\\Signer\\\\Ecdsa\\\\MultibyteStringConverter::toAsn1\",\n                \"Lcobucci\\\\JWT\\\\Signer\\\\Ecdsa\\\\MultibyteStringConverter::preparePositiveInteger\",\n                \"Lcobucci\\\\JWT\\\\Signer\\\\Ecdsa\\\\MultibyteStringConverter::retrievePositiveInteger\"\n            ]\n        },\n        \"LessThanOrEqualTo\": {\n            \"ignore\": [\n                \"Lcobucci\\\\JWT\\\\Signer\\\\Ecdsa\\\\MultibyteStringConverter::preparePositiveInteger\"\n            ]\n        },\n        \"LogicalNot\": {\n            \"ignoreSourceCodeByRegex\": [\n                \"if \\\\(! function_exists\\\\('sodium_\\\\w+'\\\\)\\\\) \\\\{\"\n            ]\n        }\n    },\n    \"minMsi\": 100,\n    \"minCoveredMsi\": 100\n}\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: lcobucci/jwt\ntheme: readthedocs\n\nnav:\n  - Intro:\n    - 'index.md'\n    - 'installation.md'\n    - 'quick-start.md'\n    - 'supported-algorithms.md'\n  - Usage:\n    - 'issuing-tokens.md'\n    - 'parsing-tokens.md'\n    - 'validating-tokens.md'\n    - 'configuration.md'\n  - Guides:\n    - 'extending-the-library.md'\n    - 'rotating-keys.md'\n    - 'upgrading.md'\n\nmarkdown_extensions:\n  - admonition\n  - footnotes\n  - toc:\n      permalink: true\n"
  },
  {
    "path": "phpbench.json",
    "content": "{\n    \"runner.bootstrap\": \"vendor/autoload.php\",\n    \"runner.path\": \"tests/Benchmark\"\n}\n"
  },
  {
    "path": "phpcs.xml.dist",
    "content": "<?xml version=\"1.0\"?>\n<ruleset\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noNamespaceSchemaLocation=\"vendor/squizlabs/php_codesniffer/phpcs.xsd\"\n>\n    <arg name=\"basepath\" value=\".\" />\n    <arg name=\"extensions\" value=\"php\" />\n    <arg name=\"parallel\" value=\"80\" />\n    <arg name=\"colors\" />\n    <arg name=\"cache\" value=\".phpcs.cache\" />\n    <arg value=\"p\" />\n\n    <file>src</file>\n    <file>tests</file>\n\n    <rule ref=\"Lcobucci\" />\n\n    <rule ref=\"SlevomatCodingStandard.Functions.UnusedParameter\">\n        <exclude-pattern>tests</exclude-pattern>\n    </rule>\n</ruleset>\n\n"
  },
  {
    "path": "phpstan.neon.dist",
    "content": "parameters:\n    level: 8 # not yet ready for all the `mixed` checks\n    paths:\n        - src\n        - tests\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n    colors=\"true\"\n    beStrictAboutOutputDuringTests=\"true\"\n    beStrictAboutChangesToGlobalState=\"true\"\n    beStrictAboutCoverageMetadata=\"true\"\n    beStrictAboutTestsThatDoNotTestAnything=\"true\"\n    requireCoverageMetadata=\"true\"\n    failOnAllIssues=\"true\"\n    displayDetailsOnAllIssues=\"true\"\n    cacheDirectory=\".phpunit.cache\"\n>\n    <testsuites>\n        <testsuite name=\"tests\">\n            <directory>tests</directory>\n        </testsuite>\n    </testsuites>\n    <source>\n        <include>\n            <directory>src</directory>\n        </include>\n    </source>\n</phpunit>\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n    \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n    \"extends\": [\n        \"local>lcobucci/.github:renovate-config\"\n    ]\n}\n"
  },
  {
    "path": "src/Builder.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Encoding\\CannotEncodeContent;\nuse Lcobucci\\JWT\\Signer\\CannotSignPayload;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\ConversionFailed;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Token\\RegisteredClaimGiven;\nuse NoDiscard;\n\n/** @immutable */\ninterface Builder\n{\n    /**\n     * Appends new items to audience\n     *\n     * @param non-empty-string ...$audiences\n     */\n    #[NoDiscard]\n    public function permittedFor(string ...$audiences): Builder;\n\n    /**\n     * Configures the expiration time\n     */\n    #[NoDiscard]\n    public function expiresAt(DateTimeImmutable $expiration): Builder;\n\n    /**\n     * Configures the token id\n     *\n     * @param non-empty-string $id\n     */\n    #[NoDiscard]\n    public function identifiedBy(string $id): Builder;\n\n    /**\n     * Configures the time that the token was issued\n     */\n    #[NoDiscard]\n    public function issuedAt(DateTimeImmutable $issuedAt): Builder;\n\n    /**\n     * Configures the issuer\n     *\n     * @param non-empty-string $issuer\n     */\n    #[NoDiscard]\n    public function issuedBy(string $issuer): Builder;\n\n    /**\n     * Configures the time before which the token cannot be accepted\n     */\n    #[NoDiscard]\n    public function canOnlyBeUsedAfter(DateTimeImmutable $notBefore): Builder;\n\n    /**\n     * Configures the subject\n     *\n     * @param non-empty-string $subject\n     */\n    #[NoDiscard]\n    public function relatedTo(string $subject): Builder;\n\n    /**\n     * Configures a header item\n     *\n     * @param non-empty-string $name\n     */\n    #[NoDiscard]\n    public function withHeader(string $name, mixed $value): Builder;\n\n    /**\n     * Configures a claim item\n     *\n     * @param non-empty-string $name\n     *\n     * @throws RegisteredClaimGiven When trying to set a registered claim.\n     */\n    #[NoDiscard]\n    public function withClaim(string $name, mixed $value): Builder;\n\n    /**\n     * Returns a signed token to be used\n     *\n     * @throws CannotEncodeContent When data cannot be converted to JSON.\n     * @throws CannotSignPayload   When payload signing fails.\n     * @throws InvalidKeyProvided  When issue key is invalid/incompatible.\n     * @throws ConversionFailed    When signature could not be converted.\n     */\n    #[NoDiscard]\n    public function getToken(Signer $signer, Key $key): UnencryptedToken;\n}\n"
  },
  {
    "path": "src/ClaimsFormatter.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse NoDiscard;\n\ninterface ClaimsFormatter\n{\n    /**\n     * @param array<non-empty-string, mixed> $claims\n     *\n     * @return array<non-empty-string, mixed>\n     */\n    #[NoDiscard]\n    public function formatClaims(array $claims): array;\n}\n"
  },
  {
    "path": "src/Configuration.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse Closure;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse NoDiscard;\n\n/**\n * Configuration container for the JWT Builder and Parser\n *\n * Serves like a small DI container to simplify the creation and usage\n * of the objects.\n */\nfinal readonly class Configuration\n{\n    private Parser $parser;\n    private Validator $validator;\n\n    /** @var Closure(ClaimsFormatter $claimFormatter): Builder */\n    private Closure $builderFactory;\n\n    /** @var Constraint[] */\n    private array $validationConstraints;\n\n    /** @param Closure(ClaimsFormatter $claimFormatter): Builder|null $builderFactory */\n    private function __construct(\n        private Signer $signer,\n        private Key $signingKey,\n        private Key $verificationKey,\n        private Encoder $encoder,\n        private Decoder $decoder,\n        ?Parser $parser,\n        ?Validator $validator,\n        ?Closure $builderFactory,\n        Constraint ...$validationConstraints,\n    ) {\n        $this->parser    = $parser ?? new Token\\Parser($decoder);\n        $this->validator = $validator ?? new Validation\\Validator();\n\n        $this->builderFactory = $builderFactory\n            ?? static function (ClaimsFormatter $claimFormatter) use ($encoder): Builder {\n                return Token\\Builder::new($encoder, $claimFormatter);\n            };\n\n        $this->validationConstraints = $validationConstraints;\n    }\n\n    #[NoDiscard]\n    public static function forAsymmetricSigner(\n        Signer $signer,\n        Key $signingKey,\n        Key $verificationKey,\n        Encoder $encoder = new JoseEncoder(),\n        Decoder $decoder = new JoseEncoder(),\n    ): self {\n        return new self(\n            $signer,\n            $signingKey,\n            $verificationKey,\n            $encoder,\n            $decoder,\n            null,\n            null,\n            null,\n        );\n    }\n\n    #[NoDiscard]\n    public static function forSymmetricSigner(\n        Signer $signer,\n        Key $key,\n        Encoder $encoder = new JoseEncoder(),\n        Decoder $decoder = new JoseEncoder(),\n    ): self {\n        return new self(\n            $signer,\n            $key,\n            $key,\n            $encoder,\n            $decoder,\n            null,\n            null,\n            null,\n        );\n    }\n\n    /** @param callable(ClaimsFormatter): Builder $builderFactory */\n    #[NoDiscard]\n    public function withBuilderFactory(callable $builderFactory): self\n    {\n        return new self(\n            $this->signer,\n            $this->signingKey,\n            $this->verificationKey,\n            $this->encoder,\n            $this->decoder,\n            $this->parser,\n            $this->validator,\n            $builderFactory(...),\n            ...$this->validationConstraints,\n        );\n    }\n\n    public function builder(?ClaimsFormatter $claimFormatter = null): Builder\n    {\n        return ($this->builderFactory)($claimFormatter ?? ChainedFormatter::default());\n    }\n\n    public function parser(): Parser\n    {\n        return $this->parser;\n    }\n\n    #[NoDiscard]\n    public function withParser(Parser $parser): self\n    {\n        return new self(\n            $this->signer,\n            $this->signingKey,\n            $this->verificationKey,\n            $this->encoder,\n            $this->decoder,\n            $parser,\n            $this->validator,\n            $this->builderFactory,\n            ...$this->validationConstraints,\n        );\n    }\n\n    public function signer(): Signer\n    {\n        return $this->signer;\n    }\n\n    public function signingKey(): Key\n    {\n        return $this->signingKey;\n    }\n\n    public function verificationKey(): Key\n    {\n        return $this->verificationKey;\n    }\n\n    public function validator(): Validator\n    {\n        return $this->validator;\n    }\n\n    #[NoDiscard]\n    public function withValidator(Validator $validator): self\n    {\n        return new self(\n            $this->signer,\n            $this->signingKey,\n            $this->verificationKey,\n            $this->encoder,\n            $this->decoder,\n            $this->parser,\n            $validator,\n            $this->builderFactory,\n            ...$this->validationConstraints,\n        );\n    }\n\n    /** @return Constraint[] */\n    public function validationConstraints(): array\n    {\n        return $this->validationConstraints;\n    }\n\n    #[NoDiscard]\n    public function withValidationConstraints(Constraint ...$validationConstraints): self\n    {\n        return new self(\n            $this->signer,\n            $this->signingKey,\n            $this->verificationKey,\n            $this->encoder,\n            $this->decoder,\n            $this->parser,\n            $this->validator,\n            $this->builderFactory,\n            ...$validationConstraints,\n        );\n    }\n}\n"
  },
  {
    "path": "src/Decoder.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse Lcobucci\\JWT\\Encoding\\CannotDecodeContent;\nuse NoDiscard;\n\ninterface Decoder\n{\n    /**\n     * Decodes from JSON, validating the errors\n     *\n     * @param non-empty-string $json\n     *\n     * @throws CannotDecodeContent When something goes wrong while decoding.\n     */\n    #[NoDiscard]\n    public function jsonDecode(string $json): mixed;\n\n    /**\n     * Decodes from Base64URL\n     *\n     * @link http://tools.ietf.org/html/rfc4648#section-5\n     *\n     * @return ($data is non-empty-string ? non-empty-string : string)\n     *\n     * @throws CannotDecodeContent When something goes wrong while decoding.\n     */\n    #[NoDiscard]\n    public function base64UrlDecode(string $data): string;\n}\n"
  },
  {
    "path": "src/Encoder.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse Lcobucci\\JWT\\Encoding\\CannotEncodeContent;\nuse NoDiscard;\n\ninterface Encoder\n{\n    /**\n     * Encodes to JSON, validating the errors\n     *\n     * @return non-empty-string\n     *\n     * @throws CannotEncodeContent When something goes wrong while encoding.\n     */\n    #[NoDiscard]\n    public function jsonEncode(mixed $data): string;\n\n    /**\n     * Encodes to base64url\n     *\n     * @link http://tools.ietf.org/html/rfc4648#section-5\n     *\n     * @return ($data is non-empty-string ? non-empty-string : string)\n     */\n    #[NoDiscard]\n    public function base64UrlEncode(string $data): string;\n}\n"
  },
  {
    "path": "src/Encoding/CannotDecodeContent.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Encoding;\n\nuse JsonException;\nuse Lcobucci\\JWT\\Exception;\nuse RuntimeException;\n\nfinal class CannotDecodeContent extends RuntimeException implements Exception\n{\n    public static function jsonIssues(JsonException $previous): self\n    {\n        return new self(message: 'Error while decoding from JSON', previous: $previous);\n    }\n\n    public static function invalidBase64String(): self\n    {\n        return new self('Error while decoding from Base64Url, invalid base64 characters detected');\n    }\n}\n"
  },
  {
    "path": "src/Encoding/CannotEncodeContent.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Encoding;\n\nuse JsonException;\nuse Lcobucci\\JWT\\Exception;\nuse RuntimeException;\n\nfinal class CannotEncodeContent extends RuntimeException implements Exception\n{\n    public static function jsonIssues(JsonException $previous): self\n    {\n        return new self(message: 'Error while encoding to JSON', previous: $previous);\n    }\n}\n"
  },
  {
    "path": "src/Encoding/ChainedFormatter.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Encoding;\n\nuse Lcobucci\\JWT\\ClaimsFormatter;\n\nfinal readonly class ChainedFormatter implements ClaimsFormatter\n{\n    /** @var array<ClaimsFormatter> */\n    private array $formatters;\n\n    public function __construct(ClaimsFormatter ...$formatters)\n    {\n        $this->formatters = $formatters;\n    }\n\n    public static function default(): self\n    {\n        return new self(new UnifyAudience(), new MicrosecondBasedDateConversion());\n    }\n\n    public static function withUnixTimestampDates(): self\n    {\n        return new self(new UnifyAudience(), new UnixTimestampDates());\n    }\n\n    /** @inheritdoc */\n    public function formatClaims(array $claims): array\n    {\n        foreach ($this->formatters as $formatter) {\n            $claims = $formatter->formatClaims($claims);\n        }\n\n        return $claims;\n    }\n}\n"
  },
  {
    "path": "src/Encoding/JoseEncoder.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Encoding;\n\nuse JsonException;\nuse Lcobucci\\JWT\\Decoder;\nuse Lcobucci\\JWT\\Encoder;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\n\nuse function json_decode;\nuse function json_encode;\n\nuse const JSON_THROW_ON_ERROR;\nuse const JSON_UNESCAPED_SLASHES;\nuse const JSON_UNESCAPED_UNICODE;\n\n/**\n * A utilitarian class that encodes and decodes data according to JOSE specifications\n */\nfinal readonly class JoseEncoder implements Encoder, Decoder\n{\n    public function jsonEncode(mixed $data): string\n    {\n        try {\n            return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);\n        } catch (JsonException $exception) {\n            throw CannotEncodeContent::jsonIssues($exception);\n        }\n    }\n\n    public function jsonDecode(string $json): mixed\n    {\n        try {\n            return json_decode(json: $json, associative: true, flags: JSON_THROW_ON_ERROR);\n        } catch (JsonException $exception) {\n            throw CannotDecodeContent::jsonIssues($exception);\n        }\n    }\n\n    public function base64UrlEncode(string $data): string\n    {\n        return SodiumBase64Polyfill::bin2base64(\n            $data,\n            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,\n        );\n    }\n\n    public function base64UrlDecode(string $data): string\n    {\n        return SodiumBase64Polyfill::base642bin(\n            $data,\n            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,\n        );\n    }\n}\n"
  },
  {
    "path": "src/Encoding/MicrosecondBasedDateConversion.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Encoding;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\ClaimsFormatter;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\n\nuse function array_key_exists;\n\nfinal readonly class MicrosecondBasedDateConversion implements ClaimsFormatter\n{\n    /** @inheritdoc */\n    public function formatClaims(array $claims): array\n    {\n        foreach (RegisteredClaims::DATE_CLAIMS as $claim) {\n            if (! array_key_exists($claim, $claims)) {\n                continue;\n            }\n\n            $claims[$claim] = $this->convertDate($claims[$claim]);\n        }\n\n        return $claims;\n    }\n\n    private function convertDate(DateTimeImmutable $date): int|float\n    {\n        if ($date->format('u') === '000000') {\n            return (int) $date->format('U');\n        }\n\n        return (float) $date->format('U.u');\n    }\n}\n"
  },
  {
    "path": "src/Encoding/UnifyAudience.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Encoding;\n\nuse Lcobucci\\JWT\\ClaimsFormatter;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\n\nuse function array_key_exists;\nuse function count;\nuse function current;\n\nfinal readonly class UnifyAudience implements ClaimsFormatter\n{\n    /** @inheritdoc */\n    public function formatClaims(array $claims): array\n    {\n        if (\n            ! array_key_exists(RegisteredClaims::AUDIENCE, $claims)\n            || count($claims[RegisteredClaims::AUDIENCE]) !== 1\n        ) {\n            return $claims;\n        }\n\n        $claims[RegisteredClaims::AUDIENCE] = current($claims[RegisteredClaims::AUDIENCE]);\n\n        return $claims;\n    }\n}\n"
  },
  {
    "path": "src/Encoding/UnixTimestampDates.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Encoding;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\ClaimsFormatter;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\n\nuse function array_key_exists;\n\nfinal readonly class UnixTimestampDates implements ClaimsFormatter\n{\n    /** @inheritdoc */\n    public function formatClaims(array $claims): array\n    {\n        foreach (RegisteredClaims::DATE_CLAIMS as $claim) {\n            if (! array_key_exists($claim, $claims)) {\n                continue;\n            }\n\n            $claims[$claim] = $this->convertDate($claims[$claim]);\n        }\n\n        return $claims;\n    }\n\n    private function convertDate(DateTimeImmutable $date): int\n    {\n        return $date->getTimestamp();\n    }\n}\n"
  },
  {
    "path": "src/Exception.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse Throwable;\n\ninterface Exception extends Throwable\n{\n}\n"
  },
  {
    "path": "src/JwtFacade.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse Closure;\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\ValidAt;\nuse Lcobucci\\JWT\\Validation\\Validator;\nuse NoDiscard;\nuse Psr\\Clock\\ClockInterface as Clock;\n\nuse function assert;\n\nfinal readonly class JwtFacade\n{\n    private Clock $clock;\n\n    public function __construct(\n        private Parser $parser = new Token\\Parser(new JoseEncoder()),\n        ?Clock $clock = null,\n    ) {\n        $this->clock = $clock ?? new class implements Clock {\n            public function now(): DateTimeImmutable\n            {\n                return new DateTimeImmutable();\n            }\n        };\n    }\n\n    /** @param Closure(Builder, DateTimeImmutable):Builder $customiseBuilder */\n    #[NoDiscard]\n    public function issue(\n        Signer $signer,\n        Key $signingKey,\n        Closure $customiseBuilder,\n    ): UnencryptedToken {\n        $builder = Token\\Builder::new(new JoseEncoder(), ChainedFormatter::withUnixTimestampDates());\n\n        $now     = $this->clock->now();\n        $builder = $builder\n            ->issuedAt($now)\n            ->canOnlyBeUsedAfter($now)\n            ->expiresAt($now->modify('+5 minutes'));\n\n        return $customiseBuilder($builder, $now)->getToken($signer, $signingKey);\n    }\n\n    /** @param non-empty-string $jwt */\n    #[NoDiscard]\n    public function parse(\n        string $jwt,\n        SignedWith $signedWith,\n        ValidAt $validAt,\n        Constraint ...$constraints,\n    ): UnencryptedToken {\n        $token = $this->parser->parse($jwt);\n        assert($token instanceof UnencryptedToken);\n\n        (new Validator())->assert(\n            $token,\n            $signedWith,\n            $validAt,\n            ...$constraints,\n        );\n\n        return $token;\n    }\n}\n"
  },
  {
    "path": "src/Parser.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse Lcobucci\\JWT\\Encoding\\CannotDecodeContent;\nuse Lcobucci\\JWT\\Token\\InvalidTokenStructure;\nuse Lcobucci\\JWT\\Token\\UnsupportedHeaderFound;\nuse NoDiscard;\n\ninterface Parser\n{\n    /**\n     * Parses the JWT and returns a token\n     *\n     * @param non-empty-string $jwt\n     *\n     * @throws CannotDecodeContent      When something goes wrong while decoding.\n     * @throws InvalidTokenStructure    When token string structure is invalid.\n     * @throws UnsupportedHeaderFound   When parsed token has an unsupported header.\n     */\n    #[NoDiscard]\n    public function parse(string $jwt): Token;\n}\n"
  },
  {
    "path": "src/Signer/Blake2b.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer;\n\nuse Lcobucci\\JWT\\Signer;\n\nuse function hash_equals;\nuse function sodium_crypto_generichash;\nuse function strlen;\n\nfinal readonly class Blake2b implements Signer\n{\n    private const int MINIMUM_KEY_LENGTH_IN_BITS = 256;\n\n    public function algorithmId(): string\n    {\n        return 'BLAKE2B';\n    }\n\n    public function sign(string $payload, Key $key): string\n    {\n        $actualKeyLength = 8 * strlen($key->contents());\n\n        if ($actualKeyLength < self::MINIMUM_KEY_LENGTH_IN_BITS) {\n            throw InvalidKeyProvided::tooShort(self::MINIMUM_KEY_LENGTH_IN_BITS, $actualKeyLength);\n        }\n\n        return sodium_crypto_generichash($payload, $key->contents());\n    }\n\n    public function verify(string $expected, string $payload, Key $key): bool\n    {\n        return hash_equals($expected, $this->sign($payload, $key));\n    }\n}\n"
  },
  {
    "path": "src/Signer/CannotSignPayload.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer;\n\nuse InvalidArgumentException;\nuse Lcobucci\\JWT\\Exception;\n\nfinal class CannotSignPayload extends InvalidArgumentException implements Exception\n{\n    public static function errorHappened(string $error): self\n    {\n        return new self('There was an error while creating the signature:' . $error);\n    }\n}\n"
  },
  {
    "path": "src/Signer/Ecdsa/ConversionFailed.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Ecdsa;\n\nuse InvalidArgumentException;\nuse Lcobucci\\JWT\\Exception;\n\nfinal class ConversionFailed extends InvalidArgumentException implements Exception\n{\n    public static function invalidLength(): self\n    {\n        return new self('Invalid signature length.');\n    }\n\n    public static function incorrectStartSequence(): self\n    {\n        return new self('Invalid data. Should start with a sequence.');\n    }\n\n    public static function integerExpected(): self\n    {\n        return new self('Invalid data. Should contain an integer.');\n    }\n}\n"
  },
  {
    "path": "src/Signer/Ecdsa/MultibyteStringConverter.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2014-2018 Spomky-Labs\n *\n * This software may be modified and distributed under the terms\n * of the MIT license.  See the LICENSE file for details.\n *\n * @link https://github.com/web-token/jwt-framework/blob/v1.2/src/Component/Core/Util/ECSignature.php\n */\n\nnamespace Lcobucci\\JWT\\Signer\\Ecdsa;\n\nuse function assert;\nuse function bin2hex;\nuse function dechex;\nuse function hex2bin;\nuse function hexdec;\nuse function is_string;\nuse function str_pad;\nuse function strlen;\nuse function substr;\n\nuse const STR_PAD_LEFT;\n\n/**\n * ECDSA signature converter using ext-mbstring\n *\n * @internal\n */\nfinal readonly class MultibyteStringConverter implements SignatureConverter\n{\n    private const string ASN1_SEQUENCE          = '30';\n    private const string ASN1_INTEGER           = '02';\n    private const int ASN1_MAX_SINGLE_BYTE      = 128;\n    private const string ASN1_LENGTH_2BYTES     = '81';\n    private const string ASN1_BIG_INTEGER_LIMIT = '7f';\n    private const string ASN1_NEGATIVE_INTEGER  = '00';\n    private const int BYTE_SIZE                 = 2;\n\n    public function toAsn1(string $points, int $length): string\n    {\n        $points = bin2hex($points);\n\n        if (self::octetLength($points) !== $length) {\n            throw ConversionFailed::invalidLength();\n        }\n\n        $pointR = self::preparePositiveInteger(substr($points, 0, $length));\n        $pointS = self::preparePositiveInteger(substr($points, $length, null));\n\n        $lengthR = self::octetLength($pointR);\n        $lengthS = self::octetLength($pointS);\n\n        $totalLength  = $lengthR + $lengthS + self::BYTE_SIZE + self::BYTE_SIZE;\n        $lengthPrefix = $totalLength > self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : '';\n\n        $asn1 = hex2bin(\n            self::ASN1_SEQUENCE\n            . $lengthPrefix . dechex($totalLength)\n            . self::ASN1_INTEGER . dechex($lengthR) . $pointR\n            . self::ASN1_INTEGER . dechex($lengthS) . $pointS,\n        );\n        assert(is_string($asn1));\n        assert($asn1 !== '');\n\n        return $asn1;\n    }\n\n    private static function octetLength(string $data): int\n    {\n        return (int) (strlen($data) / self::BYTE_SIZE);\n    }\n\n    private static function preparePositiveInteger(string $data): string\n    {\n        if (substr($data, 0, self::BYTE_SIZE) > self::ASN1_BIG_INTEGER_LIMIT) {\n            return self::ASN1_NEGATIVE_INTEGER . $data;\n        }\n\n        while (\n            substr($data, 0, self::BYTE_SIZE) === self::ASN1_NEGATIVE_INTEGER\n            && substr($data, 2, self::BYTE_SIZE) <= self::ASN1_BIG_INTEGER_LIMIT\n        ) {\n            $data = substr($data, 2, null);\n        }\n\n        return $data;\n    }\n\n    public function fromAsn1(string $signature, int $length): string\n    {\n        $message  = bin2hex($signature);\n        $position = 0;\n\n        if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_SEQUENCE) {\n            throw ConversionFailed::incorrectStartSequence();\n        }\n\n        // @phpstan-ignore-next-line\n        if (self::readAsn1Content($message, $position, self::BYTE_SIZE) === self::ASN1_LENGTH_2BYTES) {\n            $position += self::BYTE_SIZE;\n        }\n\n        $pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));\n        $pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));\n\n        $points = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT) . str_pad($pointS, $length, '0', STR_PAD_LEFT));\n        assert(is_string($points));\n        assert($points !== '');\n\n        return $points;\n    }\n\n    private static function readAsn1Content(string $message, int &$position, int $length): string\n    {\n        $content   = substr($message, $position, $length);\n        $position += $length;\n\n        return $content;\n    }\n\n    private static function readAsn1Integer(string $message, int &$position): string\n    {\n        if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_INTEGER) {\n            throw ConversionFailed::integerExpected();\n        }\n\n        $length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE));\n\n        return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE);\n    }\n\n    private static function retrievePositiveInteger(string $data): string\n    {\n        while (\n            substr($data, 0, self::BYTE_SIZE) === self::ASN1_NEGATIVE_INTEGER\n            && substr($data, 2, self::BYTE_SIZE) > self::ASN1_BIG_INTEGER_LIMIT\n        ) {\n            $data = substr($data, 2, null);\n        }\n\n        return $data;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Ecdsa/Sha256.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Ecdsa;\n\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\n\nuse const OPENSSL_ALGO_SHA256;\n\nfinal readonly class Sha256 extends Ecdsa\n{\n    public function algorithmId(): string\n    {\n        return 'ES256';\n    }\n\n    public function algorithm(): int\n    {\n        return OPENSSL_ALGO_SHA256;\n    }\n\n    public function pointLength(): int\n    {\n        return 64;\n    }\n\n    public function expectedKeyLength(): int\n    {\n        return 256;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Ecdsa/Sha384.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Ecdsa;\n\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\n\nuse const OPENSSL_ALGO_SHA384;\n\nfinal readonly class Sha384 extends Ecdsa\n{\n    public function algorithmId(): string\n    {\n        return 'ES384';\n    }\n\n    public function algorithm(): int\n    {\n        return OPENSSL_ALGO_SHA384;\n    }\n\n    public function pointLength(): int\n    {\n        return 96;\n    }\n\n    public function expectedKeyLength(): int\n    {\n        return 384;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Ecdsa/Sha512.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Ecdsa;\n\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\n\nuse const OPENSSL_ALGO_SHA512;\n\nfinal readonly class Sha512 extends Ecdsa\n{\n    public function algorithmId(): string\n    {\n        return 'ES512';\n    }\n\n    public function algorithm(): int\n    {\n        return OPENSSL_ALGO_SHA512;\n    }\n\n    public function pointLength(): int\n    {\n        return 132;\n    }\n\n    public function expectedKeyLength(): int\n    {\n        // ES512 means ECDSA using P-521 and SHA-512.\n        // The key size is indeed 521 bits.\n        return 521;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Ecdsa/SignatureConverter.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Ecdsa;\n\n/**\n * Manipulates the result of a ECDSA signature (points R and S) according to the\n * JWA specs.\n *\n * OpenSSL creates a signature using the ASN.1 format and, according the JWA specs,\n * the signature for JWTs must be the concatenated values of points R and S (in\n * big-endian octet order).\n *\n * @internal\n *\n * @see https://tools.ietf.org/html/rfc7518#page-9\n * @see https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One\n */\ninterface SignatureConverter\n{\n    /**\n     * Converts the signature generated by OpenSSL into what JWA defines\n     *\n     * @return non-empty-string\n     *\n     * @throws ConversionFailed When there was an issue during the format conversion.\n     */\n    public function fromAsn1(string $signature, int $length): string;\n\n    /**\n     * Converts the JWA signature into something OpenSSL understands\n     *\n     * @param non-empty-string $points\n     *\n     * @return non-empty-string\n     *\n     * @throws ConversionFailed When there was an issue during the format conversion.\n     */\n    public function toAsn1(string $points, int $length): string;\n}\n"
  },
  {
    "path": "src/Signer/Ecdsa.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer;\n\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\MultibyteStringConverter;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\SignatureConverter;\n\nuse const OPENSSL_KEYTYPE_EC;\n\nabstract readonly class Ecdsa extends OpenSSL\n{\n    public function __construct(\n        private SignatureConverter $converter = new MultibyteStringConverter(),\n    ) {\n    }\n\n    final public function sign(string $payload, Key $key): string\n    {\n        return $this->converter->fromAsn1(\n            $this->createSignature($key, $payload),\n            $this->pointLength(),\n        );\n    }\n\n    final public function verify(string $expected, string $payload, Key $key): bool\n    {\n        return $this->verifySignature(\n            $this->converter->toAsn1($expected, $this->pointLength()),\n            $payload,\n            $key,\n        );\n    }\n\n    /** {@inheritDoc} */\n    final protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void\n    {\n        if ($type !== OPENSSL_KEYTYPE_EC) {\n            throw InvalidKeyProvided::incompatibleKeyType(\n                self::KEY_TYPE_MAP[OPENSSL_KEYTYPE_EC],\n                self::KEY_TYPE_MAP[$type] ?? 'unknown',\n            );\n        }\n\n        $expectedKeyLength = $this->expectedKeyLength();\n\n        if ($lengthInBits !== $expectedKeyLength) {\n            throw InvalidKeyProvided::incompatibleKeyLength($expectedKeyLength, $lengthInBits);\n        }\n    }\n\n    /**\n     * @internal\n     *\n     * @return positive-int\n     */\n    abstract public function expectedKeyLength(): int;\n\n    /**\n     * Returns the length of each point in the signature, so that we can calculate and verify R and S points properly\n     *\n     * @internal\n     *\n     * @return positive-int\n     */\n    abstract public function pointLength(): int;\n}\n"
  },
  {
    "path": "src/Signer/Eddsa.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer;\n\nuse Lcobucci\\JWT\\Signer;\nuse SodiumException;\n\nuse function sodium_crypto_sign_detached;\nuse function sodium_crypto_sign_verify_detached;\n\nfinal readonly class Eddsa implements Signer\n{\n    public function algorithmId(): string\n    {\n        return 'EdDSA';\n    }\n\n    public function sign(string $payload, Key $key): string\n    {\n        try {\n            return sodium_crypto_sign_detached($payload, $key->contents());\n        } catch (SodiumException $sodiumException) {\n            throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException);\n        }\n    }\n\n    public function verify(string $expected, string $payload, Key $key): bool\n    {\n        try {\n            return sodium_crypto_sign_verify_detached($expected, $payload, $key->contents());\n        } catch (SodiumException $sodiumException) {\n            throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Signer/Hmac/Sha256.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Hmac;\n\nuse Lcobucci\\JWT\\Signer\\Hmac;\n\nfinal readonly class Sha256 extends Hmac\n{\n    public function algorithmId(): string\n    {\n        return 'HS256';\n    }\n\n    public function algorithm(): string\n    {\n        return 'sha256';\n    }\n\n    public function minimumBitsLengthForKey(): int\n    {\n        return 256;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Hmac/Sha384.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Hmac;\n\nuse Lcobucci\\JWT\\Signer\\Hmac;\n\nfinal readonly class Sha384 extends Hmac\n{\n    public function algorithmId(): string\n    {\n        return 'HS384';\n    }\n\n    public function algorithm(): string\n    {\n        return 'sha384';\n    }\n\n    public function minimumBitsLengthForKey(): int\n    {\n        return 384;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Hmac/Sha512.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Hmac;\n\nuse Lcobucci\\JWT\\Signer\\Hmac;\n\nfinal readonly class Sha512 extends Hmac\n{\n    public function algorithmId(): string\n    {\n        return 'HS512';\n    }\n\n    public function algorithm(): string\n    {\n        return 'sha512';\n    }\n\n    public function minimumBitsLengthForKey(): int\n    {\n        return 512;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Hmac.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer;\n\nuse Lcobucci\\JWT\\Signer;\n\nuse function hash_equals;\nuse function hash_hmac;\nuse function strlen;\n\nabstract readonly class Hmac implements Signer\n{\n    final public function sign(string $payload, Key $key): string\n    {\n        $actualKeyLength   = 8 * strlen($key->contents());\n        $expectedKeyLength = $this->minimumBitsLengthForKey();\n\n        if ($actualKeyLength < $expectedKeyLength) {\n            throw InvalidKeyProvided::tooShort($expectedKeyLength, $actualKeyLength);\n        }\n\n        return hash_hmac($this->algorithm(), $payload, $key->contents(), true);\n    }\n\n    final public function verify(string $expected, string $payload, Key $key): bool\n    {\n        return hash_equals($expected, $this->sign($payload, $key));\n    }\n\n    /**\n     * @internal\n     *\n     * @return non-empty-string\n     */\n    abstract public function algorithm(): string;\n\n    /**\n     * @internal\n     *\n     * @return positive-int\n     */\n    abstract public function minimumBitsLengthForKey(): int;\n}\n"
  },
  {
    "path": "src/Signer/InvalidKeyProvided.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer;\n\nuse InvalidArgumentException;\nuse Lcobucci\\JWT\\Exception;\n\nfinal class InvalidKeyProvided extends InvalidArgumentException implements Exception\n{\n    public static function cannotBeParsed(string $details): self\n    {\n        return new self('It was not possible to parse your key, reason:' . $details);\n    }\n\n    /**\n     * @param non-empty-string $expectedType\n     * @param non-empty-string $actualType\n     */\n    public static function incompatibleKeyType(string $expectedType, string $actualType): self\n    {\n        return new self(\n            'The type of the provided key is not \"' . $expectedType\n            . '\", \"' . $actualType . '\" provided',\n        );\n    }\n\n    /** @param positive-int $expectedLength */\n    public static function incompatibleKeyLength(int $expectedLength, int $actualLength): self\n    {\n        return new self(\n            'The length of the provided key is different than ' . $expectedLength . ' bits, '\n            . $actualLength . ' bits provided',\n        );\n    }\n\n    public static function cannotBeEmpty(): self\n    {\n        return new self('Key cannot be empty');\n    }\n\n    public static function tooShort(int $expectedLength, int $actualLength): self\n    {\n        return new self('Key provided is shorter than ' . $expectedLength . ' bits,'\n            . ' only ' . $actualLength . ' bits provided');\n    }\n}\n"
  },
  {
    "path": "src/Signer/Key/FileCouldNotBeRead.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Key;\n\nuse InvalidArgumentException;\nuse Lcobucci\\JWT\\Exception;\nuse Throwable;\n\nfinal class FileCouldNotBeRead extends InvalidArgumentException implements Exception\n{\n    /** @param non-empty-string $path */\n    public static function onPath(string $path, ?Throwable $cause = null): self\n    {\n        return new self(\n            message: 'The path \"' . $path . '\" does not contain a valid key file',\n            previous: $cause,\n        );\n    }\n}\n"
  },
  {
    "path": "src/Signer/Key/InMemory.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Key;\n\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse SensitiveParameter;\nuse SplFileObject;\nuse Throwable;\n\nuse function assert;\nuse function is_string;\n\nfinal readonly class InMemory implements Key\n{\n    /** @param non-empty-string $contents */\n    private function __construct(\n        #[SensitiveParameter]\n        public string $contents,\n        #[SensitiveParameter]\n        public string $passphrase,\n    ) {\n    }\n\n    /** @param non-empty-string $contents */\n    public static function plainText(\n        #[SensitiveParameter]\n        string $contents,\n        #[SensitiveParameter]\n        string $passphrase = '',\n    ): self {\n        self::guardAgainstEmptyKey($contents); // @phpstan-ignore staticMethod.alreadyNarrowedType\n\n        return new self($contents, $passphrase);\n    }\n\n    /** @param non-empty-string $contents */\n    public static function base64Encoded(\n        #[SensitiveParameter]\n        string $contents,\n        #[SensitiveParameter]\n        string $passphrase = '',\n    ): self {\n        $decoded = SodiumBase64Polyfill::base642bin(\n            $contents,\n            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL,\n        );\n\n        self::guardAgainstEmptyKey($decoded); // @phpstan-ignore staticMethod.alreadyNarrowedType\n\n        return new self($decoded, $passphrase);\n    }\n\n    /**\n     * @param non-empty-string $path\n     *\n     * @throws FileCouldNotBeRead\n     */\n    public static function file(\n        string $path,\n        #[SensitiveParameter]\n        string $passphrase = '',\n    ): self {\n        try {\n            $file = new SplFileObject($path);\n        } catch (Throwable $exception) {\n            throw FileCouldNotBeRead::onPath($path, $exception);\n        }\n\n        $fileSize = $file->getSize();\n        $contents = $fileSize > 0 ? $file->fread($file->getSize()) : '';\n        assert(is_string($contents));\n\n        self::guardAgainstEmptyKey($contents);\n\n        return new self($contents, $passphrase);\n    }\n\n    /** @phpstan-assert non-empty-string $contents */\n    private static function guardAgainstEmptyKey(string $contents): void\n    {\n        if ($contents === '') {\n            throw InvalidKeyProvided::cannotBeEmpty();\n        }\n    }\n\n    public function contents(): string\n    {\n        return $this->contents;\n    }\n\n    public function passphrase(): string\n    {\n        return $this->passphrase;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Key.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer;\n\ninterface Key\n{\n    /** @return non-empty-string */\n    public function contents(): string;\n\n    public function passphrase(): string;\n}\n"
  },
  {
    "path": "src/Signer/OpenSSL.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer;\n\nuse Lcobucci\\JWT\\Signer;\nuse OpenSSLAsymmetricKey;\n\nuse function array_key_exists;\nuse function assert;\nuse function is_array;\nuse function is_bool;\nuse function is_int;\nuse function openssl_error_string;\nuse function openssl_pkey_get_details;\nuse function openssl_pkey_get_private;\nuse function openssl_pkey_get_public;\nuse function openssl_sign;\nuse function openssl_verify;\n\nuse const OPENSSL_KEYTYPE_DH;\nuse const OPENSSL_KEYTYPE_DSA;\nuse const OPENSSL_KEYTYPE_EC;\nuse const OPENSSL_KEYTYPE_RSA;\nuse const PHP_EOL;\n\nabstract readonly class OpenSSL implements Signer\n{\n    protected const array KEY_TYPE_MAP = [\n        OPENSSL_KEYTYPE_RSA => 'RSA',\n        OPENSSL_KEYTYPE_DSA => 'DSA',\n        OPENSSL_KEYTYPE_DH => 'DH',\n        OPENSSL_KEYTYPE_EC => 'EC',\n    ];\n\n    /**\n     * @return non-empty-string\n     *\n     * @throws CannotSignPayload\n     * @throws InvalidKeyProvided\n     */\n    final protected function createSignature(\n        Key $key,\n        string $payload,\n    ): string {\n        $opensslKey = $this->getPrivateKey($key);\n\n        $signature = '';\n\n        if (! openssl_sign($payload, $signature, $opensslKey, $this->algorithm())) {\n            throw CannotSignPayload::errorHappened($this->fullOpenSSLErrorString());\n        }\n\n        return $signature;\n    }\n\n    /** @throws CannotSignPayload */\n    private function getPrivateKey(\n        Key $key,\n    ): OpenSSLAsymmetricKey {\n        return $this->validateKey(openssl_pkey_get_private($key->contents(), $key->passphrase()));\n    }\n\n    /** @throws InvalidKeyProvided */\n    final protected function verifySignature(\n        string $expected,\n        string $payload,\n        Key $key,\n    ): bool {\n        $opensslKey = $this->getPublicKey($key);\n        $result     = openssl_verify($payload, $expected, $opensslKey, $this->algorithm());\n\n        return $result === 1;\n    }\n\n    /** @throws InvalidKeyProvided */\n    private function getPublicKey(Key $key): OpenSSLAsymmetricKey\n    {\n        return $this->validateKey(openssl_pkey_get_public($key->contents()));\n    }\n\n    /**\n     * Raises an exception when the key type is not the expected type\n     *\n     * @throws InvalidKeyProvided\n     */\n    private function validateKey(OpenSSLAsymmetricKey|bool $key): OpenSSLAsymmetricKey\n    {\n        if (is_bool($key)) {\n            throw InvalidKeyProvided::cannotBeParsed($this->fullOpenSSLErrorString());\n        }\n\n        $details = openssl_pkey_get_details($key);\n        assert(is_array($details));\n\n        assert(array_key_exists('bits', $details));\n        assert(is_int($details['bits']));\n        assert(array_key_exists('type', $details));\n        assert(is_int($details['type']));\n\n        $this->guardAgainstIncompatibleKey($details['type'], $details['bits']);\n\n        return $key;\n    }\n\n    private function fullOpenSSLErrorString(): string\n    {\n        $error = '';\n\n        while ($msg = openssl_error_string()) {\n            $error .= PHP_EOL . '* ' . $msg;\n        }\n\n        return $error;\n    }\n\n    /** @throws InvalidKeyProvided */\n    abstract protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void;\n\n    /**\n     * Returns which algorithm to be used to create/verify the signature (using OpenSSL constants)\n     *\n     * @internal\n     */\n    abstract public function algorithm(): int;\n}\n"
  },
  {
    "path": "src/Signer/Rsa/Sha256.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Rsa;\n\nuse Lcobucci\\JWT\\Signer\\Rsa;\n\nuse const OPENSSL_ALGO_SHA256;\n\nfinal readonly class Sha256 extends Rsa\n{\n    public function algorithmId(): string\n    {\n        return 'RS256';\n    }\n\n    public function algorithm(): int\n    {\n        return OPENSSL_ALGO_SHA256;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Rsa/Sha384.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Rsa;\n\nuse Lcobucci\\JWT\\Signer\\Rsa;\n\nuse const OPENSSL_ALGO_SHA384;\n\nfinal readonly class Sha384 extends Rsa\n{\n    public function algorithmId(): string\n    {\n        return 'RS384';\n    }\n\n    public function algorithm(): int\n    {\n        return OPENSSL_ALGO_SHA384;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Rsa/Sha512.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer\\Rsa;\n\nuse Lcobucci\\JWT\\Signer\\Rsa;\n\nuse const OPENSSL_ALGO_SHA512;\n\nfinal readonly class Sha512 extends Rsa\n{\n    public function algorithmId(): string\n    {\n        return 'RS512';\n    }\n\n    public function algorithm(): int\n    {\n        return OPENSSL_ALGO_SHA512;\n    }\n}\n"
  },
  {
    "path": "src/Signer/Rsa.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Signer;\n\nuse const OPENSSL_KEYTYPE_RSA;\n\nabstract readonly class Rsa extends OpenSSL\n{\n    private const int MINIMUM_KEY_LENGTH = 2048;\n\n    final public function sign(string $payload, Key $key): string\n    {\n        return $this->createSignature($key, $payload);\n    }\n\n    final public function verify(string $expected, string $payload, Key $key): bool\n    {\n        return $this->verifySignature($expected, $payload, $key);\n    }\n\n    final protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void\n    {\n        if ($type !== OPENSSL_KEYTYPE_RSA) {\n            throw InvalidKeyProvided::incompatibleKeyType(\n                self::KEY_TYPE_MAP[OPENSSL_KEYTYPE_RSA],\n                self::KEY_TYPE_MAP[$type] ?? 'unknown',\n            );\n        }\n\n        if ($lengthInBits < self::MINIMUM_KEY_LENGTH) {\n            throw InvalidKeyProvided::tooShort(self::MINIMUM_KEY_LENGTH, $lengthInBits);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Signer.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse Lcobucci\\JWT\\Signer\\CannotSignPayload;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\ConversionFailed;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse NoDiscard;\n\n/** @immutable */\ninterface Signer\n{\n    /**\n     * Returns the algorithm id\n     *\n     * @return non-empty-string\n     */\n    public function algorithmId(): string;\n\n    /**\n     * Creates a hash for the given payload\n     *\n     * @param non-empty-string $payload\n     *\n     * @return non-empty-string\n     *\n     * @throws CannotSignPayload  When payload signing fails.\n     * @throws InvalidKeyProvided When issue key is invalid/incompatible.\n     * @throws ConversionFailed   When signature could not be converted.\n     */\n    #[NoDiscard]\n    public function sign(string $payload, Key $key): string;\n\n    /**\n     * Returns if the expected hash matches with the data and key\n     *\n     * @param non-empty-string $expected\n     * @param non-empty-string $payload\n     *\n     * @throws InvalidKeyProvided When issue key is invalid/incompatible.\n     * @throws ConversionFailed   When signature could not be converted.\n     */\n    #[NoDiscard]\n    public function verify(string $expected, string $payload, Key $key): bool;\n}\n"
  },
  {
    "path": "src/SodiumBase64Polyfill.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse Lcobucci\\JWT\\Encoding\\CannotDecodeContent;\nuse SodiumException;\n\nuse function base64_decode;\nuse function base64_encode;\nuse function function_exists;\nuse function is_string;\nuse function rtrim;\nuse function sodium_base642bin;\nuse function sodium_bin2base64;\nuse function strtr;\n\n/** @internal */\nfinal readonly class SodiumBase64Polyfill\n{\n    public const int SODIUM_BASE64_VARIANT_ORIGINAL            = 1;\n    public const int SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING = 3;\n    public const int SODIUM_BASE64_VARIANT_URLSAFE             = 5;\n    public const int SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING  = 7;\n\n    /** @return ($decoded is non-empty-string ? non-empty-string : string) */\n    public static function bin2base64(string $decoded, int $variant): string\n    {\n        if (! function_exists('sodium_bin2base64')) {\n            return self::bin2base64Fallback($decoded, $variant); // @codeCoverageIgnore\n        }\n\n        return sodium_bin2base64($decoded, $variant);\n    }\n\n    /** @return ($decoded is non-empty-string ? non-empty-string : string) */\n    public static function bin2base64Fallback(string $decoded, int $variant): string\n    {\n        $encoded = base64_encode($decoded);\n\n        if (\n            $variant === self::SODIUM_BASE64_VARIANT_URLSAFE\n            || $variant === self::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING\n        ) {\n            $encoded = strtr($encoded, '+/', '-_');\n        }\n\n        if (\n            $variant === self::SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING\n            || $variant === self::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING\n        ) {\n            $encoded = rtrim($encoded, '=');\n        }\n\n        return $encoded;\n    }\n\n    /**\n     * @return ($encoded is non-empty-string ? non-empty-string : string)\n     *\n     * @throws CannotDecodeContent\n     */\n    public static function base642bin(string $encoded, int $variant): string\n    {\n        if (! function_exists('sodium_base642bin')) {\n            return self::base642binFallback($encoded, $variant); // @codeCoverageIgnore\n        }\n\n        try {\n            return sodium_base642bin($encoded, $variant, '');\n        } catch (SodiumException) {\n            throw CannotDecodeContent::invalidBase64String();\n        }\n    }\n\n    /**\n     * @return ($encoded is non-empty-string ? non-empty-string : string)\n     *\n     * @throws CannotDecodeContent\n     */\n    public static function base642binFallback(string $encoded, int $variant): string\n    {\n        if (\n            $variant === self::SODIUM_BASE64_VARIANT_URLSAFE\n            || $variant === self::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING\n        ) {\n            $encoded = strtr($encoded, '-_', '+/');\n        }\n\n        $decoded = base64_decode($encoded, true);\n\n        if (! is_string($decoded)) {\n            throw CannotDecodeContent::invalidBase64String();\n        }\n\n        return $decoded;\n    }\n}\n"
  },
  {
    "path": "src/Token/Builder.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Token;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Builder as BuilderInterface;\nuse Lcobucci\\JWT\\ClaimsFormatter;\nuse Lcobucci\\JWT\\Encoder;\nuse Lcobucci\\JWT\\Encoding\\CannotEncodeContent;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\UnencryptedToken;\nuse NoDiscard;\n\nuse function array_diff;\nuse function array_merge;\nuse function in_array;\n\n/** @immutable */\nfinal readonly class Builder implements BuilderInterface\n{\n    /**\n     * @param array<non-empty-string, mixed> $headers\n     * @param array<non-empty-string, mixed> $claims\n     */\n    private function __construct(\n        private Encoder $encoder,\n        private ClaimsFormatter $claimFormatter,\n        private array $headers = ['typ' => 'JWT', 'alg' => null],\n        private array $claims = [],\n    ) {\n    }\n\n    #[NoDiscard]\n    public static function new(Encoder $encoder, ClaimsFormatter $claimFormatter): self\n    {\n        return new self($encoder, $claimFormatter);\n    }\n\n    public function permittedFor(string ...$audiences): BuilderInterface\n    {\n        $configured = $this->claims[RegisteredClaims::AUDIENCE] ?? [];\n        $toAppend   = array_diff($audiences, $configured);\n\n        return $this->newWithClaim(RegisteredClaims::AUDIENCE, array_merge($configured, $toAppend));\n    }\n\n    public function expiresAt(DateTimeImmutable $expiration): BuilderInterface\n    {\n        return $this->newWithClaim(RegisteredClaims::EXPIRATION_TIME, $expiration);\n    }\n\n    public function identifiedBy(string $id): BuilderInterface\n    {\n        return $this->newWithClaim(RegisteredClaims::ID, $id);\n    }\n\n    public function issuedAt(DateTimeImmutable $issuedAt): BuilderInterface\n    {\n        return $this->newWithClaim(RegisteredClaims::ISSUED_AT, $issuedAt);\n    }\n\n    public function issuedBy(string $issuer): BuilderInterface\n    {\n        return $this->newWithClaim(RegisteredClaims::ISSUER, $issuer);\n    }\n\n    public function canOnlyBeUsedAfter(DateTimeImmutable $notBefore): BuilderInterface\n    {\n        return $this->newWithClaim(RegisteredClaims::NOT_BEFORE, $notBefore);\n    }\n\n    public function relatedTo(string $subject): BuilderInterface\n    {\n        return $this->newWithClaim(RegisteredClaims::SUBJECT, $subject);\n    }\n\n    public function withHeader(string $name, mixed $value): BuilderInterface\n    {\n        $headers        = $this->headers;\n        $headers[$name] = $value;\n\n        return new self(\n            $this->encoder,\n            $this->claimFormatter,\n            $headers,\n            $this->claims,\n        );\n    }\n\n    public function withClaim(string $name, mixed $value): BuilderInterface\n    {\n        if (in_array($name, RegisteredClaims::ALL, true)) {\n            throw RegisteredClaimGiven::forClaim($name);\n        }\n\n        return $this->newWithClaim($name, $value);\n    }\n\n    /** @param non-empty-string $name */\n    private function newWithClaim(string $name, mixed $value): BuilderInterface\n    {\n        $claims        = $this->claims;\n        $claims[$name] = $value;\n\n        return new self(\n            $this->encoder,\n            $this->claimFormatter,\n            $this->headers,\n            $claims,\n        );\n    }\n\n    /**\n     * @param array<non-empty-string, mixed> $items\n     *\n     * @throws CannotEncodeContent When data cannot be converted to JSON.\n     */\n    private function encode(array $items): string\n    {\n        return $this->encoder->base64UrlEncode(\n            $this->encoder->jsonEncode($items),\n        );\n    }\n\n    public function getToken(Signer $signer, Key $key): UnencryptedToken\n    {\n        $headers        = $this->headers;\n        $headers['alg'] = $signer->algorithmId();\n\n        $encodedHeaders = $this->encode($headers);\n        $encodedClaims  = $this->encode($this->claimFormatter->formatClaims($this->claims));\n\n        $signature        = $signer->sign($encodedHeaders . '.' . $encodedClaims, $key);\n        $encodedSignature = $this->encoder->base64UrlEncode($signature);\n\n        return new Plain(\n            new DataSet($headers, $encodedHeaders),\n            new DataSet($this->claims, $encodedClaims),\n            new Signature($signature, $encodedSignature),\n        );\n    }\n}\n"
  },
  {
    "path": "src/Token/DataSet.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Token;\n\nuse function array_key_exists;\n\nfinal readonly class DataSet\n{\n    /** @param array<non-empty-string, mixed> $data */\n    public function __construct(private array $data, private string $encoded)\n    {\n    }\n\n    /** @param non-empty-string $name */\n    public function get(string $name, mixed $default = null): mixed\n    {\n        return $this->data[$name] ?? $default;\n    }\n\n    /** @param non-empty-string $name */\n    public function has(string $name): bool\n    {\n        return array_key_exists($name, $this->data);\n    }\n\n    /** @return array<non-empty-string, mixed> */\n    public function all(): array\n    {\n        return $this->data;\n    }\n\n    public function toString(): string\n    {\n        return $this->encoded;\n    }\n}\n"
  },
  {
    "path": "src/Token/InvalidTokenStructure.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Token;\n\nuse InvalidArgumentException;\nuse Lcobucci\\JWT\\Exception;\n\nfinal class InvalidTokenStructure extends InvalidArgumentException implements Exception\n{\n    public static function missingOrNotEnoughSeparators(): self\n    {\n        return new self('The JWT string must have two dots');\n    }\n\n    public static function missingHeaderPart(): self\n    {\n        return new self('The JWT string is missing the Header part');\n    }\n\n    public static function missingClaimsPart(): self\n    {\n        return new self('The JWT string is missing the Claim part');\n    }\n\n    public static function missingSignaturePart(): self\n    {\n        return new self('The JWT string is missing the Signature part');\n    }\n\n    /** @param non-empty-string $part */\n    public static function arrayExpected(string $part): self\n    {\n        return new self($part . ' must be an array with non-empty-string keys');\n    }\n\n    public static function dateIsNotParseable(string $value): self\n    {\n        return new self('Value is not in the allowed date format: ' . $value);\n    }\n}\n"
  },
  {
    "path": "src/Token/Parser.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Token;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Decoder;\nuse Lcobucci\\JWT\\Parser as ParserInterface;\nuse Lcobucci\\JWT\\Token as TokenInterface;\n\nuse function array_key_exists;\nuse function count;\nuse function explode;\nuse function is_array;\nuse function is_numeric;\nuse function number_format;\n\nfinal readonly class Parser implements ParserInterface\n{\n    private const int MICROSECOND_PRECISION = 6;\n\n    public function __construct(private Decoder $decoder)\n    {\n    }\n\n    public function parse(string $jwt): TokenInterface\n    {\n        [$encodedHeaders, $encodedClaims, $encodedSignature] = $this->splitJwt($jwt);\n\n        if ($encodedHeaders === '') {\n            throw InvalidTokenStructure::missingHeaderPart();\n        }\n\n        if ($encodedClaims === '') {\n            throw InvalidTokenStructure::missingClaimsPart();\n        }\n\n        if ($encodedSignature === '') {\n            throw InvalidTokenStructure::missingSignaturePart();\n        }\n\n        $header = $this->parseHeader($encodedHeaders);\n\n        return new Plain(\n            new DataSet($header, $encodedHeaders),\n            new DataSet($this->parseClaims($encodedClaims), $encodedClaims),\n            $this->parseSignature($encodedSignature),\n        );\n    }\n\n    /**\n     * Splits the JWT string into an array\n     *\n     * @param non-empty-string $jwt\n     *\n     * @return string[]\n     *\n     * @throws InvalidTokenStructure When JWT doesn't have all parts.\n     */\n    private function splitJwt(string $jwt): array\n    {\n        $data = explode('.', $jwt);\n\n        if (count($data) !== 3) {\n            throw InvalidTokenStructure::missingOrNotEnoughSeparators();\n        }\n\n        return $data;\n    }\n\n    /**\n     * Parses the header from a string\n     *\n     * @param non-empty-string $data\n     *\n     * @return array<non-empty-string, mixed>\n     *\n     * @throws UnsupportedHeaderFound When an invalid header is informed.\n     * @throws InvalidTokenStructure  When parsed content isn't an array.\n     */\n    private function parseHeader(string $data): array\n    {\n        $header = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));\n\n        if (! is_array($header)) {\n            throw InvalidTokenStructure::arrayExpected('headers');\n        }\n\n        $this->guardAgainstEmptyStringKeys($header, 'headers');\n\n        if (array_key_exists('enc', $header)) {\n            throw UnsupportedHeaderFound::encryption();\n        }\n\n        if (! array_key_exists('typ', $header)) {\n            $header['typ'] = 'JWT';\n        }\n\n        return $header;\n    }\n\n    /**\n     * Parses the claim set from a string\n     *\n     * @param non-empty-string $data\n     *\n     * @return array<non-empty-string, mixed>\n     *\n     * @throws InvalidTokenStructure When parsed content isn't an array or contains non-parseable dates.\n     */\n    private function parseClaims(string $data): array\n    {\n        $claims = $this->decoder->jsonDecode($this->decoder->base64UrlDecode($data));\n\n        if (! is_array($claims)) {\n            throw InvalidTokenStructure::arrayExpected('claims');\n        }\n\n        $this->guardAgainstEmptyStringKeys($claims, 'claims');\n\n        if (array_key_exists(RegisteredClaims::AUDIENCE, $claims)) {\n            $claims[RegisteredClaims::AUDIENCE] = (array) $claims[RegisteredClaims::AUDIENCE];\n        }\n\n        foreach (RegisteredClaims::DATE_CLAIMS as $claim) {\n            if (! array_key_exists($claim, $claims)) {\n                continue;\n            }\n\n            $claims[$claim] = $this->convertDate($claims[$claim]);\n        }\n\n        return $claims;\n    }\n\n    /**\n     * @param array<string, mixed> $array\n     * @param non-empty-string     $part\n     *\n     * @phpstan-assert array<non-empty-string, mixed> $array\n     */\n    private function guardAgainstEmptyStringKeys(array $array, string $part): void\n    {\n        foreach ($array as $key => $value) {\n            if ($key === '') {\n                throw InvalidTokenStructure::arrayExpected($part);\n            }\n        }\n    }\n\n    /** @throws InvalidTokenStructure */\n    private function convertDate(int|float|string $timestamp): DateTimeImmutable\n    {\n        if (! is_numeric($timestamp)) {\n            throw InvalidTokenStructure::dateIsNotParseable($timestamp);\n        }\n\n        $normalizedTimestamp = number_format((float) $timestamp, self::MICROSECOND_PRECISION, '.', '');\n\n        $date = DateTimeImmutable::createFromFormat('U.u', $normalizedTimestamp);\n\n        if ($date === false) {\n            throw InvalidTokenStructure::dateIsNotParseable($normalizedTimestamp);\n        }\n\n        return $date;\n    }\n\n    /**\n     * Returns the signature from given data\n     *\n     * @param non-empty-string $data\n     */\n    private function parseSignature(string $data): Signature\n    {\n        $hash = $this->decoder->base64UrlDecode($data);\n\n        return new Signature($hash, $data);\n    }\n}\n"
  },
  {
    "path": "src/Token/Plain.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Token;\n\nuse DateTimeInterface;\nuse Lcobucci\\JWT\\UnencryptedToken;\n\nuse function in_array;\n\nfinal readonly class Plain implements UnencryptedToken\n{\n    public function __construct(\n        private DataSet $headers,\n        private DataSet $claims,\n        private Signature $signature,\n    ) {\n    }\n\n    public function headers(): DataSet\n    {\n        return $this->headers;\n    }\n\n    public function claims(): DataSet\n    {\n        return $this->claims;\n    }\n\n    public function signature(): Signature\n    {\n        return $this->signature;\n    }\n\n    public function payload(): string\n    {\n        return $this->headers->toString() . '.' . $this->claims->toString();\n    }\n\n    public function isPermittedFor(string $audience): bool\n    {\n        return in_array($audience, $this->claims->get(RegisteredClaims::AUDIENCE, []), true);\n    }\n\n    public function isIdentifiedBy(string $id): bool\n    {\n        return $this->claims->get(RegisteredClaims::ID) === $id;\n    }\n\n    public function isRelatedTo(string $subject): bool\n    {\n        return $this->claims->get(RegisteredClaims::SUBJECT) === $subject;\n    }\n\n    public function hasBeenIssuedBy(string ...$issuers): bool\n    {\n        return in_array($this->claims->get(RegisteredClaims::ISSUER), $issuers, true);\n    }\n\n    public function hasBeenIssuedBefore(DateTimeInterface $now): bool\n    {\n        return $now >= $this->claims->get(RegisteredClaims::ISSUED_AT);\n    }\n\n    public function isMinimumTimeBefore(DateTimeInterface $now): bool\n    {\n        return $now >= $this->claims->get(RegisteredClaims::NOT_BEFORE);\n    }\n\n    public function isExpired(DateTimeInterface $now): bool\n    {\n        if (! $this->claims->has(RegisteredClaims::EXPIRATION_TIME)) {\n            return false;\n        }\n\n        return $now >= $this->claims->get(RegisteredClaims::EXPIRATION_TIME);\n    }\n\n    public function toString(): string\n    {\n        return $this->headers->toString() . '.'\n             . $this->claims->toString() . '.'\n             . $this->signature->toString();\n    }\n}\n"
  },
  {
    "path": "src/Token/RegisteredClaimGiven.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Token;\n\nuse InvalidArgumentException;\nuse Lcobucci\\JWT\\Exception;\n\nuse function sprintf;\n\nfinal class RegisteredClaimGiven extends InvalidArgumentException implements Exception\n{\n    private const DEFAULT_MESSAGE = 'Builder#withClaim() is meant to be used for non-registered claims, '\n                                  . 'check the documentation on how to set claim \"%s\"';\n\n    /** @param non-empty-string $name */\n    public static function forClaim(string $name): self\n    {\n        return new self(sprintf(self::DEFAULT_MESSAGE, $name));\n    }\n}\n"
  },
  {
    "path": "src/Token/RegisteredClaims.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Token;\n\n/**\n * Defines the list of claims that are registered in the IANA \"JSON Web Token Claims\" registry\n *\n * @see https://tools.ietf.org/html/rfc7519#section-4.1\n */\ninterface RegisteredClaims\n{\n    public const array ALL = [\n        self::AUDIENCE,\n        self::EXPIRATION_TIME,\n        self::ID,\n        self::ISSUED_AT,\n        self::ISSUER,\n        self::NOT_BEFORE,\n        self::SUBJECT,\n    ];\n\n    public const array DATE_CLAIMS = [\n        self::ISSUED_AT,\n        self::NOT_BEFORE,\n        self::EXPIRATION_TIME,\n    ];\n\n    /**\n     * Identifies the recipients that the JWT is intended for\n     *\n     * @see https://tools.ietf.org/html/rfc7519#section-4.1.3\n     */\n    public const string AUDIENCE = 'aud';\n\n    /**\n     * Identifies the expiration time on or after which the JWT MUST NOT be accepted for processing\n     *\n     * @see https://tools.ietf.org/html/rfc7519#section-4.1.4\n     */\n    public const string EXPIRATION_TIME = 'exp';\n\n    /**\n     * Provides a unique identifier for the JWT\n     *\n     * @see https://tools.ietf.org/html/rfc7519#section-4.1.7\n     */\n    public const string ID = 'jti';\n\n    /**\n     * Identifies the time at which the JWT was issued\n     *\n     * @see https://tools.ietf.org/html/rfc7519#section-4.1.6\n     */\n    public const string ISSUED_AT = 'iat';\n\n    /**\n     * Identifies the principal that issued the JWT\n     *\n     * @see https://tools.ietf.org/html/rfc7519#section-4.1.1\n     */\n    public const string ISSUER = 'iss';\n\n    /**\n     * Identifies the time before which the JWT MUST NOT be accepted for processing\n     *\n     * https://tools.ietf.org/html/rfc7519#section-4.1.5\n     */\n    public const string NOT_BEFORE = 'nbf';\n\n    /**\n     * Identifies the principal that is the subject of the JWT.\n     *\n     * https://tools.ietf.org/html/rfc7519#section-4.1.2\n     */\n    public const string SUBJECT = 'sub';\n}\n"
  },
  {
    "path": "src/Token/Signature.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Token;\n\nfinal readonly class Signature\n{\n    /**\n     * @param non-empty-string $hash\n     * @param non-empty-string $encoded\n     */\n    public function __construct(private string $hash, private string $encoded)\n    {\n    }\n\n    /** @return non-empty-string */\n    public function hash(): string\n    {\n        return $this->hash;\n    }\n\n    /**\n     * Returns the encoded version of the signature\n     *\n     * @return non-empty-string\n     */\n    public function toString(): string\n    {\n        return $this->encoded;\n    }\n}\n"
  },
  {
    "path": "src/Token/UnsupportedHeaderFound.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Token;\n\nuse InvalidArgumentException;\nuse Lcobucci\\JWT\\Exception;\n\nfinal class UnsupportedHeaderFound extends InvalidArgumentException implements Exception\n{\n    public static function encryption(): self\n    {\n        return new self('Encryption is not supported yet');\n    }\n}\n"
  },
  {
    "path": "src/Token.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse DateTimeInterface;\nuse Lcobucci\\JWT\\Token\\DataSet;\nuse NoDiscard;\n\n/** @immutable */\ninterface Token\n{\n    /**\n     * Returns the token headers\n     */\n    public function headers(): DataSet;\n\n    /**\n     * Returns if the token is allowed to be used by the audience\n     *\n     * @param non-empty-string $audience\n     */\n    public function isPermittedFor(string $audience): bool;\n\n    /**\n     * Returns if the token has the given id\n     *\n     * @param non-empty-string $id\n     */\n    public function isIdentifiedBy(string $id): bool;\n\n    /**\n     * Returns if the token has the given subject\n     *\n     * @param non-empty-string $subject\n     */\n    public function isRelatedTo(string $subject): bool;\n\n    /**\n     * Returns if the token was issued by any of given issuers\n     *\n     * @param non-empty-string ...$issuers\n     */\n    public function hasBeenIssuedBy(string ...$issuers): bool;\n\n    /**\n     * Returns if the token was issued before of given time\n     */\n    public function hasBeenIssuedBefore(DateTimeInterface $now): bool;\n\n    /**\n     * Returns if the token minimum time is before than given time\n     */\n    public function isMinimumTimeBefore(DateTimeInterface $now): bool;\n\n    /**\n     * Returns if the token is expired\n     */\n    public function isExpired(DateTimeInterface $now): bool;\n\n    /**\n     * Returns an encoded representation of the token\n     *\n     * @return non-empty-string\n     */\n    #[NoDiscard]\n    public function toString(): string;\n}\n"
  },
  {
    "path": "src/UnencryptedToken.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse Lcobucci\\JWT\\Token\\DataSet;\nuse Lcobucci\\JWT\\Token\\Signature;\n\ninterface UnencryptedToken extends Token\n{\n    /**\n     * Returns the token claims\n     */\n    public function claims(): DataSet;\n\n    /**\n     * Returns the token signature\n     */\n    public function signature(): Signature;\n\n    /**\n     * Returns the token payload\n     *\n     * @return non-empty-string\n     */\n    public function payload(): string;\n}\n"
  },
  {
    "path": "src/Validation/Constraint/CannotValidateARegisteredClaim.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse InvalidArgumentException;\nuse Lcobucci\\JWT\\Exception;\n\nfinal class CannotValidateARegisteredClaim extends InvalidArgumentException implements Exception\n{\n    /** @param non-empty-string $claim */\n    public static function create(string $claim): self\n    {\n        return new self(\n            'The claim \"' . $claim . '\" is a registered claim, another constraint must be used to validate its value',\n        );\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/HasClaim.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\UnencryptedToken;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\n\nuse function in_array;\n\nfinal readonly class HasClaim implements Constraint\n{\n    /** @param non-empty-string $claim */\n    public function __construct(private string $claim)\n    {\n        if (in_array($claim, Token\\RegisteredClaims::ALL, true)) {\n            throw CannotValidateARegisteredClaim::create($claim);\n        }\n    }\n\n    public function assert(Token $token): void\n    {\n        if (! $token instanceof UnencryptedToken) {\n            throw ConstraintViolation::error('You should pass a plain token', $this);\n        }\n\n        $claims = $token->claims();\n\n        if (! $claims->has($this->claim)) {\n            throw ConstraintViolation::error('The token does not have the claim \"' . $this->claim . '\"', $this);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/HasClaimWithValue.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\UnencryptedToken;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\n\nuse function in_array;\n\nfinal readonly class HasClaimWithValue implements Constraint\n{\n    /** @param non-empty-string $claim */\n    public function __construct(private string $claim, private mixed $expectedValue)\n    {\n        if (in_array($claim, Token\\RegisteredClaims::ALL, true)) {\n            throw CannotValidateARegisteredClaim::create($claim);\n        }\n    }\n\n    public function assert(Token $token): void\n    {\n        if (! $token instanceof UnencryptedToken) {\n            throw ConstraintViolation::error('You should pass a plain token', $this);\n        }\n\n        $claims = $token->claims();\n\n        if (! $claims->has($this->claim)) {\n            throw ConstraintViolation::error('The token does not have the claim \"' . $this->claim . '\"', $this);\n        }\n\n        if ($claims->get($this->claim) !== $this->expectedValue) {\n            throw ConstraintViolation::error(\n                'The claim \"' . $this->claim . '\" does not have the expected value',\n                $this,\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/IdentifiedBy.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\n\nfinal readonly class IdentifiedBy implements Constraint\n{\n    /** @param non-empty-string $id */\n    public function __construct(private string $id)\n    {\n    }\n\n    public function assert(Token $token): void\n    {\n        if (! $token->isIdentifiedBy($this->id)) {\n            throw ConstraintViolation::error(\n                'The token is not identified with the expected ID',\n                $this,\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/IssuedBy.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\n\nfinal readonly class IssuedBy implements Constraint\n{\n    /** @var non-empty-string[] */\n    private array $issuers;\n\n    /** @param non-empty-string ...$issuers */\n    public function __construct(string ...$issuers)\n    {\n        $this->issuers = $issuers;\n    }\n\n    public function assert(Token $token): void\n    {\n        if (! $token->hasBeenIssuedBy(...$this->issuers)) {\n            throw ConstraintViolation::error(\n                'The token was not issued by the given issuers',\n                $this,\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/LeewayCannotBeNegative.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse InvalidArgumentException;\nuse Lcobucci\\JWT\\Exception;\n\nfinal class LeewayCannotBeNegative extends InvalidArgumentException implements Exception\n{\n    public static function create(): self\n    {\n        return new self('Leeway cannot be negative');\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/LooseValidAt.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse DateInterval;\nuse DateTimeInterface;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\ValidAt as ValidAtInterface;\nuse Psr\\Clock\\ClockInterface as Clock;\n\nfinal readonly class LooseValidAt implements ValidAtInterface\n{\n    private DateInterval $leeway;\n\n    public function __construct(private Clock $clock, ?DateInterval $leeway = null)\n    {\n        $this->leeway = $this->guardLeeway($leeway);\n    }\n\n    private function guardLeeway(?DateInterval $leeway): DateInterval\n    {\n        if ($leeway === null) {\n            return new DateInterval('PT0S');\n        }\n\n        if ($leeway->invert === 1) {\n            throw LeewayCannotBeNegative::create();\n        }\n\n        return $leeway;\n    }\n\n    public function assert(Token $token): void\n    {\n        $now = $this->clock->now();\n\n        $this->assertIssueTime($token, $now->add($this->leeway));\n        $this->assertMinimumTime($token, $now->add($this->leeway));\n        $this->assertExpiration($token, $now->sub($this->leeway));\n    }\n\n    /** @throws ConstraintViolation */\n    private function assertExpiration(Token $token, DateTimeInterface $now): void\n    {\n        if ($token->isExpired($now)) {\n            throw ConstraintViolation::error('The token is expired', $this);\n        }\n    }\n\n    /** @throws ConstraintViolation */\n    private function assertMinimumTime(Token $token, DateTimeInterface $now): void\n    {\n        if (! $token->isMinimumTimeBefore($now)) {\n            throw ConstraintViolation::error('The token cannot be used yet', $this);\n        }\n    }\n\n    /** @throws ConstraintViolation */\n    private function assertIssueTime(Token $token, DateTimeInterface $now): void\n    {\n        if (! $token->hasBeenIssuedBefore($now)) {\n            throw ConstraintViolation::error('The token was issued in the future', $this);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/PermittedFor.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\n\nfinal readonly class PermittedFor implements Constraint\n{\n    /** @param non-empty-string $audience */\n    public function __construct(private string $audience)\n    {\n    }\n\n    public function assert(Token $token): void\n    {\n        if (! $token->isPermittedFor($this->audience)) {\n            throw ConstraintViolation::error(\n                'The token is not allowed to be used by this audience',\n                $this,\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/RelatedTo.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\n\nfinal readonly class RelatedTo implements Constraint\n{\n    /** @param non-empty-string $subject */\n    public function __construct(private string $subject)\n    {\n    }\n\n    public function assert(Token $token): void\n    {\n        if (! $token->isRelatedTo($this->subject)) {\n            throw ConstraintViolation::error(\n                'The token is not related to the expected subject',\n                $this,\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/SignedWith.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\UnencryptedToken;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\SignedWith as SignedWithInterface;\n\nfinal readonly class SignedWith implements SignedWithInterface\n{\n    public function __construct(private Signer $signer, private Signer\\Key $key)\n    {\n    }\n\n    public function assert(Token $token): void\n    {\n        if (! $token instanceof UnencryptedToken) {\n            throw ConstraintViolation::error('You should pass a plain token', $this);\n        }\n\n        if ($token->headers()->get('alg') !== $this->signer->algorithmId()) {\n            throw ConstraintViolation::error('Token signer mismatch', $this);\n        }\n\n        if (! $this->signer->verify($token->signature()->hash(), $token->payload(), $this->key)) {\n            throw ConstraintViolation::error('Token signature mismatch', $this);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/SignedWithOneInSet.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\SignedWith as SignedWithInterface;\n\nuse const PHP_EOL;\n\nfinal readonly class SignedWithOneInSet implements SignedWithInterface\n{\n    /** @var array<SignedWithUntilDate> */\n    private array $constraints;\n\n    public function __construct(SignedWithUntilDate ...$constraints)\n    {\n        $this->constraints = $constraints;\n    }\n\n    public function assert(Token $token): void\n    {\n        $errorMessage = 'It was not possible to verify the signature of the token, reasons:';\n\n        foreach ($this->constraints as $constraint) {\n            try {\n                $constraint->assert($token);\n\n                return;\n            } catch (ConstraintViolation $violation) {\n                $errorMessage .= PHP_EOL . '- ' . $violation->getMessage();\n            }\n        }\n\n        throw ConstraintViolation::error($errorMessage, $this);\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/SignedWithUntilDate.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse DateTimeImmutable;\nuse DateTimeInterface;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\SignedWith as SignedWithInterface;\nuse Psr\\Clock\\ClockInterface;\n\nfinal readonly class SignedWithUntilDate implements SignedWithInterface\n{\n    private SignedWith $verifySignature;\n    private ClockInterface $clock;\n\n    public function __construct(\n        Signer $signer,\n        Signer\\Key $key,\n        private DateTimeImmutable $validUntil,\n        ?ClockInterface $clock = null,\n    ) {\n        $this->verifySignature = new SignedWith($signer, $key);\n\n        $this->clock = $clock ?? new class () implements ClockInterface {\n            public function now(): DateTimeImmutable\n            {\n                return new DateTimeImmutable();\n            }\n        };\n    }\n\n    public function assert(Token $token): void\n    {\n        if ($this->validUntil < $this->clock->now()) {\n            throw ConstraintViolation::error(\n                'This constraint was only usable until '\n                . $this->validUntil->format(DateTimeInterface::RFC3339),\n                $this,\n            );\n        }\n\n        $this->verifySignature->assert($token);\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint/StrictValidAt.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation\\Constraint;\n\nuse DateInterval;\nuse DateTimeInterface;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\UnencryptedToken;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\ValidAt as ValidAtInterface;\nuse Psr\\Clock\\ClockInterface as Clock;\n\nfinal readonly class StrictValidAt implements ValidAtInterface\n{\n    private DateInterval $leeway;\n\n    public function __construct(private Clock $clock, ?DateInterval $leeway = null)\n    {\n        $this->leeway = $this->guardLeeway($leeway);\n    }\n\n    private function guardLeeway(?DateInterval $leeway): DateInterval\n    {\n        if ($leeway === null) {\n            return new DateInterval('PT0S');\n        }\n\n        if ($leeway->invert === 1) {\n            throw LeewayCannotBeNegative::create();\n        }\n\n        return $leeway;\n    }\n\n    public function assert(Token $token): void\n    {\n        if (! $token instanceof UnencryptedToken) {\n            throw ConstraintViolation::error('You should pass a plain token', $this);\n        }\n\n        $now = $this->clock->now();\n\n        $this->assertIssueTime($token, $now->add($this->leeway));\n        $this->assertMinimumTime($token, $now->add($this->leeway));\n        $this->assertExpiration($token, $now->sub($this->leeway));\n    }\n\n    /** @throws ConstraintViolation */\n    private function assertExpiration(UnencryptedToken $token, DateTimeInterface $now): void\n    {\n        if (! $token->claims()->has(Token\\RegisteredClaims::EXPIRATION_TIME)) {\n            throw ConstraintViolation::error('\"Expiration Time\" claim missing', $this);\n        }\n\n        if ($token->isExpired($now)) {\n            throw ConstraintViolation::error('The token is expired', $this);\n        }\n    }\n\n    /** @throws ConstraintViolation */\n    private function assertMinimumTime(UnencryptedToken $token, DateTimeInterface $now): void\n    {\n        if (! $token->claims()->has(Token\\RegisteredClaims::NOT_BEFORE)) {\n            throw ConstraintViolation::error('\"Not Before\" claim missing', $this);\n        }\n\n        if (! $token->isMinimumTimeBefore($now)) {\n            throw ConstraintViolation::error('The token cannot be used yet', $this);\n        }\n    }\n\n    /** @throws ConstraintViolation */\n    private function assertIssueTime(UnencryptedToken $token, DateTimeInterface $now): void\n    {\n        if (! $token->claims()->has(Token\\RegisteredClaims::ISSUED_AT)) {\n            throw ConstraintViolation::error('\"Issued At\" claim missing', $this);\n        }\n\n        if (! $token->hasBeenIssuedBefore($now)) {\n            throw ConstraintViolation::error('The token was issued in the future', $this);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Validation/Constraint.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation;\n\nuse Lcobucci\\JWT\\Token;\n\ninterface Constraint\n{\n    /** @throws ConstraintViolation */\n    public function assert(Token $token): void;\n}\n"
  },
  {
    "path": "src/Validation/ConstraintViolation.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation;\n\nuse Lcobucci\\JWT\\Exception;\nuse RuntimeException;\n\nfinal class ConstraintViolation extends RuntimeException implements Exception\n{\n    /** @param class-string<Constraint>|null $constraint */\n    public function __construct(\n        string $message = '',\n        public readonly ?string $constraint = null,\n    ) {\n        parent::__construct($message);\n    }\n\n    /** @param non-empty-string $message */\n    public static function error(string $message, Constraint $constraint): self\n    {\n        return new self(message: $message, constraint: $constraint::class);\n    }\n}\n"
  },
  {
    "path": "src/Validation/NoConstraintsGiven.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation;\n\nuse Lcobucci\\JWT\\Exception;\nuse RuntimeException;\n\nfinal class NoConstraintsGiven extends RuntimeException implements Exception\n{\n}\n"
  },
  {
    "path": "src/Validation/RequiredConstraintsViolated.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation;\n\nuse Lcobucci\\JWT\\Exception;\nuse RuntimeException;\n\nuse function array_map;\nuse function implode;\n\nfinal class RequiredConstraintsViolated extends RuntimeException implements Exception\n{\n    /** @param ConstraintViolation[] $violations */\n    public function __construct(\n        string $message = '',\n        public readonly array $violations = [],\n    ) {\n        parent::__construct($message);\n    }\n\n    public static function fromViolations(ConstraintViolation ...$violations): self\n    {\n        return new self(message: self::buildMessage($violations), violations: $violations);\n    }\n\n    /** @param ConstraintViolation[] $violations */\n    private static function buildMessage(array $violations): string\n    {\n        $violations = array_map(\n            static function (ConstraintViolation $violation): string {\n                return '- ' . $violation->getMessage();\n            },\n            $violations,\n        );\n\n        $message  = \"The token violates some mandatory constraints, details:\\n\";\n        $message .= implode(\"\\n\", $violations);\n\n        return $message;\n    }\n\n    /** @return ConstraintViolation[] */\n    public function violations(): array\n    {\n        return $this->violations;\n    }\n}\n"
  },
  {
    "path": "src/Validation/SignedWith.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation;\n\ninterface SignedWith extends Constraint\n{\n}\n"
  },
  {
    "path": "src/Validation/ValidAt.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation;\n\ninterface ValidAt extends Constraint\n{\n}\n"
  },
  {
    "path": "src/Validation/Validator.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Validation;\n\nuse Lcobucci\\JWT\\Token;\n\nfinal readonly class Validator implements \\Lcobucci\\JWT\\Validator\n{\n    public function assert(Token $token, Constraint ...$constraints): void\n    {\n        if ($constraints === []) {\n            throw new NoConstraintsGiven('No constraint given.');\n        }\n\n        $violations = [];\n\n        foreach ($constraints as $constraint) {\n            $this->checkConstraint($constraint, $token, $violations);\n        }\n\n        if ($violations !== []) {\n            throw RequiredConstraintsViolated::fromViolations(...$violations);\n        }\n    }\n\n    /** @param ConstraintViolation[] $violations */\n    private function checkConstraint(\n        Constraint $constraint,\n        Token $token,\n        array &$violations,\n    ): void {\n        try {\n            $constraint->assert($token);\n        } catch (ConstraintViolation $e) {\n            $violations[] = $e;\n        }\n    }\n\n    public function validate(Token $token, Constraint ...$constraints): bool\n    {\n        if ($constraints === []) {\n            throw new NoConstraintsGiven('No constraint given.');\n        }\n\n        try {\n            foreach ($constraints as $constraint) {\n                $constraint->assert($token);\n            }\n\n            return true;\n        } catch (ConstraintViolation) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "src/Validator.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT;\n\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\NoConstraintsGiven;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse NoDiscard;\n\ninterface Validator\n{\n    /**\n     * @throws RequiredConstraintsViolated\n     * @throws NoConstraintsGiven\n     */\n    public function assert(Token $token, Constraint ...$constraints): void;\n\n    /** @throws NoConstraintsGiven */\n    #[NoDiscard]\n    public function validate(Token $token, Constraint ...$constraints): bool;\n}\n"
  },
  {
    "path": "tests/Benchmark/AlgorithmsBench.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Benchmark;\n\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse PhpBench\\Attributes as Bench;\nuse RuntimeException;\n\n#[Bench\\Iterations(5)]\n#[Bench\\Revs(100)]\n#[Bench\\Warmup(3)]\nabstract class AlgorithmsBench\n{\n    private const array SUPPORTED_ALGORITHMS = [\n        'hmac' => ['HS256', 'HS384', 'HS512'],\n        'rsa' => ['RS256', 'RS384', 'RS512'],\n        'ecdsa' => ['ES256', 'ES384', 'ES512'],\n        'eddsa' => ['EdDSA'],\n        'blake2b' => ['BLAKE2B'],\n    ];\n\n    protected const string PAYLOAD = \"It\\xe2\\x80\\x99s a dangerous business, Frodo, going out your door. You step\"\n        . \" onto the road, and if you don't keep your feet, there\\xe2\\x80\\x99s no knowing where you might be swept\"\n        . ' off to.';\n\n    #[Bench\\Subject]\n    #[Bench\\ParamProviders('hmacAlgorithms')]\n    #[Bench\\Groups(['hmac', 'symmetric'])]\n    public function hmac(): void\n    {\n        $this->runBenchmark();\n    }\n\n    /** @return iterable<string, array{algorithm: string}> */\n    public function hmacAlgorithms(): iterable\n    {\n        yield from $this->iterateAlgorithms('hmac');\n    }\n\n    #[Bench\\Subject]\n    #[Bench\\ParamProviders('rsaAlgorithms')]\n    #[Bench\\Groups(['rsa', 'asymmetric'])]\n    public function rsa(): void\n    {\n        $this->runBenchmark();\n    }\n\n    /** @return iterable<string, array{algorithm: string}> */\n    public function rsaAlgorithms(): iterable\n    {\n        yield from $this->iterateAlgorithms('rsa');\n    }\n\n    #[Bench\\Subject]\n    #[Bench\\ParamProviders('ecdsaAlgorithms')]\n    #[Bench\\Groups(['ecdsa', 'asymmetric'])]\n    public function ecdsa(): void\n    {\n        $this->runBenchmark();\n    }\n\n    /** @return iterable<string, array{algorithm: string}> */\n    public function ecdsaAlgorithms(): iterable\n    {\n        yield from $this->iterateAlgorithms('ecdsa');\n    }\n\n    #[Bench\\Subject]\n    #[Bench\\ParamProviders('eddsaAlgorithms')]\n    #[Bench\\Groups(['eddsa', 'asymmetric'])]\n    public function eddsa(): void\n    {\n        $this->runBenchmark();\n    }\n\n    /** @return iterable<string, array{algorithm: string}> */\n    public function eddsaAlgorithms(): iterable\n    {\n        yield from $this->iterateAlgorithms('eddsa');\n    }\n\n    #[Bench\\Subject]\n    #[Bench\\ParamProviders('blake2bAlgorithms')]\n    #[Bench\\Groups(['blake2b', 'symmetric'])]\n    public function blake2b(): void\n    {\n        $this->runBenchmark();\n    }\n\n    /** @return iterable<string, array{algorithm: string}> */\n    public function blake2bAlgorithms(): iterable\n    {\n        yield from $this->iterateAlgorithms('blake2b');\n    }\n\n    abstract protected function runBenchmark(): void;\n\n    protected function resolveAlgorithm(string $name): Signer\n    {\n        return match ($name) {\n            'HS256' => new Signer\\Hmac\\Sha256(),\n            'HS384' => new Signer\\Hmac\\Sha384(),\n            'HS512' => new Signer\\Hmac\\Sha512(),\n            'RS256' => new Signer\\Rsa\\Sha256(),\n            'RS384' => new Signer\\Rsa\\Sha384(),\n            'RS512' => new Signer\\Rsa\\Sha512(),\n            'ES256' => new Signer\\Ecdsa\\Sha256(),\n            'ES384' => new Signer\\Ecdsa\\Sha384(),\n            'ES512' => new Signer\\Ecdsa\\Sha512(),\n            'EdDSA' => new Signer\\Eddsa(),\n            'BLAKE2B' => new Signer\\Blake2b(),\n            default => throw new RuntimeException('Unknown algorithm'),\n        };\n    }\n\n    protected function resolveSigningKey(string $name): Key\n    {\n        return match ($name) {\n            'HS256' => InMemory::base64Encoded('n5p7sBK+dvBmSKNlQIFrsuB1cnmnwsxGyWXPgRSZtWY='),\n            'HS384' => InMemory::base64Encoded('kNUb8KvJC+fvhPzIuimwWHleES3AAnUjI+UIWZyor5HT33st9KIjfPkgtfu60UL2'),\n            'HS512' => InMemory::base64Encoded(\n                'OgXKIs+aZCQgXnDfi8mAFnWVo+Xn3JTR7BvT/j1Q1zP9oRx9xGg4jmpq00RsPPDclYi8+jRl664pu4d0zan2ow==',\n            ),\n            'RS256', 'RS384', 'RS512' => InMemory::file(__DIR__ . '/Rsa/private.key'),\n            'ES256' => InMemory::file(__DIR__ . '/Ecdsa/private-256.key'),\n            'ES384' => InMemory::file(__DIR__ . '/Ecdsa/private-384.key'),\n            'ES512' => InMemory::file(__DIR__ . '/Ecdsa/private-521.key'),\n            'EdDSA' => InMemory::base64Encoded(\n                'K3NWT0XqaH+4jgi42gQmHnFE+HTPVhFYi3u4DFJ3OpRHRMt/aGRBoKD/Pt5H/iYgGCla7Q04CdjOUpLSrjZhtg==',\n            ),\n            'BLAKE2B' => InMemory::base64Encoded('b6DNRcX2SFapbICe6lXWYoOZA+JXL/dvkfWiv2hJv3Y='),\n            default => throw new RuntimeException('Unknown algorithm'),\n        };\n    }\n\n    protected function resolveVerificationKey(string $name): Key\n    {\n        return match ($name) {\n            'HS256', 'HS384', 'HS512', 'BLAKE2B' => $this->resolveSigningKey($name),\n            'RS256', 'RS384', 'RS512' => InMemory::file(__DIR__ . '/Rsa/public.key'),\n            'ES256' => InMemory::file(__DIR__ . '/Ecdsa/public-256.key'),\n            'ES384' => InMemory::file(__DIR__ . '/Ecdsa/public-384.key'),\n            'ES512' => InMemory::file(__DIR__ . '/Ecdsa/public-521.key'),\n            'EdDSA' => InMemory::base64Encoded('R0TLf2hkQaCg/z7eR/4mIBgpWu0NOAnYzlKS0q42YbY='),\n            default => throw new RuntimeException('Unknown algorithm'),\n        };\n    }\n\n    /** @return iterable<string, array{algorithm: string}> */\n    private function iterateAlgorithms(string $family): iterable\n    {\n        foreach (self::SUPPORTED_ALGORITHMS[$family] ?? [] as $algorithm) {\n            yield $algorithm => ['algorithm' => $algorithm];\n        }\n    }\n}\n"
  },
  {
    "path": "tests/Benchmark/CreateSignatureBench.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Benchmark;\n\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse PhpBench\\Attributes as Bench;\n\n#[Bench\\BeforeMethods('initialize')]\nfinal class CreateSignatureBench extends AlgorithmsBench\n{\n    private Signer $algorithm;\n    private Key $key;\n\n    /** @param array{algorithm: string} $params */\n    public function initialize(array $params): void\n    {\n        $this->algorithm = $this->resolveAlgorithm($params['algorithm']);\n        $this->key       = $this->resolveSigningKey($params['algorithm']);\n    }\n\n    protected function runBenchmark(): void\n    {\n        $void = $this->algorithm->sign(self::PAYLOAD, $this->key);\n    }\n}\n"
  },
  {
    "path": "tests/Benchmark/Ecdsa/private-256.key",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIK/5B8mfmtOq5sTN8hEivOK9aLUoPmkHFUrZEYQPogjPoAoGCCqGSM49\nAwEHoUQDQgAEZe2loSV3wrroKUN/4zhwGhCqo3Xhu1td4QjeQ5wIVR0eUu11cBFj\n9/nkDd+fNBs9ybqGCvfgynyn6e7NAITRnA==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "tests/Benchmark/Ecdsa/private-384.key",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDDZi2q9Sdoiu0XNsrdD/PLBtgAr48L4MWy7XMiND8riSeJkTYnhPlra\nxMSRKkd2OhSgBwYFK4EEACKhZANiAAR+oJdVSn/ZrdLRzsad6Dv7bOVLdPkc0GZu\nn5//VFLgobl2lxEhRvWxW0tBRkJKj8tnVRWaWbe0L1C7QCtGN6NnSxpn6Y4FhtE2\nprdfXjnjWAQI0+TdPeCvQtOFQjrLWrw=\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "tests/Benchmark/Ecdsa/private-521.key",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIBCwZmxfodGCjbu5tgb4al9Qwv36dS9lCYk4Hjq6VMMneMH2tlDaS1\nkEid5mVnJrznhLJFn5IO3mB+FC/V1q2RKQigBwYFK4EEACOhgYkDgYYABAGjhQzd\n+mwlkFVzY2Ak1fW+DMrqPxszQO6SR8cqpcAhb9BSR9whqghljOU1X9cJe/A6/2WF\nWqTRpj6RaRkzot6KbwEC0jo08XIXdyWkp4AsNbLKPDaO2DZH/8LMkeouNHxJJC/8\n/nU+5kOPBeoZV9qodJYOhnkxiNjHJjrFL8YYRXgUTw==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "tests/Benchmark/Ecdsa/public-256.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZe2loSV3wrroKUN/4zhwGhCqo3Xh\nu1td4QjeQ5wIVR0eUu11cBFj9/nkDd+fNBs9ybqGCvfgynyn6e7NAITRnA==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "tests/Benchmark/Ecdsa/public-384.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEfqCXVUp/2a3S0c7Gneg7+2zlS3T5HNBm\nbp+f/1RS4KG5dpcRIUb1sVtLQUZCSo/LZ1UVmlm3tC9Qu0ArRjejZ0saZ+mOBYbR\nNqa3X14541gECNPk3T3gr0LThUI6y1q8\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "tests/Benchmark/Ecdsa/public-521.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBo4UM3fpsJZBVc2NgJNX1vgzK6j8b\nM0DukkfHKqXAIW/QUkfcIaoIZYzlNV/XCXvwOv9lhVqk0aY+kWkZM6Leim8BAtI6\nNPFyF3clpKeALDWyyjw2jtg2R//CzJHqLjR8SSQv/P51PuZDjwXqGVfaqHSWDoZ5\nMYjYxyY6xS/GGEV4FE8=\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "tests/Benchmark/IssueTokenBench.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Benchmark;\n\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse PhpBench\\Attributes as Bench;\n\n#[Bench\\BeforeMethods('initialize')]\nfinal class IssueTokenBench extends AlgorithmsBench\n{\n    private Signer $algorithm;\n    private Key $key;\n\n    /** @param array{algorithm: string} $params */\n    public function initialize(array $params): void\n    {\n        $this->algorithm = $this->resolveAlgorithm($params['algorithm']);\n        $this->key       = $this->resolveSigningKey($params['algorithm']);\n    }\n\n    protected function runBenchmark(): void\n    {\n        $void = (new JwtFacade())->issue(\n            $this->algorithm,\n            $this->key,\n            static fn (Builder $builder): Builder => $builder\n                ->identifiedBy('token-1')\n                ->issuedBy('lcobucci.jwt.benchmarks')\n                ->relatedTo('user-1')\n                ->permittedFor('lcobucci.jwt'),\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Benchmark/ParseTokenBench.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Benchmark;\n\nuse Lcobucci\\Clock\\SystemClock;\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse PhpBench\\Attributes as Bench;\n\n#[Bench\\BeforeMethods('initialize')]\nfinal class ParseTokenBench extends AlgorithmsBench\n{\n    private Signer $algorithm;\n    private Key $key;\n    /** @var non-empty-string */\n    private string $jwt;\n\n    /** @param array{algorithm: string} $params */\n    public function initialize(array $params): void\n    {\n        $this->algorithm = $this->resolveAlgorithm($params['algorithm']);\n        $this->key       = $this->resolveVerificationKey($params['algorithm']);\n\n        $this->jwt = (new JwtFacade())->issue(\n            $this->algorithm,\n            $this->resolveSigningKey($params['algorithm']),\n            static fn (Builder $builder): Builder => $builder\n                ->identifiedBy('token-1')\n                ->issuedBy('lcobucci.jwt.benchmarks')\n                ->relatedTo('user-1')\n                ->permittedFor('lcobucci.jwt'),\n        )->toString();\n    }\n\n    protected function runBenchmark(): void\n    {\n        $void = (new JwtFacade())->parse(\n            $this->jwt,\n            new Constraint\\SignedWith($this->algorithm, $this->key),\n            new Constraint\\StrictValidAt(SystemClock::fromSystemTimezone()),\n            new Constraint\\IssuedBy('lcobucci.jwt.benchmarks'),\n            new Constraint\\RelatedTo('user-1'),\n            new Constraint\\PermittedFor('lcobucci.jwt'),\n            new Constraint\\IdentifiedBy('token-1'),\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Benchmark/Rsa/private.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCfgQ+0A4Jz0CWR\n5Ac/MdK2ABuCzttNkvBQFl1Hz8q4o8Qct3isdVN5P475dXaNGiN02HElZMO813ue\npDRUSJlAfP8AmZIKkxokxEFIUqspvbCpXAZT82xg5gv5C2JY3aVvNwR7pcLR0Cmv\nnJ1AuseqQceKDdEGit1pnoCP6gEeoUQdik97tOl7459V8d3UTpxLozUVlwPU00tg\nPmUUek8j1tPAmWx17e6EaoLRkK4QeDyWHPA4eu0hBtLQVVtv2Tf61VNTh+D/cv++\neJQUArC4IuoqdLYFjB2r+bNKdstjuH+qLGhHuOKDf/+RGG5rHBSRHPmJqJCSqBzm\nAd2s0/nPAgMBAAECggEAbWUC9B+EFRIo8kpGfh0ZuyGPvMNKvYWNtB/ikiH9k20e\nT+O1q/I78eiZkpXxXQ0UTEs2LsNRS+8uJbvQ+A1irkwMSMkK1J3XTGgdrhCku9gR\nldY7sNA/AKZGh+Q661/42rINLRCe8W+nZ34ui/qOfkLnK9QWDDqpaIsA+bMwWWSD\nFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l+DqEiWxqg82s\nXt2h+LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5P\nlyl21hsFf4L/mHCuoFau7gdsPfHPxxjVOcOpBrQzwQKBgQDdKXGD8PBNclxvrT3l\nGhfKBAIBnlGcC9mWejXKEe2dR7H9+nsBn/2dFo7sdf/5IV8ZB661qjZMOMMBZThW\n6mTyvD0lHQDNnQ3Z++4gCav9YKyYal42pCd6/VPsjISyeHxQy36fkJp+GSKTOESy\nuad0fovE6u9EmWw+npm/xtSrSQKBgQC4oTZ2H5xN/oREXiTh7+PLvwZ89hQhpTKh\nJIm4HOncK5uTc4KqzqCtPxtH9y7QObUxnBaa12oPIj3ketR6rcw/Xm8ww43yUdN5\nm7aWYq/CpbtqdXlTOEzWJnvPjIyS5TAVagG/Jjz3wRe9EP6F2pHEeVKoBnX3bMHe\nlUUnSzukVwKBgAfD1b15Lya49i/hmEO798va+isOYPUmoVwcLFlM6dfU1ZYCPmFf\nOatTSG9a8ULQ/iLF10d/k2p3r7kT0beTgTnYjBkKfKW7duoJY2HylPxPcZ/kVCx8\n9PnnfRPYFyyg+FRp4Kc/j30P6tvaZOcVh6CadNPUH9R7woYsUV+fXoYpAoGACLDm\nDGduhylc9o7r84rEUVn7pzQ6PF83Y+iBZx5NT+TpnOZKF1pErAMVeKzFEl41DlHH\nqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJKbi/7k/vJgGHwHxgPaX2PnvP\n+zyEkDERuf+ry4c/Z11Cq9AqC2yeL6kdKT1cYF8CgYEA3PiqvXQN0zwMeE+sBvZg\ni289XP9XCQF3VWqPzMKnIgQp7/Tugo6+NZBKCQsMf3HaEGBjTVJs/jcK8+TRXvaK\ne+7ZMaQj8VfBdYkssbu0NKDDhjJ+GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h\n6ZEpMF6xmujs4qMpPz8aaI4=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "tests/Benchmark/Rsa/public.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn4EPtAOCc9AlkeQHPzHS\ntgAbgs7bTZLwUBZdR8/KuKPEHLd4rHVTeT+O+XV2jRojdNhxJWTDvNd7nqQ0VEiZ\nQHz/AJmSCpMaJMRBSFKrKb2wqVwGU/NsYOYL+QtiWN2lbzcEe6XC0dApr5ydQLrH\nqkHHig3RBordaZ6Aj+oBHqFEHYpPe7Tpe+OfVfHd1E6cS6M1FZcD1NNLYD5lFHpP\nI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3+tVTU4fg/3L/vniUFAKw\nuCLqKnS2BYwdq/mzSnbLY7h/qixoR7jig3//kRhuaxwUkRz5iaiQkqgc5gHdrNP5\nzwIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "tests/Benchmark/VerifySignatureBench.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Benchmark;\n\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse PhpBench\\Attributes as Bench;\n\n#[Bench\\BeforeMethods('initialize')]\nfinal class VerifySignatureBench extends AlgorithmsBench\n{\n    private Signer $algorithm;\n    private Key $key;\n    /** @var non-empty-string */\n    private string $signature;\n\n    /** @param array{algorithm: string} $params */\n    public function initialize(array $params): void\n    {\n        $this->algorithm = $this->resolveAlgorithm($params['algorithm']);\n        $this->key       = $this->resolveVerificationKey($params['algorithm']);\n\n        $this->signature = $this->algorithm->sign(\n            self::PAYLOAD,\n            $this->resolveSigningKey($params['algorithm']),\n        );\n    }\n\n    protected function runBenchmark(): void\n    {\n        $void = $this->algorithm->verify($this->signature, self::PAYLOAD, $this->key);\n    }\n}\n"
  },
  {
    "path": "tests/ConfigurationTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\ClaimsFormatter;\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Decoder;\nuse Lcobucci\\JWT\\Encoder;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Parser;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Token\\Builder as BuilderImpl;\nuse Lcobucci\\JWT\\Token\\Parser as ParserImpl;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validator;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\MockObject\\Stub;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(Configuration::class)]\n#[PHPUnit\\UsesClass(ChainedFormatter::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\n#[PHPUnit\\UsesClass(BuilderImpl::class)]\n#[PHPUnit\\UsesClass(ParserImpl::class)]\n#[PHPUnit\\UsesClass(\\Lcobucci\\JWT\\Validation\\Validator::class)]\nfinal class ConfigurationTest extends TestCase\n{\n    private Parser&Stub $parser;\n    private Signer&Stub $signer;\n    private Encoder&Stub $encoder;\n    private Decoder&Stub $decoder;\n    private Validator&Stub $validator;\n    private Constraint&Stub $validationConstraints;\n\n    #[PHPUnit\\Before]\n    public function createDependencies(): void\n    {\n        $this->signer                = self::createStub(Signer::class);\n        $this->encoder               = self::createStub(Encoder::class);\n        $this->decoder               = self::createStub(Decoder::class);\n        $this->parser                = self::createStub(Parser::class);\n        $this->validator             = self::createStub(Validator::class);\n        $this->validationConstraints = self::createStub(Constraint::class);\n    }\n\n    #[PHPUnit\\Test]\n    public function forAsymmetricSignerShouldConfigureSignerAndBothKeys(): void\n    {\n        $signingKey      = InMemory::plainText('private');\n        $verificationKey = InMemory::plainText('public');\n\n        $config = Configuration::forAsymmetricSigner($this->signer, $signingKey, $verificationKey);\n\n        self::assertSame($this->signer, $config->signer());\n        self::assertSame($signingKey, $config->signingKey());\n        self::assertSame($verificationKey, $config->verificationKey());\n    }\n\n    #[PHPUnit\\Test]\n    public function forSymmetricSignerShouldConfigureSignerAndBothKeys(): void\n    {\n        $key    = InMemory::plainText('private');\n        $config = Configuration::forSymmetricSigner($this->signer, $key);\n\n        self::assertSame($this->signer, $config->signer());\n        self::assertSame($key, $config->signingKey());\n        self::assertSame($key, $config->verificationKey());\n    }\n\n    #[PHPUnit\\Test]\n    public function builderShouldCreateABuilderWithDefaultEncoderAndClaimFactory(): void\n    {\n        $config  = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n        $builder = $config->builder();\n\n        self::assertInstanceOf(BuilderImpl::class, $builder);\n        self::assertNotEquals(BuilderImpl::new($this->encoder, ChainedFormatter::default()), $builder);\n        self::assertEquals(BuilderImpl::new(new JoseEncoder(), ChainedFormatter::default()), $builder);\n    }\n\n    #[PHPUnit\\Test]\n    public function builderShouldCreateABuilderWithCustomizedEncoderAndClaimFactory(): void\n    {\n        $config  = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n            $this->encoder,\n        );\n        $builder = $config->builder();\n\n        self::assertInstanceOf(BuilderImpl::class, $builder);\n        self::assertEquals(BuilderImpl::new($this->encoder, ChainedFormatter::default()), $builder);\n    }\n\n    #[PHPUnit\\Test]\n    public function builderShouldUseBuilderFactoryWhenThatIsConfigured(): void\n    {\n        $builder = self::createStub(Builder::class);\n\n        $config    = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n        $newConfig = $config->withBuilderFactory(\n            static function () use ($builder): Builder {\n                return $builder;\n            },\n        );\n        self::assertNotSame($builder, $config->builder());\n        self::assertSame($builder, $newConfig->builder());\n    }\n\n    #[PHPUnit\\Test]\n    public function parserShouldReturnAParserWithDefaultDecoder(): void\n    {\n        $config = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n        $parser = $config->parser();\n\n        self::assertNotEquals(new ParserImpl($this->decoder), $parser);\n    }\n\n    #[PHPUnit\\Test]\n    public function parserShouldReturnAParserWithCustomizedDecoder(): void\n    {\n        $config = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n            decoder: $this->decoder,\n        );\n        $parser = $config->parser();\n\n        self::assertEquals(new ParserImpl($this->decoder), $parser);\n    }\n\n    #[PHPUnit\\Test]\n    public function parserShouldNotCreateAnInstanceIfItWasConfigured(): void\n    {\n        $config    = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n        $newConfig = $config->withParser($this->parser);\n\n        self::assertNotSame($this->parser, $config->parser());\n        self::assertSame($this->parser, $newConfig->parser());\n    }\n\n    #[PHPUnit\\Test]\n    public function validatorShouldReturnTheDefaultWhenItWasNotConfigured(): void\n    {\n        $config    = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n        $validator = $config->validator();\n\n        self::assertNotSame($this->validator, $validator);\n    }\n\n    #[PHPUnit\\Test]\n    public function validatorShouldReturnTheConfiguredValidator(): void\n    {\n        $config    = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n        $newConfig = $config->withValidator($this->validator);\n\n        self::assertNotSame($this->validator, $config->validator());\n        self::assertSame($this->validator, $newConfig->validator());\n    }\n\n    #[PHPUnit\\Test]\n    public function validationConstraintsShouldReturnAnEmptyArrayWhenItWasNotConfigured(): void\n    {\n        $config = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n\n        self::assertSame([], $config->validationConstraints());\n    }\n\n    #[PHPUnit\\Test]\n    public function validationConstraintsShouldReturnTheConfiguredValidator(): void\n    {\n        $config    = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n        $newConfig = $config->withValidationConstraints($this->validationConstraints);\n\n        self::assertNotSame([$this->validationConstraints], $config->validationConstraints());\n        self::assertSame([$this->validationConstraints], $newConfig->validationConstraints());\n    }\n\n    #[PHPUnit\\Test]\n    public function customClaimFormatterCanBeUsed(): void\n    {\n        $formatter = self::createStub(ClaimsFormatter::class);\n        $config    = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n\n        self::assertEquals(BuilderImpl::new(new JoseEncoder(), $formatter), $config->builder($formatter));\n    }\n}\n"
  },
  {
    "path": "tests/ES512TokenTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Encoding\\MicrosecondBasedDateConversion;\nuse Lcobucci\\JWT\\Encoding\\UnifyAudience;\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha256;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha512;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse Lcobucci\\JWT\\Validation\\Validator;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\n\n#[PHPUnit\\CoversClass(Configuration::class)]\n#[PHPUnit\\CoversClass(JoseEncoder::class)]\n#[PHPUnit\\CoversClass(ChainedFormatter::class)]\n#[PHPUnit\\CoversClass(MicrosecondBasedDateConversion::class)]\n#[PHPUnit\\CoversClass(UnifyAudience::class)]\n#[PHPUnit\\CoversClass(Token\\Builder::class)]\n#[PHPUnit\\CoversClass(Token\\Parser::class)]\n#[PHPUnit\\CoversClass(Token\\Plain::class)]\n#[PHPUnit\\CoversClass(Token\\DataSet::class)]\n#[PHPUnit\\CoversClass(Token\\Signature::class)]\n#[PHPUnit\\CoversClass(Ecdsa::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\MultibyteStringConverter::class)]\n#[PHPUnit\\CoversClass(Sha256::class)]\n#[PHPUnit\\CoversClass(Sha512::class)]\n#[PHPUnit\\CoversClass(InMemory::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\CoversClass(Validator::class)]\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(SignedWith::class)]\n#[PHPUnit\\CoversClass(RequiredConstraintsViolated::class)]\nclass ES512TokenTest extends TestCase\n{\n    use Keys;\n\n    private Configuration $config;\n\n    #[PHPUnit\\Before]\n    public function createConfiguration(): void\n    {\n        $this->config = Configuration::forAsymmetricSigner(\n            new Sha512(),\n            static::$ecdsaKeys['private_ec512'],\n            static::$ecdsaKeys['public_ec512'],\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function builderShouldRaiseExceptionWhenKeyIsInvalid(): void\n    {\n        $builder = $this->config->builder()\n            ->identifiedBy('1')\n            ->permittedFor('https://client.abc.com')\n            ->issuedBy('https://api.abc.com')\n            ->withClaim('user', ['name' => 'testing', 'email' => 'testing@abc.com']);\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('It was not possible to parse your key, reason:');\n\n        $void = $builder->getToken($this->config->signer(), InMemory::plainText('testing'));\n    }\n\n    #[PHPUnit\\Test]\n    public function builderShouldRaiseExceptionWhenKeyIsNotEcdsaCompatible(): void\n    {\n        $builder = $this->config->builder()\n            ->identifiedBy('1')\n            ->permittedFor('https://client.abc.com')\n            ->issuedBy('https://api.abc.com')\n            ->withClaim('user', ['name' => 'testing', 'email' => 'testing@abc.com']);\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('The type of the provided key is not \"EC\", \"RSA\" provided');\n\n        $void = $builder->getToken($this->config->signer(), static::$rsaKeys['private']);\n    }\n\n    #[PHPUnit\\Test]\n    public function builderCanGenerateAToken(): Token\n    {\n        $user    = ['name' => 'testing', 'email' => 'testing@abc.com'];\n        $builder = $this->config->builder();\n\n        $token = $builder->identifiedBy('1')\n            ->permittedFor('https://client.abc.com')\n            ->permittedFor('https://client2.abc.com')\n            ->issuedBy('https://api.abc.com')\n            ->withClaim('user', $user)\n            ->withHeader('jki', '1234')\n            ->getToken($this->config->signer(), $this->config->signingKey());\n\n        self::assertSame('1234', $token->headers()->get('jki'));\n        self::assertSame('https://api.abc.com', $token->claims()->get(Token\\RegisteredClaims::ISSUER));\n        self::assertSame($user, $token->claims()->get('user'));\n\n        self::assertSame(\n            ['https://client.abc.com', 'https://client2.abc.com'],\n            $token->claims()->get(Token\\RegisteredClaims::AUDIENCE),\n        );\n\n        return $token;\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function parserCanReadAToken(Token $generated): void\n    {\n        $read = $this->config->parser()->parse($generated->toString());\n        assert($read instanceof Token\\Plain);\n\n        self::assertEquals($generated, $read);\n        self::assertSame('testing', $read->claims()->get('user')['name']);\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenKeyIsNotRight(Token $token): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith(\n                $this->config->signer(),\n                self::$ecdsaKeys['public2_ec512'],\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenAlgorithmIsDifferent(Token $token): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith(\n                new Sha256(),\n                self::$ecdsaKeys['public_ec512'],\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenKeyIsNotEcdsaCompatible(Token $token): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('The type of the provided key is not \"EC\", \"RSA\" provided');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith($this->config->signer(), self::$rsaKeys['public']),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureValidationShouldSucceedWhenKeyIsRight(Token $token): void\n    {\n        $constraint = new SignedWith(\n            $this->config->signer(),\n            $this->config->verificationKey(),\n        );\n\n        self::assertTrue($this->config->validator()->validate($token, $constraint));\n    }\n}\n"
  },
  {
    "path": "tests/EcdsaTokenTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Encoding\\MicrosecondBasedDateConversion;\nuse Lcobucci\\JWT\\Encoding\\UnifyAudience;\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\MultibyteStringConverter;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha256;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha512;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse Lcobucci\\JWT\\Validation\\Validator;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\n\nuse const PHP_EOL;\n\n#[PHPUnit\\CoversClass(Configuration::class)]\n#[PHPUnit\\CoversClass(JoseEncoder::class)]\n#[PHPUnit\\CoversClass(ChainedFormatter::class)]\n#[PHPUnit\\CoversClass(MicrosecondBasedDateConversion::class)]\n#[PHPUnit\\CoversClass(UnifyAudience::class)]\n#[PHPUnit\\CoversClass(Token\\Builder::class)]\n#[PHPUnit\\CoversClass(Token\\Parser::class)]\n#[PHPUnit\\CoversClass(Token\\Plain::class)]\n#[PHPUnit\\CoversClass(Token\\DataSet::class)]\n#[PHPUnit\\CoversClass(Token\\Signature::class)]\n#[PHPUnit\\CoversClass(InMemory::class)]\n#[PHPUnit\\CoversClass(Ecdsa::class)]\n#[PHPUnit\\CoversClass(MultibyteStringConverter::class)]\n#[PHPUnit\\CoversClass(Sha256::class)]\n#[PHPUnit\\CoversClass(Sha512::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\CoversClass(Validator::class)]\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(SignedWith::class)]\n#[PHPUnit\\CoversClass(RequiredConstraintsViolated::class)]\nclass EcdsaTokenTest extends TestCase\n{\n    use Keys;\n\n    private Configuration $config;\n\n    #[PHPUnit\\Before]\n    public function createConfiguration(): void\n    {\n        $this->config = Configuration::forAsymmetricSigner(\n            new Sha256(),\n            static::$ecdsaKeys['private'],\n            static::$ecdsaKeys['public1'],\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function builderShouldRaiseExceptionWhenKeyIsInvalid(): void\n    {\n        $builder = $this->config->builder()\n            ->identifiedBy('1')\n            ->permittedFor('https://client.abc.com')\n            ->issuedBy('https://api.abc.com')\n            ->withClaim('user', ['name' => 'testing', 'email' => 'testing@abc.com']);\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('It was not possible to parse your key, reason:');\n\n        $void = $builder->getToken($this->config->signer(), InMemory::plainText('testing'));\n    }\n\n    #[PHPUnit\\Test]\n    public function builderShouldRaiseExceptionWhenKeyIsNotEcdsaCompatible(): void\n    {\n        $builder = $this->config->builder()\n            ->identifiedBy('1')\n            ->permittedFor('https://client.abc.com')\n            ->issuedBy('https://api.abc.com')\n            ->withClaim('user', ['name' => 'testing', 'email' => 'testing@abc.com']);\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('The type of the provided key is not \"EC\", \"RSA\" provided');\n\n        $void = $builder->getToken($this->config->signer(), static::$rsaKeys['private']);\n    }\n\n    #[PHPUnit\\Test]\n    public function builderCanGenerateAToken(): Token\n    {\n        $user    = ['name' => 'testing', 'email' => 'testing@abc.com'];\n        $builder = $this->config->builder();\n\n        $token = $builder->identifiedBy('1')\n                         ->permittedFor('https://client.abc.com')\n                         ->permittedFor('https://client2.abc.com')\n                         ->issuedBy('https://api.abc.com')\n                         ->withClaim('user', $user)\n                         ->withHeader('jki', '1234')\n                         ->getToken($this->config->signer(), $this->config->signingKey());\n\n        self::assertSame('1234', $token->headers()->get('jki'));\n        self::assertSame('https://api.abc.com', $token->claims()->get(Token\\RegisteredClaims::ISSUER));\n        self::assertSame($user, $token->claims()->get('user'));\n\n        self::assertSame(\n            ['https://client.abc.com', 'https://client2.abc.com'],\n            $token->claims()->get(Token\\RegisteredClaims::AUDIENCE),\n        );\n\n        return $token;\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function parserCanReadAToken(Token $generated): void\n    {\n        $read = $this->config->parser()->parse($generated->toString());\n        assert($read instanceof Token\\Plain);\n\n        self::assertEquals($generated, $read);\n        self::assertSame('testing', $read->claims()->get('user')['name']);\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenKeyIsNotRight(Token $token): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith(\n                $this->config->signer(),\n                self::$ecdsaKeys['public2'],\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenAlgorithmIsDifferent(Token $token): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith(\n                new Sha512(),\n                self::$ecdsaKeys['public1'],\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenKeyIsNotEcdsaCompatible(Token $token): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('The type of the provided key is not \"EC\", \"RSA\" provided');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith($this->config->signer(), self::$rsaKeys['public']),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureValidationShouldSucceedWhenKeyIsRight(Token $token): void\n    {\n        $constraint = new SignedWith(\n            $this->config->signer(),\n            $this->config->verificationKey(),\n        );\n\n        self::assertTrue($this->config->validator()->validate($token, $constraint));\n    }\n\n    #[PHPUnit\\Test]\n    public function everythingShouldWorkWithAKeyWithParams(): void\n    {\n        $builder = $this->config->builder();\n        $signer  = $this->config->signer();\n\n        $token = $builder->identifiedBy('1')\n                         ->permittedFor('https://client.abc.com')\n                         ->issuedBy('https://api.abc.com')\n                         ->withClaim('user', ['name' => 'testing', 'email' => 'testing@abc.com'])\n                         ->withHeader('jki', '1234')\n                         ->getToken($signer, static::$ecdsaKeys['private-params']);\n\n        $constraint = new SignedWith(\n            $this->config->signer(),\n            static::$ecdsaKeys['public-params'],\n        );\n\n        self::assertTrue($this->config->validator()->validate($token, $constraint));\n    }\n\n    #[PHPUnit\\Test]\n    public function everythingShouldWorkWhenUsingATokenGeneratedByOtherLibs(): void\n    {\n        $data = 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6IndvcmxkIn0.'\n                . 'AQx1MqdTni6KuzfOoedg2-7NUiwe-b88SWbdmviz40GTwrM0Mybp1i1tVtm'\n                . 'TSQ91oEXGXBdtwsN6yalzP9J-sp2YATX_Tv4h-BednbdSvYxZsYnUoZ--ZU'\n                . 'dL10t7g8Yt3y9hdY_diOjIptcha6ajX8yzkDGYG42iSe3f5LywSuD6FO5c';\n\n        $key = '-----BEGIN PUBLIC KEY-----' . PHP_EOL\n               . 'MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAcpkss6wI7PPlxj3t7A1RqMH3nvL4' . PHP_EOL\n               . 'L5Tzxze/XeeYZnHqxiX+gle70DlGRMqqOq+PJ6RYX7vK0PJFdiAIXlyPQq0B3KaU' . PHP_EOL\n               . 'e86IvFeQSFrJdCc0K8NfiH2G1loIk3fiR+YLqlXk6FAeKtpXJKxR1pCQCAM+vBCs' . PHP_EOL\n               . 'mZudf1zCUZ8/4eodlHU=' . PHP_EOL\n               . '-----END PUBLIC KEY-----';\n\n        $token = $this->config->parser()->parse($data);\n        assert($token instanceof Token\\Plain);\n        $constraint = new SignedWith(new Sha512(), InMemory::plainText($key));\n\n        self::assertTrue($this->config->validator()->validate($token, $constraint));\n        self::assertSame('world', $token->claims()->get('hello'));\n    }\n}\n"
  },
  {
    "path": "tests/EddsaTokenTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Encoding\\MicrosecondBasedDateConversion;\nuse Lcobucci\\JWT\\Encoding\\UnifyAudience;\nuse Lcobucci\\JWT\\Signer\\Eddsa;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse Lcobucci\\JWT\\Validation\\Validator;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\n\n#[PHPUnit\\CoversClass(Configuration::class)]\n#[PHPUnit\\CoversClass(JoseEncoder::class)]\n#[PHPUnit\\CoversClass(ChainedFormatter::class)]\n#[PHPUnit\\CoversClass(MicrosecondBasedDateConversion::class)]\n#[PHPUnit\\CoversClass(UnifyAudience::class)]\n#[PHPUnit\\CoversClass(Token\\Builder::class)]\n#[PHPUnit\\CoversClass(Token\\Parser::class)]\n#[PHPUnit\\CoversClass(Token\\Plain::class)]\n#[PHPUnit\\CoversClass(Token\\DataSet::class)]\n#[PHPUnit\\CoversClass(Token\\Signature::class)]\n#[PHPUnit\\CoversClass(InMemory::class)]\n#[PHPUnit\\CoversClass(Eddsa::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\CoversClass(Validator::class)]\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(SignedWith::class)]\n#[PHPUnit\\CoversClass(RequiredConstraintsViolated::class)]\nclass EddsaTokenTest extends TestCase\n{\n    use Keys;\n\n    private Configuration $config;\n\n    #[PHPUnit\\Before]\n    public function createConfiguration(): void\n    {\n        $this->config = Configuration::forAsymmetricSigner(\n            new Eddsa(),\n            static::$eddsaKeys['private'],\n            static::$eddsaKeys['public1'],\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function builderShouldRaiseExceptionWhenKeyIsInvalid(): void\n    {\n        $builder = $this->config->builder()\n            ->identifiedBy('1')\n            ->permittedFor('https://client.abc.com')\n            ->issuedBy('https://api.abc.com')\n            ->withClaim('user', ['name' => 'testing', 'email' => 'testing@abc.com']);\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('SODIUM_CRYPTO_SIGN_SECRETKEYBYTES');\n\n        $void = $builder->getToken($this->config->signer(), InMemory::plainText('testing'));\n    }\n\n    #[PHPUnit\\Test]\n    public function builderCanGenerateAToken(): Token\n    {\n        $user    = ['name' => 'testing', 'email' => 'testing@abc.com'];\n        $builder = $this->config->builder();\n\n        $token = $builder->identifiedBy('1')\n                         ->permittedFor('https://client.abc.com')\n                         ->permittedFor('https://client2.abc.com')\n                         ->issuedBy('https://api.abc.com')\n                         ->withClaim('user', $user)\n                         ->withHeader('jki', '1234')\n                         ->getToken($this->config->signer(), $this->config->signingKey());\n\n        self::assertSame('1234', $token->headers()->get('jki'));\n        self::assertSame('https://api.abc.com', $token->claims()->get(Token\\RegisteredClaims::ISSUER));\n        self::assertSame($user, $token->claims()->get('user'));\n\n        self::assertSame(\n            ['https://client.abc.com', 'https://client2.abc.com'],\n            $token->claims()->get(Token\\RegisteredClaims::AUDIENCE),\n        );\n\n        return $token;\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function parserCanReadAToken(Token $generated): void\n    {\n        $read = $this->config->parser()->parse($generated->toString());\n        assert($read instanceof Token\\Plain);\n\n        self::assertEquals($generated, $read);\n        self::assertSame('testing', $read->claims()->get('user')['name']);\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenKeyIsNotRight(Token $token): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith(\n                $this->config->signer(),\n                self::$eddsaKeys['public2'],\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureValidationShouldSucceedWhenKeyIsRight(Token $token): void\n    {\n        $constraint = new SignedWith(\n            $this->config->signer(),\n            $this->config->verificationKey(),\n        );\n\n        self::assertTrue($this->config->validator()->validate($token, $constraint));\n    }\n}\n"
  },
  {
    "path": "tests/Encoding/ChainedFormatterTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Encoding;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\MicrosecondBasedDateConversion;\nuse Lcobucci\\JWT\\Encoding\\UnifyAudience;\nuse Lcobucci\\JWT\\Encoding\\UnixTimestampDates;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(ChainedFormatter::class)]\n#[PHPUnit\\UsesClass(MicrosecondBasedDateConversion::class)]\n#[PHPUnit\\UsesClass(UnifyAudience::class)]\n#[PHPUnit\\UsesClass(UnixTimestampDates::class)]\nfinal class ChainedFormatterTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    public function formatClaimsShouldApplyAllConfiguredFormatters(): void\n    {\n        $expiration = DateTimeImmutable::createFromFormat('U.u', '1487285080.123456');\n        self::assertInstanceOf(DateTimeImmutable::class, $expiration);\n\n        $claims = [\n            RegisteredClaims::AUDIENCE        => ['test'],\n            RegisteredClaims::EXPIRATION_TIME => $expiration,\n        ];\n\n        $formatter = ChainedFormatter::default();\n        $formatted = $formatter->formatClaims($claims);\n\n        self::assertSame('test', $formatted[RegisteredClaims::AUDIENCE]);\n        self::assertSame(1487285080.123456, $formatted[RegisteredClaims::EXPIRATION_TIME]);\n\n        $formatter = ChainedFormatter::withUnixTimestampDates();\n        $formatted = $formatter->formatClaims($claims);\n\n        self::assertSame('test', $formatted[RegisteredClaims::AUDIENCE]);\n        self::assertSame(1487285080, $formatted[RegisteredClaims::EXPIRATION_TIME]);\n    }\n}\n"
  },
  {
    "path": "tests/Encoding/JoseEncoderTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Encoding;\n\nuse Lcobucci\\JWT\\Encoding\\CannotDecodeContent;\nuse Lcobucci\\JWT\\Encoding\\CannotEncodeContent;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\nuse function base64_decode;\nuse function is_string;\n\n#[PHPUnit\\CoversClass(JoseEncoder::class)]\n#[PHPUnit\\CoversClass(CannotDecodeContent::class)]\n#[PHPUnit\\CoversClass(CannotEncodeContent::class)]\n#[PHPUnit\\UsesClass(SodiumBase64Polyfill::class)]\nfinal class JoseEncoderTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    public function jsonEncodeMustReturnAJSONString(): void\n    {\n        $encoder = new JoseEncoder();\n\n        self::assertSame('{\"test\":\"test\"}', $encoder->jsonEncode(['test' => 'test']));\n    }\n\n    #[PHPUnit\\Test]\n    public function jsonEncodeShouldNotEscapeUnicode(): void\n    {\n        $encoder = new JoseEncoder();\n\n        self::assertSame('\"汉语\"', $encoder->jsonEncode('汉语'));\n    }\n\n    #[PHPUnit\\Test]\n    public function jsonEncodeShouldNotEscapeSlashes(): void\n    {\n        $encoder = new JoseEncoder();\n\n        self::assertSame('\"https://google.com\"', $encoder->jsonEncode('https://google.com'));\n    }\n\n    #[PHPUnit\\Test]\n    public function jsonEncodeMustRaiseExceptionWhenAnErrorHasOccurred(): void\n    {\n        $encoder = new JoseEncoder();\n\n        $this->expectException(CannotEncodeContent::class);\n        $this->expectExceptionCode(0);\n        $this->expectExceptionMessage('Error while encoding to JSON');\n\n        $encoder->jsonEncode(\"\\xB1\\x31\");\n    }\n\n    #[PHPUnit\\Test]\n    public function jsonDecodeMustReturnTheDecodedData(): void\n    {\n        $decoder = new JoseEncoder();\n\n        self::assertSame(\n            ['test' => ['test' => []]],\n            $decoder->jsonDecode('{\"test\":{\"test\":{}}}'),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function jsonDecodeMustRaiseExceptionWhenAnErrorHasOccurred(): void\n    {\n        $decoder = new JoseEncoder();\n\n        $this->expectException(CannotDecodeContent::class);\n        $this->expectExceptionCode(0);\n        $this->expectExceptionMessage('Error while decoding from JSON');\n\n        $decoder->jsonDecode('{\"test\":\\'test\\'}');\n    }\n\n    #[PHPUnit\\Test]\n    public function base64UrlEncodeMustReturnAUrlSafeBase64(): void\n    {\n        $data = base64_decode('0MB2wKB+L3yvIdzeggmJ+5WOSLaRLTUPXbpzqUe0yuo=', true);\n        assert(is_string($data));\n\n        $encoder = new JoseEncoder();\n        self::assertSame('0MB2wKB-L3yvIdzeggmJ-5WOSLaRLTUPXbpzqUe0yuo', $encoder->base64UrlEncode($data));\n    }\n\n    #[PHPUnit\\Test]\n    public function base64UrlEncodeMustEncodeBilboMessageProperly(): void\n    {\n        /** @link https://tools.ietf.org/html/rfc7520#section-4 */\n        $message = 'It’s a dangerous business, Frodo, going out your door. You step '\n                   . \"onto the road, and if you don't keep your feet, there’s no knowing \"\n                   . 'where you might be swept off to.';\n\n        $expected = 'SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH'\n                    . 'lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk'\n                    . 'b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm'\n                    . 'UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4';\n\n        $encoder = new JoseEncoder();\n        self::assertSame($expected, $encoder->base64UrlEncode($message));\n    }\n\n    #[PHPUnit\\Test]\n    public function base64UrlDecodeMustRaiseExceptionWhenInvalidBase64CharsAreUsed(): void\n    {\n        $decoder = new JoseEncoder();\n\n        $this->expectException(CannotDecodeContent::class);\n        $this->expectExceptionCode(0);\n        $this->expectExceptionMessage('Error while decoding from Base64Url, invalid base64 characters detected');\n\n        $decoder->base64UrlDecode('ááá');\n    }\n\n    #[PHPUnit\\Test]\n    public function base64UrlDecodeMustReturnTheRightData(): void\n    {\n        $data = base64_decode('0MB2wKB+L3yvIdzeggmJ+5WOSLaRLTUPXbpzqUe0yuo=', true);\n\n        $decoder = new JoseEncoder();\n        self::assertSame($data, $decoder->base64UrlDecode('0MB2wKB-L3yvIdzeggmJ-5WOSLaRLTUPXbpzqUe0yuo'));\n    }\n\n    #[PHPUnit\\Test]\n    public function base64UrlDecodeMustDecodeBilboMessageProperly(): void\n    {\n        /** @link https://tools.ietf.org/html/rfc7520#section-4 */\n        $message = 'SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH'\n                   . 'lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk'\n                   . 'b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm'\n                   . 'UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4';\n\n        $expected = 'It’s a dangerous business, Frodo, going out your door. You step '\n                    . \"onto the road, and if you don't keep your feet, there’s no knowing \"\n                    . 'where you might be swept off to.';\n\n        $encoder = new JoseEncoder();\n        self::assertSame($expected, $encoder->base64UrlDecode($message));\n    }\n}\n"
  },
  {
    "path": "tests/Encoding/MicrosecondBasedDateConversionTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Encoding;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Encoding\\MicrosecondBasedDateConversion;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(MicrosecondBasedDateConversion::class)]\nfinal class MicrosecondBasedDateConversionTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    public function dateClaimsHaveMicrosecondsOrSeconds(): void\n    {\n        $issuedAt   = new DateTimeImmutable('@1487285080');\n        $notBefore  = DateTimeImmutable::createFromFormat('U.u', '1487285080.000123');\n        $expiration = DateTimeImmutable::createFromFormat('U.u', '1487285080.123456');\n\n        self::assertInstanceOf(DateTimeImmutable::class, $notBefore);\n        self::assertInstanceOf(DateTimeImmutable::class, $expiration);\n\n        $claims = [\n            RegisteredClaims::ISSUED_AT => $issuedAt,\n            RegisteredClaims::NOT_BEFORE => $notBefore,\n            RegisteredClaims::EXPIRATION_TIME => $expiration,\n            'testing' => 'test',\n        ];\n\n        $formatter = new MicrosecondBasedDateConversion();\n        $formatted = $formatter->formatClaims($claims);\n\n        self::assertSame(1487285080, $formatted[RegisteredClaims::ISSUED_AT]);\n        self::assertSame(1487285080.000123, $formatted[RegisteredClaims::NOT_BEFORE]);\n        self::assertSame(1487285080.123456, $formatted[RegisteredClaims::EXPIRATION_TIME]);\n        self::assertSame('test', $formatted['testing']); // this should remain untouched\n    }\n\n    #[PHPUnit\\Test]\n    public function notAllDateClaimsNeedToBeConfigured(): void\n    {\n        $issuedAt   = new DateTimeImmutable('@1487285080');\n        $expiration = DateTimeImmutable::createFromFormat('U.u', '1487285080.123456');\n\n        $claims = [\n            RegisteredClaims::ISSUED_AT => $issuedAt,\n            RegisteredClaims::EXPIRATION_TIME => $expiration,\n            'testing' => 'test',\n        ];\n\n        $formatter = new MicrosecondBasedDateConversion();\n        $formatted = $formatter->formatClaims($claims);\n\n        self::assertSame(1487285080, $formatted[RegisteredClaims::ISSUED_AT]);\n        self::assertSame(1487285080.123456, $formatted[RegisteredClaims::EXPIRATION_TIME]);\n        self::assertSame('test', $formatted['testing']); // this should remain untouched\n    }\n}\n"
  },
  {
    "path": "tests/Encoding/UnifyAudienceTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Encoding;\n\nuse Lcobucci\\JWT\\Encoding\\UnifyAudience;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(UnifyAudience::class)]\nfinal class UnifyAudienceTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    public function nothingShouldBeDoneWhenAudienceIsNotSet(): void\n    {\n        $claims = ['testing' => 'test'];\n\n        $formatter = new UnifyAudience();\n        $formatted = $formatter->formatClaims($claims);\n\n        self::assertSame('test', $formatted['testing']);\n    }\n\n    #[PHPUnit\\Test]\n    public function audienceShouldBeFormattedAsSingleStringWhenOneValueIsUsed(): void\n    {\n        $claims = [\n            RegisteredClaims::AUDIENCE => ['test1'],\n            'testing' => 'test',\n        ];\n\n        $formatter = new UnifyAudience();\n        $formatted = $formatter->formatClaims($claims);\n\n        self::assertSame('test1', $formatted[RegisteredClaims::AUDIENCE]);\n        self::assertSame('test', $formatted['testing']); // this should remain untouched\n    }\n\n    #[PHPUnit\\Test]\n    public function audienceShouldBeFormattedAsArrayWhenMultipleValuesAreUsed(): void\n    {\n        $claims = [\n            RegisteredClaims::AUDIENCE => ['test1', 'test2', 'test3'],\n            'testing' => 'test',\n        ];\n\n        $formatter = new UnifyAudience();\n        $formatted = $formatter->formatClaims($claims);\n\n        self::assertSame(['test1', 'test2', 'test3'], $formatted[RegisteredClaims::AUDIENCE]);\n        self::assertSame('test', $formatted['testing']); // this should remain untouched\n    }\n}\n"
  },
  {
    "path": "tests/Encoding/UnixTimestampDatesTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Encoding;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Encoding\\UnixTimestampDates;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(UnixTimestampDates::class)]\nfinal class UnixTimestampDatesTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    public function dateClaimsHaveMicrosecondsOrSeconds(): void\n    {\n        $issuedAt   = new DateTimeImmutable('@1487285080');\n        $notBefore  = DateTimeImmutable::createFromFormat('U.u', '1487285080.000123');\n        $expiration = DateTimeImmutable::createFromFormat('U.u', '1487285080.123456');\n\n        self::assertInstanceOf(DateTimeImmutable::class, $notBefore);\n        self::assertInstanceOf(DateTimeImmutable::class, $expiration);\n\n        $claims = [\n            RegisteredClaims::ISSUED_AT => $issuedAt,\n            RegisteredClaims::NOT_BEFORE => $notBefore,\n            RegisteredClaims::EXPIRATION_TIME => $expiration,\n            'testing' => 'test',\n        ];\n\n        $formatter = new UnixTimestampDates();\n        $formatted = $formatter->formatClaims($claims);\n\n        self::assertSame(1487285080, $formatted[RegisteredClaims::ISSUED_AT]);\n        self::assertSame(1487285080, $formatted[RegisteredClaims::NOT_BEFORE]);\n        self::assertSame(1487285080, $formatted[RegisteredClaims::EXPIRATION_TIME]);\n        self::assertSame('test', $formatted['testing']); // this should remain untouched\n    }\n\n    #[PHPUnit\\Test]\n    public function notAllDateClaimsNeedToBeConfigured(): void\n    {\n        $issuedAt   = new DateTimeImmutable('@1487285080');\n        $expiration = DateTimeImmutable::createFromFormat('U.u', '1487285080.123456');\n\n        $claims = [\n            RegisteredClaims::ISSUED_AT => $issuedAt,\n            RegisteredClaims::EXPIRATION_TIME => $expiration,\n            'testing' => 'test',\n        ];\n\n        $formatter = new UnixTimestampDates();\n        $formatted = $formatter->formatClaims($claims);\n\n        self::assertSame(1487285080, $formatted[RegisteredClaims::ISSUED_AT]);\n        self::assertSame(1487285080, $formatted[RegisteredClaims::EXPIRATION_TIME]);\n        self::assertSame('test', $formatted['testing']); // this should remain untouched\n    }\n}\n"
  },
  {
    "path": "tests/HmacTokenTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Encoding\\MicrosecondBasedDateConversion;\nuse Lcobucci\\JWT\\Encoding\\UnifyAudience;\nuse Lcobucci\\JWT\\Signer\\Hmac;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha512;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse Lcobucci\\JWT\\Validation\\Validator;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\nuse function file_put_contents;\nuse function sys_get_temp_dir;\nuse function tempnam;\n\n#[PHPUnit\\CoversClass(Configuration::class)]\n#[PHPUnit\\CoversClass(JoseEncoder::class)]\n#[PHPUnit\\CoversClass(ChainedFormatter::class)]\n#[PHPUnit\\CoversClass(MicrosecondBasedDateConversion::class)]\n#[PHPUnit\\CoversClass(UnifyAudience::class)]\n#[PHPUnit\\CoversClass(Token\\Builder::class)]\n#[PHPUnit\\CoversClass(Token\\Parser::class)]\n#[PHPUnit\\CoversClass(Token\\Plain::class)]\n#[PHPUnit\\CoversClass(Token\\DataSet::class)]\n#[PHPUnit\\CoversClass(Token\\Signature::class)]\n#[PHPUnit\\CoversClass(InMemory::class)]\n#[PHPUnit\\CoversClass(Hmac::class)]\n#[PHPUnit\\CoversClass(Sha256::class)]\n#[PHPUnit\\CoversClass(Sha512::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\CoversClass(Validator::class)]\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(SignedWith::class)]\n#[PHPUnit\\CoversClass(RequiredConstraintsViolated::class)]\nclass HmacTokenTest extends TestCase\n{\n    private Configuration $config;\n\n    #[PHPUnit\\Before]\n    public function createConfiguration(): void\n    {\n        $this->config = Configuration::forSymmetricSigner(\n            new Sha256(),\n            InMemory::base64Encoded('Z0Y6xrhjGQYrEDsP+7aQ3ZAKKERSBeQjP33M0H7Nq6s='),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function builderCanGenerateAToken(): Token\n    {\n        $user    = ['name' => 'testing', 'email' => 'testing@abc.com'];\n        $builder = $this->config->builder();\n\n        $token = $builder->identifiedBy('1')\n                         ->permittedFor('https://client.abc.com')\n                         ->issuedBy('https://api.abc.com')\n                         ->withClaim('user', $user)\n                         ->withHeader('jki', '1234')\n                         ->getToken($this->config->signer(), $this->config->signingKey());\n\n        self::assertSame('1234', $token->headers()->get('jki'));\n        self::assertSame(['https://client.abc.com'], $token->claims()->get(Token\\RegisteredClaims::AUDIENCE));\n        self::assertSame('https://api.abc.com', $token->claims()->get(Token\\RegisteredClaims::ISSUER));\n        self::assertSame($user, $token->claims()->get('user'));\n\n        return $token;\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function parserCanReadAToken(Token $generated): void\n    {\n        $read = $this->config->parser()->parse($generated->toString());\n        assert($read instanceof Token\\Plain);\n\n        self::assertEquals($generated, $read);\n        self::assertSame('testing', $read->claims()->get('user')['name']);\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenKeyIsNotRight(Token $token): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith(\n                $this->config->signer(),\n                InMemory::base64Encoded('O0MpjL80kE382RyX0rfr9PrNfVclXcdnru2aryanR2o='),\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenAlgorithmIsDifferent(Token $token): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith(new Sha512(), $this->config->verificationKey()),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureValidationShouldSucceedWhenKeyIsRight(Token $token): void\n    {\n        $constraint = new SignedWith($this->config->signer(), $this->config->verificationKey());\n\n        self::assertTrue($this->config->validator()->validate($token, $constraint));\n    }\n\n    #[PHPUnit\\Test]\n    public function everythingShouldWorkWhenUsingATokenGeneratedByOtherLibs(): void\n    {\n        $config = Configuration::forSymmetricSigner(\n            new Sha256(),\n            InMemory::base64Encoded('FkL2+V+1k2auI3xxTz/2skChDQVVjT9PW1/grXafg3M='),\n        );\n\n        $data = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6IndvcmxkIn0.'\n              . 'ZQfnc_iFebE--gXmnhJrqMXv3GWdH9uvdkFXTgBcMFw';\n\n        $token = $config->parser()->parse($data);\n        assert($token instanceof Token\\Plain);\n        $constraint = new SignedWith($config->signer(), $config->verificationKey());\n\n        self::assertTrue($config->validator()->validate($token, $constraint));\n        self::assertSame('world', $token->claims()->get('hello'));\n    }\n\n    #[PHPUnit\\Test]\n    public function signatureValidationWithLocalFileKeyReferenceWillOperateWithKeyContents(): void\n    {\n        $key = tempnam(sys_get_temp_dir(), 'a-very-long-prefix-to-create-a-longer-key');\n        self::assertIsString($key);\n\n        file_put_contents(\n            $key,\n            SodiumBase64Polyfill::base642bin(\n                'FkL2+V+1k2auI3xxTz/2skChDQVVjT9PW1/grXafg3M=',\n                SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL,\n            ),\n        );\n\n        $validKey      = InMemory::file($key);\n        $invalidKey    = InMemory::plainText('file://' . $key);\n        $signer        = new Sha256();\n        $configuration = Configuration::forSymmetricSigner($signer, $validKey);\n        $validator     = $configuration->validator();\n\n        $token = $configuration->builder()\n            ->withClaim('foo', 'bar')\n            ->getToken($configuration->signer(), $configuration->signingKey());\n\n        self::assertFalse(\n            $validator->validate(\n                $token,\n                new SignedWith($signer, $invalidKey),\n            ),\n            'Token cannot be validated against the **path** of the key',\n        );\n\n        self::assertTrue(\n            $validator->validate(\n                $token,\n                new SignedWith($signer, $validKey),\n            ),\n            'Token can be validated against the **contents** of the key',\n        );\n    }\n}\n"
  },
  {
    "path": "tests/JwtFacadeTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse AssertionError;\nuse DateTimeImmutable;\nuse Lcobucci\\Clock\\FrozenClock;\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\Encoding;\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer\\Hmac;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse Lcobucci\\JWT\\Validation\\Validator;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\nuse Psr\\Clock\\ClockInterface;\n\n#[PHPUnit\\CoversClass(JwtFacade::class)]\n#[PHPUnit\\UsesClass(Token\\Builder::class)]\n#[PHPUnit\\UsesClass(Token\\Parser::class)]\n#[PHPUnit\\UsesClass(Token\\Plain::class)]\n#[PHPUnit\\UsesClass(Token\\DataSet::class)]\n#[PHPUnit\\UsesClass(Token\\Signature::class)]\n#[PHPUnit\\UsesClass(Encoding\\JoseEncoder::class)]\n#[PHPUnit\\UsesClass(Encoding\\ChainedFormatter::class)]\n#[PHPUnit\\UsesClass(Encoding\\UnixTimestampDates::class)]\n#[PHPUnit\\UsesClass(Encoding\\UnifyAudience::class)]\n#[PHPUnit\\UsesClass(Hmac::class)]\n#[PHPUnit\\UsesClass(Hmac\\Sha256::class)]\n#[PHPUnit\\UsesClass(Hmac\\Sha384::class)]\n#[PHPUnit\\UsesClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\n#[PHPUnit\\UsesClass(Validator::class)]\n#[PHPUnit\\UsesClass(Constraint\\IssuedBy::class)]\n#[PHPUnit\\UsesClass(Constraint\\SignedWith::class)]\n#[PHPUnit\\UsesClass(Constraint\\SignedWithOneInSet::class)]\n#[PHPUnit\\UsesClass(Constraint\\SignedWithUntilDate::class)]\n#[PHPUnit\\UsesClass(Constraint\\StrictValidAt::class)]\n#[PHPUnit\\UsesClass(ConstraintViolation::class)]\n#[PHPUnit\\UsesClass(RequiredConstraintsViolated::class)]\nfinal class JwtFacadeTest extends TestCase\n{\n    private FrozenClock $clock;\n    private Hmac\\Sha256 $signer;\n    private InMemory $key;\n    /** @var non-empty-string */\n    private string $issuer;\n\n    #[PHPUnit\\Before]\n    public function configureDependencies(): void\n    {\n        $this->clock  = new FrozenClock(new DateTimeImmutable('2021-07-10'));\n        $this->signer = new Hmac\\Sha256();\n        $this->key    = InMemory::base64Encoded('qOIXmZRqZKY80qg0BjtCrskM6OK7gPOea8mz1H7h/dE=');\n        $this->issuer = 'bar';\n    }\n\n    /** @return non-empty-string */\n    private function createToken(): string\n    {\n        return (new JwtFacade(clock: $this->clock))->issue(\n            $this->signer,\n            $this->key,\n            fn (Builder $builder, DateTimeImmutable $issuedAt): Builder => $builder\n                    ->expiresAt($issuedAt->modify('+5 minutes'))\n                    ->issuedBy($this->issuer),\n        )->toString();\n    }\n\n    #[PHPUnit\\Test]\n    public function issueSetTimeValidity(): void\n    {\n        $token = (new JwtFacade(clock: $this->clock))->issue(\n            $this->signer,\n            $this->key,\n            static fn (Builder $builder): Builder => $builder,\n        );\n\n        $now = $this->clock->now();\n\n        self::assertTrue($token->hasBeenIssuedBefore($now));\n        self::assertTrue($token->isMinimumTimeBefore($now));\n        self::assertFalse($token->isExpired($now));\n\n        $aYearAgo = $now->modify('-1 year');\n\n        self::assertFalse($token->hasBeenIssuedBefore($aYearAgo));\n        self::assertFalse($token->isMinimumTimeBefore($aYearAgo));\n        self::assertFalse($token->isExpired($aYearAgo));\n\n        $inOneYear = $now->modify('+1 year');\n\n        self::assertTrue($token->hasBeenIssuedBefore($inOneYear));\n        self::assertTrue($token->isMinimumTimeBefore($inOneYear));\n        self::assertTrue($token->isExpired($inOneYear));\n    }\n\n    #[PHPUnit\\Test]\n    public function issueAllowsTimeValidityOverwrite(): void\n    {\n        $then  = new DateTimeImmutable('2001-02-03 04:05:06');\n        $token = (new JwtFacade())->issue(\n            $this->signer,\n            $this->key,\n            static function (Builder $builder) use ($then): Builder {\n                return $builder\n                    ->issuedAt($then)\n                    ->canOnlyBeUsedAfter($then)\n                    ->expiresAt($then->modify('+1 minute'));\n            },\n        );\n\n        $now = $then->modify('+30 seconds');\n\n        self::assertTrue($token->hasBeenIssuedBefore($now));\n        self::assertTrue($token->isMinimumTimeBefore($now));\n        self::assertFalse($token->isExpired($now));\n\n        $aYearAgo = $then->modify('-1 year');\n\n        self::assertFalse($token->hasBeenIssuedBefore($aYearAgo));\n        self::assertFalse($token->isMinimumTimeBefore($aYearAgo));\n        self::assertFalse($token->isExpired($aYearAgo));\n\n        $inOneYear = $then->modify('+1 year');\n\n        self::assertTrue($token->hasBeenIssuedBefore($inOneYear));\n        self::assertTrue($token->isMinimumTimeBefore($inOneYear));\n        self::assertTrue($token->isExpired($inOneYear));\n    }\n\n    #[PHPUnit\\Test]\n    public function goodJwt(): void\n    {\n        $token = (new JwtFacade())->parse(\n            $this->createToken(),\n            new Constraint\\SignedWith($this->signer, $this->key),\n            new Constraint\\StrictValidAt($this->clock),\n            new Constraint\\IssuedBy($this->issuer),\n        );\n\n        self::assertInstanceOf(Token\\Plain::class, $token);\n    }\n\n    #[PHPUnit\\Test]\n    public function badSigner(): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('Token signer mismatch');\n\n        $void = (new JwtFacade())->parse(\n            $this->createToken(),\n            new Constraint\\SignedWith(new Hmac\\Sha384(), $this->key),\n            new Constraint\\StrictValidAt($this->clock),\n            new Constraint\\IssuedBy($this->issuer),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function badKey(): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('Token signature mismatch');\n\n        $void = (new JwtFacade())->parse(\n            $this->createToken(),\n            new Constraint\\SignedWith(\n                $this->signer,\n                InMemory::base64Encoded('czyPTpN595zVNSuvoNNlXCRFgXS2fHscMR36dGojaUE='),\n            ),\n            new Constraint\\StrictValidAt($this->clock),\n            new Constraint\\IssuedBy($this->issuer),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function badTime(): void\n    {\n        $token = $this->createToken();\n        $this->clock->setTo($this->clock->now()->modify('+30 days'));\n\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token is expired');\n\n        $void = (new JwtFacade())->parse(\n            $token,\n            new Constraint\\SignedWith($this->signer, $this->key),\n            new Constraint\\StrictValidAt($this->clock),\n            new Constraint\\IssuedBy($this->issuer),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function badIssuer(): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token was not issued by the given issuers');\n\n        $void = (new JwtFacade())->parse(\n            $this->createToken(),\n            new Constraint\\SignedWith($this->signer, $this->key),\n            new Constraint\\StrictValidAt($this->clock),\n            new Constraint\\IssuedBy('xyz'),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function parserForNonUnencryptedTokens(): void\n    {\n        $this->expectException(AssertionError::class);\n\n        $void = (new JwtFacade(new UnsupportedParser()))->parse(\n            'a.very-broken.token',\n            new Constraint\\SignedWith($this->signer, $this->key),\n            new Constraint\\StrictValidAt($this->clock),\n            new Constraint\\IssuedBy($this->issuer),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function customPsrClock(): void\n    {\n        $clock = new class () implements ClockInterface {\n            public function now(): DateTimeImmutable\n            {\n                return new DateTimeImmutable('2021-07-10');\n            }\n        };\n\n        $facade = new JwtFacade(clock: $clock);\n\n        $token = $facade->issue(\n            $this->signer,\n            $this->key,\n            static fn (Builder $builder): Builder => $builder,\n        );\n\n        self::assertEquals(\n            $token,\n            $facade->parse(\n                $token->toString(),\n                new Constraint\\SignedWith($this->signer, $this->key),\n                new Constraint\\StrictValidAt($clock),\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function multipleKeys(): void\n    {\n        $clock = new FrozenClock(new DateTimeImmutable('2023-11-19 22:10:00'));\n\n        $token = (new JwtFacade())->parse(\n            $this->createToken(),\n            new Constraint\\SignedWithOneInSet(\n                new Constraint\\SignedWithUntilDate(\n                    $this->signer,\n                    InMemory::base64Encoded('czyPTpN595zVNSuvoNNlXCRFgXS2fHscMR36dGojaUE='),\n                    new DateTimeImmutable('2024-11-19 22:10:00'),\n                    $clock,\n                ),\n                new Constraint\\SignedWithUntilDate(\n                    $this->signer,\n                    $this->key,\n                    new DateTimeImmutable('2025-11-19 22:10:00'),\n                    $clock,\n                ),\n            ),\n            new Constraint\\StrictValidAt($this->clock),\n            new Constraint\\IssuedBy($this->issuer),\n        );\n\n        self::assertInstanceOf(Token\\Plain::class, $token);\n    }\n}\n"
  },
  {
    "path": "tests/KeyDumpSigner.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key;\n\nfinal readonly class KeyDumpSigner implements Signer\n{\n    public function algorithmId(): string\n    {\n        return 'keydump';\n    }\n\n    // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter\n    public function sign(string $payload, Key $key): string\n    {\n        return $key->contents();\n    }\n\n    // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter\n    public function verify(string $expected, string $payload, Key $key): bool\n    {\n        return $expected === $key->contents();\n    }\n}\n"
  },
  {
    "path": "tests/Keys.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Signer\\Key;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\ntrait Keys\n{\n    /** @var array<string, Key> */\n    protected static array $rsaKeys;\n\n    /** @var array<string, Key> */\n    protected static array $ecdsaKeys;\n\n    /** @var array<string, Key> */\n    protected static array $eddsaKeys;\n\n    #[PHPUnit\\BeforeClass]\n    public static function createRsaKeys(): void\n    {\n        if (isset(static::$rsaKeys)) {\n            return;\n        }\n\n        static::$rsaKeys = [\n            'private'           => Key\\InMemory::file(__DIR__ . '/_keys/rsa/private.key'),\n            'public'            => Key\\InMemory::file(__DIR__ . '/_keys/rsa/public.key'),\n            'encrypted-private' => Key\\InMemory::file(__DIR__ . '/_keys/rsa/encrypted-private.key', 'testing'),\n            'encrypted-public'  => Key\\InMemory::file(__DIR__ . '/_keys/rsa/encrypted-public.key'),\n            'private_short'     => Key\\InMemory::file(__DIR__ . '/_keys/rsa/private_512.key'),\n            'public_short'      => Key\\InMemory::file(__DIR__ . '/_keys/rsa/public_512.key'),\n        ];\n    }\n\n    #[PHPUnit\\BeforeClass]\n    public static function createEcdsaKeys(): void\n    {\n        if (isset(static::$ecdsaKeys)) {\n            return;\n        }\n\n        static::$ecdsaKeys = [\n            'private'        => Key\\InMemory::file(__DIR__ . '/_keys/ecdsa/private.key'),\n            'private-params' => Key\\InMemory::file(__DIR__ . '/_keys/ecdsa/private2.key'),\n            'public1'        => Key\\InMemory::file(__DIR__ . '/_keys/ecdsa/public1.key'),\n            'public2'        => Key\\InMemory::file(__DIR__ . '/_keys/ecdsa/public2.key'),\n            'public-params'  => Key\\InMemory::file(__DIR__ . '/_keys/ecdsa/public3.key'),\n            'private_ec384'  => Key\\InMemory::file(__DIR__ . '/_keys/ecdsa/private_ec384.key'),\n            'public_ec384'   => Key\\InMemory::file(__DIR__ . '/_keys/ecdsa/public_ec384.key'),\n            'private_ec512'  => Key\\InMemory::file(__DIR__ . '/_keys/ecdsa/private_ec512.key'),\n            'public_ec512'   => Key\\InMemory::file(__DIR__ . '/_keys/ecdsa/public_ec512.key'),\n            'public2_ec512'  => Key\\InMemory::file(__DIR__ . '/_keys/ecdsa/public2_ec512.key'),\n        ];\n    }\n\n    #[PHPUnit\\BeforeClass]\n    public static function createEddsaKeys(): void\n    {\n        if (isset(static::$eddsaKeys)) {\n            return;\n        }\n\n        static::$eddsaKeys = [\n            'private' => Key\\InMemory::base64Encoded(\n                'K3NWT0XqaH+4jgi42gQmHnFE+HTPVhFYi3u4DFJ3OpRHRMt/aGRBoKD/Pt5H/iYgGCla7Q04CdjOUpLSrjZhtg==',\n            ),\n            'public1' => Key\\InMemory::base64Encoded('R0TLf2hkQaCg/z7eR/4mIBgpWu0NOAnYzlKS0q42YbY='),\n            'public2' => Key\\InMemory::base64Encoded('8uLLzCdMrIWcOrAxS/fteYyJhWIGH+wav2fNz8NZhvI='),\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/MaliciousTamperingPreventionTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Encoding;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha512 as ES512;\nuse Lcobucci\\JWT\\Signer\\Hmac;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256 as HS512;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Token\\Plain;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\Validator;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\nuse function explode;\nuse function hash_hmac;\nuse function implode;\n\nuse const PHP_EOL;\n\n#[PHPUnit\\CoversClass(Configuration::class)]\n#[PHPUnit\\CoversClass(Encoding\\JoseEncoder::class)]\n#[PHPUnit\\CoversClass(Token\\Parser::class)]\n#[PHPUnit\\CoversClass(Token\\Plain::class)]\n#[PHPUnit\\CoversClass(Token\\DataSet::class)]\n#[PHPUnit\\CoversClass(Token\\Signature::class)]\n#[PHPUnit\\CoversClass(Ecdsa::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\Sha512::class)]\n#[PHPUnit\\CoversClass(Hmac\\Sha256::class)]\n#[PHPUnit\\CoversClass(InMemory::class)]\n#[PHPUnit\\CoversClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(Validator::class)]\n#[PHPUnit\\CoversClass(SignedWith::class)]\nfinal class MaliciousTamperingPreventionTest extends TestCase\n{\n    use Keys;\n\n    private Configuration $config;\n\n    #[PHPUnit\\Before]\n    public function createConfiguration(): void\n    {\n        $this->config = Configuration::forAsymmetricSigner(\n            new ES512(),\n            InMemory::plainText('my-private-key'),\n            InMemory::plainText(\n                '-----BEGIN PUBLIC KEY-----' . PHP_EOL\n                . 'MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAcpkss6wI7PPlxj3t7A1RqMH3nvL4' . PHP_EOL\n                . 'L5Tzxze/XeeYZnHqxiX+gle70DlGRMqqOq+PJ6RYX7vK0PJFdiAIXlyPQq0B3KaU' . PHP_EOL\n                . 'e86IvFeQSFrJdCc0K8NfiH2G1loIk3fiR+YLqlXk6FAeKtpXJKxR1pCQCAM+vBCs' . PHP_EOL\n                . 'mZudf1zCUZ8/4eodlHU=' . PHP_EOL\n                . '-----END PUBLIC KEY-----',\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function preventRegressionsThatAllowsMaliciousTampering(): void\n    {\n        $data = 'eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJoZWxsbyI6IndvcmxkIn0.'\n            . 'AQx1MqdTni6KuzfOoedg2-7NUiwe-b88SWbdmviz40GTwrM0Mybp1i1tVtm'\n            . 'TSQ91oEXGXBdtwsN6yalzP9J-sp2YATX_Tv4h-BednbdSvYxZsYnUoZ--ZU'\n            . 'dL10t7g8Yt3y9hdY_diOjIptcha6ajX8yzkDGYG42iSe3f5LywSuD6FO5c';\n\n        // Let's let the attacker tamper with our message!\n        $bad = $this->createMaliciousToken($data);\n\n        /**\n         * At this point, we have our forged message in $bad for testing...\n         *\n         * Now, if we allow the attacker to dictate what Signer we use\n         * (e.g. HMAC-SHA512 instead of ECDSA), they can forge messages!\n         */\n\n        $token = $this->config->parser()->parse($bad);\n        assert($token instanceof Plain);\n\n        self::assertSame('world', $token->claims()->get('hello'), 'The claim content should not be modified');\n\n        $validator = $this->config->validator();\n\n        self::assertFalse(\n            $validator->validate($token, new SignedWith(new HS512(), $this->config->verificationKey())),\n            'Using the attackers signer should make things unsafe',\n        );\n\n        self::assertFalse(\n            $validator->validate(\n                $token,\n                new SignedWith(\n                    $this->config->signer(),\n                    $this->config->verificationKey(),\n                ),\n            ),\n            'But we know which Signer should be used so the attack fails',\n        );\n    }\n\n    /** @return non-empty-string */\n    private function createMaliciousToken(string $token): string\n    {\n        $dec     = new JoseEncoder();\n        $asplode = explode('.', $token);\n\n        // The user is lying; we insist that we're using HMAC-SHA512, with the\n        // public key as the HMAC secret key. This just builds a forged message:\n        $asplode[0] = $dec->base64UrlEncode('{\"alg\":\"HS512\",\"typ\":\"JWT\"}');\n\n        $hmac = hash_hmac(\n            'sha512',\n            $asplode[0] . '.' . $asplode[1],\n            $this->config->verificationKey()->contents(),\n            true,\n        );\n\n        $asplode[2] = $dec->base64UrlEncode($hmac);\n\n        return implode('.', $asplode);\n    }\n}\n"
  },
  {
    "path": "tests/RFC6978VectorTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha256;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha384;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha512;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function hex2bin;\n\nuse const PHP_EOL;\n\n#[PHPUnit\\CoversClass(InMemory::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(Ecdsa::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\MultibyteStringConverter::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\Sha256::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\Sha384::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\Sha512::class)]\nfinal class RFC6978VectorTest extends TestCase\n{\n    /**\n     * @see https://tools.ietf.org/html/rfc6979#appendix-A.2.5\n     * @see https://tools.ietf.org/html/rfc6979#appendix-A.2.6\n     * @see https://tools.ietf.org/html/rfc6979#appendix-A.2.7\n     *\n     * @param non-empty-string $payload\n     */\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('dataRFC6979')]\n    public function theVectorsFromRFC6978CanBeVerified(\n        Ecdsa $signer,\n        Key $key,\n        string $payload,\n        string $expectedR,\n        string $expectedS,\n    ): void {\n        $signature = hex2bin($expectedR . $expectedS);\n        self::assertIsString($signature);\n        self::assertNotSame('', $signature);\n\n        static::assertTrue($signer->verify($signature, $payload, $key));\n    }\n\n    /** @return mixed[] */\n    public static function dataRFC6979(): iterable\n    {\n        yield from self::sha256Data();\n        yield from self::sha384Data();\n        yield from self::sha512Data();\n    }\n\n    /** @return mixed[] */\n    public static function sha256Data(): iterable\n    {\n        $signer = new Sha256();\n        $key    = InMemory::plainText(\n            '-----BEGIN PUBLIC KEY-----' . PHP_EOL\n            . 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYP7UuiVanTHJYet0xjVtaMBJuJI7' . PHP_EOL\n            . 'Yfps5mliLmDyn7Z5A/4QCLi8maQa6elWKLxk8vGyDC1+n1F3o8KU1EYimQ==' . PHP_EOL\n            . '-----END PUBLIC KEY-----',\n        );\n\n        yield 'SHA-256 (sample)' => [\n            $signer,\n            $key,\n            'sample',\n            'EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716',\n            'F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8',\n        ];\n\n        yield 'SHA-256 (test)' => [\n            $signer,\n            $key,\n            'test',\n            'F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367',\n            '019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083',\n        ];\n    }\n\n    /** @return mixed[] */\n    public static function sha384Data(): iterable\n    {\n        $signer = new Sha384();\n        $key    = InMemory::plainText(\n            '-----BEGIN PUBLIC KEY-----' . PHP_EOL\n            . 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7DpOQVtOGaRWhhgCn0J/pdqai8SukuAu' . PHP_EOL\n            . 'BqrlKGswDGTe+PDqkFWGYGSiVFFUgLwTgBXZty19VyROqO+awMYhiWcIpZNn+d+5' . PHP_EOL\n            . '9UyoSz8cnbEoiyMcOuDU/nNE/SUzJkcg' . PHP_EOL\n            . '-----END PUBLIC KEY-----',\n        );\n\n        yield 'SHA-384 (sample)' => [\n            $signer,\n            $key,\n            'sample',\n            '94EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE46',\n            '99EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8',\n        ];\n\n        yield 'SHA-384 (test)' => [\n            $signer,\n            $key,\n            'test',\n            '8203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB',\n            'DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B827C2F13173923E06A739F040649A667BF3B828246BAA5A5',\n        ];\n    }\n\n    /** @return mixed[] */\n    public static function sha512Data(): iterable\n    {\n        $signer = new Sha512();\n        $key    = InMemory::plainText(\n            '-----BEGIN PUBLIC KEY-----' . PHP_EOL\n            . 'MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBiUVQ0HhZMuAOqiO2lPIT+MMSH4bc' . PHP_EOL\n            . 'l6BOWnFn205bzTcRI9RuRdtrXVNwp/IPtjMVXTj/oW0r12HcrEdLmi9QI6QASTEB' . PHP_EOL\n            . 'yWLNTS/d94IoXmRYQTnC+RtH+H/4I1TWYw90aiig2yV0G1s0qCgAiyKswj+ST6r7' . PHP_EOL\n            . '1NM/gepmlW3+qiv9/PU=' . PHP_EOL\n            . '-----END PUBLIC KEY-----',\n        );\n\n        yield 'SHA-512 (sample)' => [\n            $signer,\n            $key,\n            'sample',\n            '00C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F174E25A1EE9017B5D450606ADD152B534931D7D4E8'\n            . '455CC91F9B15BF05EC36E377FA',\n            '00617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF282623EAA63E5B5C0723D8B8C37FF0777B1A20F8CC'\n            . 'B1DCCC43997F1EE0E44DA4A67A',\n        ];\n\n        yield 'SHA-512 (test)' => [\n            $signer,\n            $key,\n            'test',\n            '013E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10CDB93482FDCC54EDCEE91ECA4166B2A7C6265EF0C'\n            . 'E2BD7051B7CEF945BABD47EE6D',\n            '01FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A19CA69EFF5C57400E3B3A0AD66CE0978214D13BA'\n            . 'F4E9AC60752F7B155E2DE4DCE3',\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/RsaTokenTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Encoding;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse Lcobucci\\JWT\\Signer\\Rsa;\nuse Lcobucci\\JWT\\Signer\\Rsa\\Sha256;\nuse Lcobucci\\JWT\\Signer\\Rsa\\Sha512;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse Lcobucci\\JWT\\Validation\\Validator;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\n\n#[PHPUnit\\CoversClass(Configuration::class)]\n#[PHPUnit\\CoversClass(Encoding\\JoseEncoder::class)]\n#[PHPUnit\\CoversClass(Encoding\\ChainedFormatter::class)]\n#[PHPUnit\\CoversClass(Encoding\\MicrosecondBasedDateConversion::class)]\n#[PHPUnit\\CoversClass(Encoding\\UnifyAudience::class)]\n#[PHPUnit\\CoversClass(Token\\Builder::class)]\n#[PHPUnit\\CoversClass(Token\\Parser::class)]\n#[PHPUnit\\CoversClass(Token\\Plain::class)]\n#[PHPUnit\\CoversClass(Token\\DataSet::class)]\n#[PHPUnit\\CoversClass(Token\\Signature::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(Rsa::class)]\n#[PHPUnit\\CoversClass(Rsa\\Sha256::class)]\n#[PHPUnit\\CoversClass(Rsa\\Sha512::class)]\n#[PHPUnit\\CoversClass(InMemory::class)]\n#[PHPUnit\\CoversClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(RequiredConstraintsViolated::class)]\n#[PHPUnit\\CoversClass(Validator::class)]\n#[PHPUnit\\CoversClass(SignedWith::class)]\nclass RsaTokenTest extends TestCase\n{\n    use Keys;\n\n    private Configuration $config;\n\n    #[PHPUnit\\Before]\n    public function createConfiguration(): void\n    {\n        $this->config = Configuration::forAsymmetricSigner(\n            new Sha256(),\n            static::$rsaKeys['private'],\n            static::$rsaKeys['public'],\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function builderShouldRaiseExceptionWhenKeyIsInvalid(): void\n    {\n        $builder = $this->config->builder()\n            ->identifiedBy('1')\n            ->permittedFor('https://client.abc.com')\n            ->issuedBy('https://api.abc.com')\n            ->withClaim('user', ['name' => 'testing', 'email' => 'testing@abc.com']);\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('It was not possible to parse your key');\n\n        $void = $builder->getToken($this->config->signer(), InMemory::plainText('testing'));\n    }\n\n    #[PHPUnit\\Test]\n    public function builderShouldRaiseExceptionWhenKeyIsNotRsaCompatible(): void\n    {\n        $builder = $this->config->builder()\n            ->identifiedBy('1')\n            ->permittedFor('https://client.abc.com')\n            ->issuedBy('https://api.abc.com')\n            ->withClaim('user', ['name' => 'testing', 'email' => 'testing@abc.com']);\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('The type of the provided key is not \"RSA\", \"EC\" provided');\n\n        $void = $builder->getToken($this->config->signer(), static::$ecdsaKeys['private']);\n    }\n\n    #[PHPUnit\\Test]\n    public function builderCanGenerateAToken(): Token\n    {\n        $user    = ['name' => 'testing', 'email' => 'testing@abc.com'];\n        $builder = $this->config->builder();\n\n        $token = $builder->identifiedBy('1')\n                         ->permittedFor('https://client.abc.com')\n                         ->issuedBy('https://api.abc.com')\n                         ->withClaim('user', $user)\n                         ->withHeader('jki', '1234')\n                         ->getToken($this->config->signer(), $this->config->signingKey());\n\n        self::assertSame('1234', $token->headers()->get('jki'));\n        self::assertSame(['https://client.abc.com'], $token->claims()->get(Token\\RegisteredClaims::AUDIENCE));\n        self::assertSame('https://api.abc.com', $token->claims()->get(Token\\RegisteredClaims::ISSUER));\n        self::assertSame($user, $token->claims()->get('user'));\n\n        return $token;\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function parserCanReadAToken(Token $generated): void\n    {\n        $read = $this->config->parser()->parse($generated->toString());\n        assert($read instanceof Token\\Plain);\n\n        self::assertEquals($generated, $read);\n        self::assertSame('testing', $read->claims()->get('user')['name']);\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenKeyIsNotRight(Token $token): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith($this->config->signer(), self::$rsaKeys['encrypted-public']),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenAlgorithmIsDifferent(Token $token): void\n    {\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith(new Sha512(), $this->config->verificationKey()),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureAssertionShouldRaiseExceptionWhenKeyIsNotRsaCompatible(Token $token): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('The type of the provided key is not \"RSA\", \"EC\" provided');\n\n        $this->config->validator()->assert(\n            $token,\n            new SignedWith(\n                $this->config->signer(),\n                self::$ecdsaKeys['public1'],\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function signatureValidationShouldSucceedWhenKeyIsRight(Token $token): void\n    {\n        $constraint = new SignedWith($this->config->signer(), $this->config->verificationKey());\n\n        self::assertTrue($this->config->validator()->validate($token, $constraint));\n    }\n\n    #[PHPUnit\\Test]\n    public function everythingShouldWorkWhenUsingATokenGeneratedByOtherLibs(): void\n    {\n        $data = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJoZWxsbyI6IndvcmxkIn0.s'\n                . 'GYbB1KrmnESNfJ4D9hOe1Zad_BMyxdb8G4p4LNP7StYlOyBWck6q7XPpPj_6gB'\n                . 'Bo1ohD3MA2o0HY42lNIrAStaVhfsFKGdIou8TarwMGZBPcif_3ThUV1pGS3fZc'\n                . 'lFwF2SP7rqCngQis_xcUVCyqa8E1Wa_v28grnl1QZrnmQFO8B5JGGLqcrfUHJO'\n                . 'nJCupP-Lqh4TmIhftIimSCgLNmJg80wyrpUEfZYReE7hPuEmY0ClTqAGIMQoNS'\n                . '98ljwDxwhfbSuL2tAdbV4DekbTpWzspe3dOJ7RSzmPKVZ6NoezaIazKqyqkmHZfcMaHI1lQeGia6LTbHU1bp0gINi74Vw';\n\n        $token = $this->config->parser()->parse($data);\n        assert($token instanceof Token\\Plain);\n        $constraint = new SignedWith($this->config->signer(), $this->config->verificationKey());\n\n        self::assertTrue($this->config->validator()->validate($token, $constraint));\n        self::assertSame('world', $token->claims()->get('hello'));\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Blake2bTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer;\n\nuse Lcobucci\\JWT\\Signer\\Blake2b;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function hash_equals;\n\n#[PHPUnit\\CoversClass(Blake2b::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\n#[PHPUnit\\UsesClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(SodiumBase64Polyfill::class)]\nfinal class Blake2bTest extends TestCase\n{\n    private const string KEY_ONE                    = 'GOu4rLyVCBxmxP+sbniU68ojAja5PkRdvv7vNvBCqDQ=';\n    private const string KEY_TWO                    = 'Pu7gywseH+R5HLIWnMll4rEg1ltjUPq/P9WwEzAsAb8=';\n    private const string CONTENTS                   = 'test';\n    private const string EXPECTED_HASH_WITH_KEY_ONE = '/TG5kmkav/YGl3I9uQiv4cm1VN6Q0zPCom4G7+p74JU=';\n\n    private const string SHORT_KEY = 'PIBQuM5PopdMxtmTWmyvNA==';\n\n    private InMemory $keyOne;\n    private InMemory $keyTwo;\n    /** @var non-empty-string */\n    private string $expectedHashWithKeyOne;\n\n    #[PHPUnit\\Before]\n    public function initializeKey(): void\n    {\n        $this->keyOne = InMemory::base64Encoded(self::KEY_ONE);\n        $this->keyTwo = InMemory::base64Encoded(self::KEY_TWO);\n\n        $this->expectedHashWithKeyOne = SodiumBase64Polyfill::base642bin(\n            self::EXPECTED_HASH_WITH_KEY_ONE,\n            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL,\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function algorithmIdMustBeCorrect(): void\n    {\n        $signer = new Blake2b();\n\n        self::assertSame('BLAKE2B', $signer->algorithmId());\n    }\n\n    #[PHPUnit\\Test]\n    public function generatedSignatureMustBeSuccessfullyVerified(): void\n    {\n        $signer = new Blake2b();\n\n        self::assertTrue(hash_equals($this->expectedHashWithKeyOne, $signer->sign(self::CONTENTS, $this->keyOne)));\n        self::assertTrue($signer->verify($this->expectedHashWithKeyOne, self::CONTENTS, $this->keyOne));\n    }\n\n    #[PHPUnit\\Test]\n    public function signShouldRejectShortKeys(): void\n    {\n        $signer = new Blake2b();\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('Key provided is shorter than 256 bits, only 128 bits provided');\n\n        $signer->sign(self::CONTENTS, InMemory::base64Encoded(self::SHORT_KEY));\n    }\n\n    #[PHPUnit\\Test]\n    public function verifyShouldReturnFalseWhenExpectedHashWasNotCreatedWithSameInformation(): void\n    {\n        $signer = new Blake2b();\n\n        self::assertFalse(hash_equals($this->expectedHashWithKeyOne, $signer->sign(self::CONTENTS, $this->keyTwo)));\n        self::assertFalse($signer->verify($this->expectedHashWithKeyOne, self::CONTENTS, $this->keyTwo));\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Ecdsa/EcdsaTestCase.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Ecdsa;\n\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\MultibyteStringConverter;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Tests\\Keys;\nuse OpenSSLAsymmetricKey;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\nuse function openssl_error_string;\nuse function openssl_pkey_get_private;\nuse function openssl_pkey_get_public;\nuse function openssl_sign;\nuse function openssl_verify;\n\nabstract class EcdsaTestCase extends TestCase\n{\n    use Keys;\n\n    protected MultibyteStringConverter $pointsManipulator;\n\n    #[PHPUnit\\After]\n    final public function clearOpenSSLErrors(): void\n    {\n        // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedWhile\n        while (openssl_error_string()) {\n        }\n    }\n\n    #[PHPUnit\\Before]\n    final public function createDependencies(): void\n    {\n        $this->pointsManipulator = new MultibyteStringConverter();\n    }\n\n    abstract protected function algorithm(): Ecdsa;\n\n    abstract protected function algorithmId(): string;\n\n    abstract protected function signatureAlgorithm(): int;\n\n    abstract protected function pointLength(): int;\n\n    abstract protected function keyLength(): int;\n\n    abstract protected function verificationKey(): Key;\n\n    abstract protected function signingKey(): Key;\n\n    #[PHPUnit\\Test]\n    final public function algorithmIdMustBeCorrect(): void\n    {\n        self::assertSame($this->algorithmId(), $this->algorithm()->algorithmId());\n    }\n\n    #[PHPUnit\\Test]\n    final public function signatureAlgorithmMustBeCorrect(): void\n    {\n        self::assertSame($this->signatureAlgorithm(), $this->algorithm()->algorithm());\n    }\n\n    #[PHPUnit\\Test]\n    final public function pointLengthMustBeCorrect(): void\n    {\n        self::assertSame($this->pointLength(), $this->algorithm()->pointLength());\n    }\n\n    #[PHPUnit\\Test]\n    final public function expectedKeyLengthMustBeCorrect(): void\n    {\n        self::assertSame($this->keyLength(), $this->algorithm()->expectedKeyLength());\n    }\n\n    #[PHPUnit\\Test]\n    public function signShouldReturnTheAHashBasedOnTheOpenSslSignature(): void\n    {\n        $payload = 'testing';\n\n        $signer    = $this->algorithm();\n        $signature = $signer->sign($payload, $this->signingKey());\n\n        $publicKey = openssl_pkey_get_public($this->verificationKey()->contents());\n        assert($publicKey instanceof OpenSSLAsymmetricKey);\n\n        self::assertSame(\n            1,\n            openssl_verify(\n                $payload,\n                $this->pointsManipulator->toAsn1($signature, $signer->pointLength()),\n                $publicKey,\n                $this->signatureAlgorithm(),\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('incompatibleKeys')]\n    public function signShouldRaiseAnExceptionWhenKeyLengthIsNotTheExpectedOne(\n        string $keyId,\n        int $keyLength,\n    ): void {\n        self::assertArrayHasKey($keyId, self::$ecdsaKeys);\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage(\n            'The length of the provided key is different than ' . $this->keyLength()\n            . ' bits, ' . $keyLength . ' bits provided',\n        );\n\n        $this->algorithm()->sign('testing', self::$ecdsaKeys[$keyId]);\n    }\n\n    /** @return iterable<string, array{string, int}> */\n    abstract public static function incompatibleKeys(): iterable;\n\n    #[PHPUnit\\Test]\n    public function signShouldRaiseAnExceptionWhenKeyTypeIsNotEC(): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('The type of the provided key is not \"EC\", \"RSA\" provided');\n\n        $this->algorithm()->sign('testing', self::$rsaKeys['private']);\n    }\n\n    #[PHPUnit\\Test]\n    public function verifyShouldDelegateToEcdsaSignerUsingPublicKey(): void\n    {\n        $payload    = 'testing';\n        $privateKey = openssl_pkey_get_private($this->signingKey()->contents());\n        assert($privateKey instanceof OpenSSLAsymmetricKey);\n\n        $signature = '';\n        openssl_sign($payload, $signature, $privateKey, $this->signatureAlgorithm());\n\n        $signer = $this->algorithm();\n\n        self::assertTrue(\n            $signer->verify(\n                $this->pointsManipulator->fromAsn1($signature, $signer->pointLength()),\n                $payload,\n                $this->verificationKey(),\n            ),\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Ecdsa/MultibyteStringConverterTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Ecdsa;\n\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\ConversionFailed;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\MultibyteStringConverter;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function bin2hex;\nuse function hex2bin;\nuse function strlen;\n\n/** @coversDefaultClass \\Lcobucci\\JWT\\Signer\\Ecdsa\\MultibyteStringConverter */\n#[PHPUnit\\CoversClass(MultibyteStringConverter::class)]\n#[PHPUnit\\CoversClass(ConversionFailed::class)]\nfinal class MultibyteStringConverterTest extends TestCase\n{\n    /**\n     * @param non-empty-string $r\n     * @param non-empty-string $s\n     * @param non-empty-string $asn1\n     */\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('pointsConversionData')]\n    public function toAsn1ShouldReturnThePointsInAnAsn1SequenceFormat(\n        string $r,\n        string $s,\n        string $asn1,\n    ): void {\n        $converter = new MultibyteStringConverter();\n        $message   = hex2bin($r . $s);\n        self::assertIsString($message);\n        self::assertNotSame('', $message);\n\n        self::assertSame($asn1, bin2hex($converter->toAsn1($message, strlen($r))));\n    }\n\n    #[PHPUnit\\Test]\n    public function toAsn1ShouldRaiseExceptionWhenPointsDoNotHaveCorrectLength(): void\n    {\n        $converter = new MultibyteStringConverter();\n\n        $this->expectException(ConversionFailed::class);\n        $this->expectExceptionMessage('Invalid signature length');\n        $converter->toAsn1('a very wrong string', 64);\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('pointsConversionData')]\n    public function fromAsn1ShouldReturnTheConcatenatedPoints(string $r, string $s, string $asn1): void\n    {\n        $converter = new MultibyteStringConverter();\n        $message   = hex2bin($asn1);\n        self::assertIsString($message);\n        self::assertNotSame('', $message);\n\n        self::assertSame($r . $s, bin2hex($converter->fromAsn1($message, strlen($r))));\n    }\n\n    /** @return string[][] */\n    public static function pointsConversionData(): iterable\n    {\n        yield [\n            'efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716',\n            'f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8',\n            '3046022100efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716022100f7cb1c942d657c41d436c7'\n            . 'a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8',\n        ];\n\n        yield [\n            '94edbb92a5ecb8aad4736e56c691916b3f88140666ce9fa73d64c4ea95ad133c81a648152e44acf96e36dd1e80fabe46',\n            '99ef4aeb15f178cea1fe40db2603138f130e740a19624526203b6351d0a3a94fa329c145786e679e7b82c71a38628ac8',\n            '306602310094edbb92a5ecb8aad4736e56c691916b3f88140666ce9fa73d64c4ea95ad133c81a648152e44acf96e36dd1e80fa'\n            . 'be4602310099ef4aeb15f178cea1fe40db2603138f130e740a19624526203b6351d0a3a94fa329c145786e679e7b82c71a38'\n            . '628ac8',\n        ];\n\n        yield [\n            '00c328fafcbd79dd77850370c46325d987cb525569fb63c5d3bc53950e6d4c5f174e25a1ee9017b5d450606add152b534931d7'\n            . 'd4e8455cc91f9b15bf05ec36e377fa',\n            '00617cce7cf5064806c467f678d3b4080d6f1cc50af26ca209417308281b68af282623eaa63e5b5c0723d8b8c37ff0777b1a20'\n            . 'f8ccb1dccc43997f1ee0e44da4a67a',\n            '308187024200c328fafcbd79dd77850370c46325d987cb525569fb63c5d3bc53950e6d4c5f174e25a1ee9017b5d450606add15'\n            . '2b534931d7d4e8455cc91f9b15bf05ec36e377fa0241617cce7cf5064806c467f678d3b4080d6f1cc50af26ca20941730828'\n            . '1b68af282623eaa63e5b5c0723d8b8c37ff0777b1a20f8ccb1dccc43997f1ee0e44da4a67a',\n        ];\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('invalidAsn1Structures')]\n    public function fromAsn1ShouldRaiseExceptionOnInvalidMessage(string $message, string $expectedMessage): void\n    {\n        $converter = new MultibyteStringConverter();\n        $message   = hex2bin($message);\n        self::assertIsString($message);\n\n        $this->expectException(ConversionFailed::class);\n        $this->expectExceptionMessage($expectedMessage);\n        $converter->fromAsn1($message, 64);\n    }\n\n    /** @return string[][] */\n    public static function invalidAsn1Structures(): iterable\n    {\n        yield 'Not a sequence' => ['', 'Should start with a sequence'];\n        yield 'Sequence without length'  => ['30', 'Should contain an integer'];\n        yield 'Only one string element'  => ['3006030204f0', 'Should contain an integer'];\n        yield 'Only one integer element' => ['3004020101', 'Should contain an integer'];\n        yield 'Integer+string elements'  => ['300a020101030204f0', 'Should contain an integer'];\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Ecdsa/Sha256Test.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Ecdsa;\n\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha256;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\nuse const OPENSSL_ALGO_SHA256;\n\n#[PHPUnit\\CoversClass(Ecdsa::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\MultibyteStringConverter::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\Sha256::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(Key\\InMemory::class)]\nfinal class Sha256Test extends EcdsaTestCase\n{\n    protected function algorithm(): Ecdsa\n    {\n        return new Sha256($this->pointsManipulator);\n    }\n\n    protected function algorithmId(): string\n    {\n        return 'ES256';\n    }\n\n    protected function signatureAlgorithm(): int\n    {\n        return OPENSSL_ALGO_SHA256;\n    }\n\n    protected function pointLength(): int\n    {\n        return 64;\n    }\n\n    protected function keyLength(): int\n    {\n        return 256;\n    }\n\n    protected function verificationKey(): Key\n    {\n        return self::$ecdsaKeys['public1'];\n    }\n\n    protected function signingKey(): Key\n    {\n        return self::$ecdsaKeys['private'];\n    }\n\n    /** {@inheritDoc} */\n    public static function incompatibleKeys(): iterable\n    {\n        yield '384 bits' => ['private_ec384', 384];\n        yield '521 bits' => ['private_ec512', 521];\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Ecdsa/Sha384Test.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Ecdsa;\n\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha384;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\nuse const OPENSSL_ALGO_SHA384;\n\n#[PHPUnit\\CoversClass(Ecdsa::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\MultibyteStringConverter::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\Sha384::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(Key\\InMemory::class)]\nfinal class Sha384Test extends EcdsaTestCase\n{\n    protected function algorithm(): Ecdsa\n    {\n        return new Sha384($this->pointsManipulator);\n    }\n\n    protected function algorithmId(): string\n    {\n        return 'ES384';\n    }\n\n    protected function signatureAlgorithm(): int\n    {\n        return OPENSSL_ALGO_SHA384;\n    }\n\n    protected function pointLength(): int\n    {\n        return 96;\n    }\n\n    protected function keyLength(): int\n    {\n        return 384;\n    }\n\n    protected function verificationKey(): Key\n    {\n        return self::$ecdsaKeys['public_ec384'];\n    }\n\n    protected function signingKey(): Key\n    {\n        return self::$ecdsaKeys['private_ec384'];\n    }\n\n    /** {@inheritDoc} */\n    public static function incompatibleKeys(): iterable\n    {\n        yield '256 bits' => ['private', 256];\n        yield '521 bits' => ['private_ec512', 521];\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Ecdsa/Sha512Test.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Ecdsa;\n\nuse Lcobucci\\JWT\\Signer\\Ecdsa;\nuse Lcobucci\\JWT\\Signer\\Ecdsa\\Sha512;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\nuse const OPENSSL_ALGO_SHA512;\n\n#[PHPUnit\\CoversClass(Ecdsa::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\MultibyteStringConverter::class)]\n#[PHPUnit\\CoversClass(Ecdsa\\Sha512::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(Key\\InMemory::class)]\nfinal class Sha512Test extends EcdsaTestCase\n{\n    protected function algorithm(): Ecdsa\n    {\n        return new Sha512($this->pointsManipulator);\n    }\n\n    protected function algorithmId(): string\n    {\n        return 'ES512';\n    }\n\n    protected function signatureAlgorithm(): int\n    {\n        return OPENSSL_ALGO_SHA512;\n    }\n\n    protected function pointLength(): int\n    {\n        return 132;\n    }\n\n    protected function keyLength(): int\n    {\n        return 521;\n    }\n\n    protected function verificationKey(): Key\n    {\n        return self::$ecdsaKeys['public_ec512'];\n    }\n\n    protected function signingKey(): Key\n    {\n        return self::$ecdsaKeys['private_ec512'];\n    }\n\n    /** {@inheritDoc} */\n    public static function incompatibleKeys(): iterable\n    {\n        yield '256 bits' => ['private', 256];\n        yield '384 bits' => ['private_ec384', 384];\n    }\n}\n"
  },
  {
    "path": "tests/Signer/EddsaTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer;\n\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Signer\\Eddsa;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Tests\\Keys;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function sodium_crypto_sign_detached;\nuse function sodium_crypto_sign_verify_detached;\n\n#[PHPUnit\\CoversClass(Eddsa::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\n#[PHPUnit\\UsesClass(JoseEncoder::class)]\n#[PHPUnit\\UsesClass(SodiumBase64Polyfill::class)]\nfinal class EddsaTest extends TestCase\n{\n    use Keys;\n\n    #[PHPUnit\\Test]\n    public function algorithmIdMustBeCorrect(): void\n    {\n        self::assertSame('EdDSA', (new Eddsa())->algorithmId());\n    }\n\n    #[PHPUnit\\Test]\n    public function signShouldReturnAValidEddsaSignature(): void\n    {\n        $payload = 'testing';\n\n        $signer    = new Eddsa();\n        $signature = $signer->sign($payload, self::$eddsaKeys['private']);\n\n        $publicKey = self::$eddsaKeys['public1']->contents();\n\n        self::assertTrue(sodium_crypto_sign_verify_detached($signature, $payload, $publicKey));\n    }\n\n    #[PHPUnit\\Test]\n    public function signShouldRaiseAnExceptionWhenKeyIsInvalid(): void\n    {\n        $signer = new Eddsa();\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionCode(0);\n        $this->expectExceptionMessage('SODIUM_CRYPTO_SIGN_SECRETKEYBYTES');\n\n        $signer->sign('testing', InMemory::plainText('tooshort'));\n    }\n\n    #[PHPUnit\\Test]\n    public function verifyShouldReturnTrueWhenSignatureIsValid(): void\n    {\n        $payload   = 'testing';\n        $signature = sodium_crypto_sign_detached($payload, self::$eddsaKeys['private']->contents());\n        $signer    = new Eddsa();\n\n        self::assertTrue($signer->verify($signature, $payload, self::$eddsaKeys['public1']));\n    }\n\n    #[PHPUnit\\Test]\n    public function verifyShouldRaiseAnExceptionWhenKeyIsNotParseable(): void\n    {\n        $signer = new Eddsa();\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionCode(0);\n        $this->expectExceptionMessage('SODIUM_CRYPTO_SIGN_BYTES');\n\n        $signer->verify('testing', 'testing', InMemory::plainText('blablabla'));\n    }\n\n    /** @see https://tools.ietf.org/html/rfc8037#appendix-A.4 */\n    #[PHPUnit\\Test]\n    public function signatureOfRfcExample(): void\n    {\n        $signer  = new Eddsa();\n        $encoder = new JoseEncoder();\n\n        $decoded   = $encoder->base64UrlDecode('nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A');\n        $key       = InMemory::plainText(\n            $decoded\n            . $encoder->base64UrlDecode('11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo'),\n        );\n        $payload   = $encoder->base64UrlEncode('{\"alg\":\"EdDSA\"}')\n            . '.'\n            . $encoder->base64UrlEncode('Example of Ed25519 signing');\n        $signature = $signer->sign($payload, $key);\n\n        self::assertSame('eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc', $payload);\n        self::assertSame(\n            'hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg',\n            $encoder->base64UrlEncode($signature),\n        );\n    }\n\n    /** @see https://tools.ietf.org/html/rfc8037#appendix-A.5 */\n    #[PHPUnit\\Test]\n    public function verificationOfRfcExample(): void\n    {\n        $signer  = new Eddsa();\n        $encoder = new JoseEncoder();\n\n        $decoded = $encoder->base64UrlDecode('11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo');\n\n        $key       = InMemory::plainText($decoded);\n        $payload   = 'eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc';\n        $signature = $encoder->base64UrlDecode(\n            'hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg',\n        );\n\n        self::assertTrue($signer->verify($signature, $payload, $key));\n    }\n}\n"
  },
  {
    "path": "tests/Signer/FakeSigner.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer;\n\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key;\n\nfinal class FakeSigner implements Signer\n{\n    /** @param non-empty-string $signature */\n    public function __construct(private readonly string $signature)\n    {\n    }\n\n    public function algorithmId(): string\n    {\n        return 'FAKE-' . $this->signature;\n    }\n\n    public function sign(string $payload, Key $key): string\n    {\n        return $this->signature . '-' . $key->contents();\n    }\n\n    public function verify(string $expected, string $payload, Key $key): bool\n    {\n        return $this->signature . '-' . $key->contents() === $expected;\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Hmac/HmacTestCase.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Hmac;\n\nuse Lcobucci\\JWT\\Signer\\Hmac;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\nuse function hash_equals;\nuse function hash_hmac;\nuse function is_int;\nuse function random_bytes;\nuse function sprintf;\nuse function strlen;\n\nabstract class HmacTestCase extends TestCase\n{\n    abstract protected function algorithm(): Hmac;\n\n    abstract protected function hashAlgorithm(): string;\n\n    abstract protected function expectedAlgorithmId(): string;\n\n    abstract protected function expectedMinimumBits(): int;\n\n    #[PHPUnit\\Test]\n    public function algorithmIdMustBeCorrect(): void\n    {\n        self::assertEquals($this->expectedAlgorithmId(), $this->algorithm()->algorithmId());\n    }\n\n    #[PHPUnit\\Test]\n    public function signMustReturnAHashAccordingWithTheAlgorithm(): void\n    {\n        $secret = $this->generateSecret();\n\n        $expectedHash = hash_hmac($this->hashAlgorithm(), 'test', $secret, true);\n        $signature    = $this->algorithm()->sign('test', InMemory::plainText($secret));\n\n        self::assertTrue(hash_equals($expectedHash, $signature));\n    }\n\n    #[PHPUnit\\Test]\n    public function verifyMustReturnTrueWhenContentWasSignedWithTheSameKey(): void\n    {\n        $secret = $this->generateSecret();\n\n        $signature = hash_hmac($this->hashAlgorithm(), 'test', $secret, true);\n\n        self::assertTrue($this->algorithm()->verify($signature, 'test', InMemory::plainText($secret)));\n    }\n\n    #[PHPUnit\\Test]\n    public function verifyMustReturnTrueWhenContentWasSignedWithADifferentKey(): void\n    {\n        $signature = hash_hmac(\n            $this->hashAlgorithm(),\n            'test',\n            $this->generateSecret(),\n            true,\n        );\n\n        self::assertFalse(\n            $this->algorithm()->verify(\n                $signature,\n                'test',\n                InMemory::plainText($this->generateSecret()),\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function keyMustFulfillMinimumLengthRequirement(): void\n    {\n        $secret = $this->generateSecret(($this->expectedMinimumBits() / 8) - 1);\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage(\n            sprintf(\n                'Key provided is shorter than %d bits, only %d bits provided',\n                $this->expectedMinimumBits(),\n                strlen($secret) * 8,\n            ),\n        );\n\n        $this->algorithm()->sign('test', InMemory::plainText($secret));\n    }\n\n    /** @return non-empty-string */\n    private function generateSecret(?int $length = null): string\n    {\n        $length ??= $this->expectedMinimumBits() / 8;\n        assert(is_int($length));\n        assert($length > 1);\n\n        return random_bytes($length);\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Hmac/Sha256Test.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Hmac;\n\nuse Lcobucci\\JWT\\Signer\\Hmac;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha256;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(Hmac::class)]\n#[PHPUnit\\CoversClass(Sha256::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\nfinal class Sha256Test extends HmacTestCase\n{\n    protected function algorithm(): Hmac\n    {\n        return new Sha256();\n    }\n\n    protected function expectedAlgorithmId(): string\n    {\n        return 'HS256';\n    }\n\n    protected function expectedMinimumBits(): int\n    {\n        return 256;\n    }\n\n    protected function hashAlgorithm(): string\n    {\n        return 'sha256';\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Hmac/Sha384Test.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Hmac;\n\nuse Lcobucci\\JWT\\Signer\\Hmac;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha384;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(Hmac::class)]\n#[PHPUnit\\CoversClass(Sha384::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\nfinal class Sha384Test extends HmacTestCase\n{\n    protected function algorithm(): Hmac\n    {\n        return new Sha384();\n    }\n\n    protected function expectedAlgorithmId(): string\n    {\n        return 'HS384';\n    }\n\n    protected function expectedMinimumBits(): int\n    {\n        return 384;\n    }\n\n    protected function hashAlgorithm(): string\n    {\n        return 'sha384';\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Hmac/Sha512Test.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Hmac;\n\nuse Lcobucci\\JWT\\Signer\\Hmac;\nuse Lcobucci\\JWT\\Signer\\Hmac\\Sha512;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(Hmac::class)]\n#[PHPUnit\\CoversClass(Sha512::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\nfinal class Sha512Test extends HmacTestCase\n{\n    protected function algorithm(): Hmac\n    {\n        return new Sha512();\n    }\n\n    protected function expectedAlgorithmId(): string\n    {\n        return 'HS512';\n    }\n\n    protected function expectedMinimumBits(): int\n    {\n        return 512;\n    }\n\n    protected function hashAlgorithm(): string\n    {\n        return 'sha512';\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Key/InMemoryTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Key;\n\nuse Lcobucci\\JWT\\Encoding\\CannotDecodeContent;\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\FileCouldNotBeRead;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function base64_encode;\n\n#[PHPUnit\\CoversClass(CannotDecodeContent::class)]\n#[PHPUnit\\CoversClass(FileCouldNotBeRead::class)]\n#[PHPUnit\\CoversClass(InMemory::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(SodiumBase64Polyfill::class)]\nfinal class InMemoryTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    public function exceptionShouldBeRaisedWhenInvalidBase64CharsAreUsed(): void\n    {\n        $this->expectException(CannotDecodeContent::class);\n        $this->expectExceptionMessage('Error while decoding from Base64Url, invalid base64 characters detected');\n\n        InMemory::base64Encoded('ááá');\n    }\n\n    #[PHPUnit\\Test]\n    public function base64EncodedShouldDecodeKeyContents(): void\n    {\n        $key = InMemory::base64Encoded(base64_encode('testing'));\n\n        self::assertSame('testing', $key->contents());\n    }\n\n    #[PHPUnit\\Test]\n    public function exceptionShouldBeRaisedWhenFileDoesNotExists(): void\n    {\n        $path = __DIR__ . '/not-found.pem';\n\n        $this->expectException(FileCouldNotBeRead::class);\n        $this->expectExceptionMessage('The path \"' . $path . '\" does not contain a valid key file');\n        $this->expectExceptionCode(0);\n\n        InMemory::file($path);\n    }\n\n    #[PHPUnit\\Test]\n    public function exceptionShouldBeRaisedWhenFileIsEmpty(): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('Key cannot be empty');\n\n        InMemory::file(__DIR__ . '/empty.pem');\n    }\n\n    #[PHPUnit\\Test]\n    public function contentsShouldReturnConfiguredData(): void\n    {\n        $key = InMemory::plainText('testing', 'test');\n\n        self::assertSame('testing', $key->contents());\n    }\n\n    #[PHPUnit\\Test]\n    public function contentsShouldReturnFileContentsWhenFilePathHasBeenPassed(): void\n    {\n        $key = InMemory::file(__DIR__ . '/test.pem');\n\n        self::assertSame('testing', $key->contents());\n    }\n\n    #[PHPUnit\\Test]\n    public function passphraseShouldReturnConfiguredData(): void\n    {\n        $key = InMemory::plainText('testing', 'test');\n\n        self::assertSame('test', $key->passphrase());\n    }\n\n    #[PHPUnit\\Test]\n    public function emptyPlainTextContentShouldRaiseException(): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n\n        // @phpstan-ignore-next-line\n        InMemory::plainText('');\n    }\n\n    #[PHPUnit\\Test]\n    public function emptyBase64ContentShouldRaiseException(): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n\n        // @phpstan-ignore-next-line\n        InMemory::base64Encoded('');\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Key/empty.pem",
    "content": ""
  },
  {
    "path": "tests/Signer/Key/test.pem",
    "content": "testing"
  },
  {
    "path": "tests/Signer/Rsa/KeyValidationSigner.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Rsa;\n\nuse Lcobucci\\JWT\\Signer\\Key;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\n\nuse const OPENSSL_ALGO_SHA256;\n\nfinal readonly class KeyValidationSigner extends OpenSSL\n{\n    // phpcs:ignore SlevomatCodingStandard.Functions.UnusedParameter.UnusedParameter\n    protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void\n    {\n    }\n\n    public function algorithm(): int\n    {\n        return OPENSSL_ALGO_SHA256;\n    }\n\n    public function algorithmId(): string\n    {\n        return 'RS256';\n    }\n\n    public function sign(string $payload, Key $key): string\n    {\n        return $this->createSignature($key, $payload);\n    }\n\n    public function verify(string $expected, string $payload, Key $key): bool\n    {\n        return $this->verifySignature($expected, $payload, $key);\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Rsa/KeyValidationTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Rsa;\n\nuse Lcobucci\\JWT\\Signer\\CannotSignPayload;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function openssl_error_string;\n\nuse const PHP_EOL;\n\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(CannotSignPayload::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\nfinal class KeyValidationTest extends TestCase\n{\n    #[PHPUnit\\After]\n    public function clearOpenSSLErrors(): void\n    {\n        // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedWhile\n        while (openssl_error_string()) {\n        }\n    }\n\n    #[PHPUnit\\Test]\n    public function signShouldRaiseAnExceptionWhenKeyIsInvalid(): void\n    {\n        $key = <<<'KEY'\n-----BEGIN RSA PRIVATE KEY-----\nMGECAQACEQC4MRKSVsq5XnRBrJoX6+rnAgMBAAECECO8SZkgw6Yg66A6SUly/3kC\nCQDtPXZtCQWJuwIJAMbBu17GDOrFAggopfhNlFcjkwIIVjb7G+U0/TECCEERyvxP\nTWdN\n-----END RSA PRIVATE KEY-----\nKEY;\n\n        $this->expectException(CannotSignPayload::class);\n        $this->expectExceptionMessage('There was an error while creating the signature:' . PHP_EOL . '* error:');\n\n        $void = $this->algorithm()->sign('testing', InMemory::plainText($key));\n    }\n\n    private function algorithm(): OpenSSL\n    {\n        return new KeyValidationSigner();\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Rsa/RsaTestCase.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Rsa;\n\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\Rsa;\nuse Lcobucci\\JWT\\Tests\\Keys;\nuse OpenSSLAsymmetricKey;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\nuse function openssl_error_string;\nuse function openssl_pkey_get_private;\nuse function openssl_pkey_get_public;\nuse function openssl_sign;\nuse function openssl_verify;\n\nuse const PHP_EOL;\n\nabstract class RsaTestCase extends TestCase\n{\n    use Keys;\n\n    abstract protected function algorithm(): Rsa;\n\n    abstract protected function algorithmId(): string;\n\n    abstract protected function signatureAlgorithm(): int;\n\n    #[PHPUnit\\After]\n    final public function clearOpenSSLErrors(): void\n    {\n        // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedWhile\n        while (openssl_error_string()) {\n        }\n    }\n\n    #[PHPUnit\\Test]\n    final public function algorithmIdMustBeCorrect(): void\n    {\n        self::assertSame($this->algorithmId(), $this->algorithm()->algorithmId());\n    }\n\n    #[PHPUnit\\Test]\n    final public function signatureAlgorithmMustBeCorrect(): void\n    {\n        self::assertSame($this->signatureAlgorithm(), $this->algorithm()->algorithm());\n    }\n\n    #[PHPUnit\\Test]\n    public function signShouldReturnAValidOpensslSignature(): void\n    {\n        $payload   = 'testing';\n        $signature = $this->algorithm()->sign($payload, self::$rsaKeys['private']);\n\n        $publicKey = openssl_pkey_get_public(self::$rsaKeys['public']->contents());\n        assert($publicKey instanceof OpenSSLAsymmetricKey);\n\n        self::assertSame(\n            1,\n            openssl_verify($payload, $signature, $publicKey, $this->signatureAlgorithm()),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function signShouldRaiseAnExceptionWhenKeyIsNotParseable(): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('It was not possible to parse your key, reason:' . PHP_EOL . '* error:');\n\n        $this->algorithm()->sign('testing', InMemory::plainText('blablabla'));\n    }\n\n    #[PHPUnit\\Test]\n    public function allOpenSSLErrorsShouldBeOnTheErrorMessage(): void\n    {\n        // Injects a random OpenSSL error message\n        openssl_pkey_get_private('blahblah');\n\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessageMatches('/^.* reason:(' . PHP_EOL . '\\* error:.*){2,}/');\n\n        $this->algorithm()->sign('testing', InMemory::plainText('blablabla'));\n    }\n\n    #[PHPUnit\\Test]\n    public function signShouldRaiseAnExceptionWhenKeyTypeIsNotRsa(): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('The type of the provided key is not \"RSA\", \"EC\" provided');\n\n        $this->algorithm()->sign('testing', self::$ecdsaKeys['private']);\n    }\n\n    #[PHPUnit\\Test]\n    public function signShouldRaiseAnExceptionWhenKeyLengthIsBelowMinimum(): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('Key provided is shorter than 2048 bits, only 512 bits provided');\n\n        $this->algorithm()->sign('testing', self::$rsaKeys['private_short']);\n    }\n\n    #[PHPUnit\\Test]\n    public function verifyShouldReturnTrueWhenSignatureIsValid(): void\n    {\n        $payload    = 'testing';\n        $privateKey = openssl_pkey_get_private(self::$rsaKeys['private']->contents());\n        assert($privateKey instanceof OpenSSLAsymmetricKey);\n\n        $signature = '';\n        openssl_sign($payload, $signature, $privateKey, $this->signatureAlgorithm());\n\n        self::assertTrue($this->algorithm()->verify($signature, $payload, self::$rsaKeys['public']));\n    }\n\n    #[PHPUnit\\Test]\n    public function verifyShouldRaiseAnExceptionWhenKeyIsNotParseable(): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('It was not possible to parse your key, reason:' . PHP_EOL . '* error:');\n\n        $this->algorithm()->verify('testing', 'testing', InMemory::plainText('blablabla'));\n    }\n\n    #[PHPUnit\\Test]\n    public function verifyShouldRaiseAnExceptionWhenKeyTypeIsNotRsa(): void\n    {\n        $this->expectException(InvalidKeyProvided::class);\n        $this->expectExceptionMessage('It was not possible to parse your key');\n\n        $this->algorithm()->verify('testing', 'testing', self::$ecdsaKeys['private']);\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Rsa/Sha256Test.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Rsa;\n\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse Lcobucci\\JWT\\Signer\\Rsa;\nuse Lcobucci\\JWT\\Signer\\Rsa\\Sha256;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\nuse const OPENSSL_ALGO_SHA256;\n\n#[PHPUnit\\CoversClass(Sha256::class)]\n#[PHPUnit\\CoversClass(Rsa::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\nfinal class Sha256Test extends RsaTestCase\n{\n    protected function algorithm(): Rsa\n    {\n        return new Sha256();\n    }\n\n    protected function algorithmId(): string\n    {\n        return 'RS256';\n    }\n\n    protected function signatureAlgorithm(): int\n    {\n        return OPENSSL_ALGO_SHA256;\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Rsa/Sha384Test.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Rsa;\n\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse Lcobucci\\JWT\\Signer\\Rsa;\nuse Lcobucci\\JWT\\Signer\\Rsa\\Sha384;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\nuse const OPENSSL_ALGO_SHA384;\n\n#[PHPUnit\\CoversClass(Sha384::class)]\n#[PHPUnit\\CoversClass(Rsa::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\nfinal class Sha384Test extends RsaTestCase\n{\n    protected function algorithm(): Rsa\n    {\n        return new Sha384();\n    }\n\n    protected function algorithmId(): string\n    {\n        return 'RS384';\n    }\n\n    protected function signatureAlgorithm(): int\n    {\n        return OPENSSL_ALGO_SHA384;\n    }\n}\n"
  },
  {
    "path": "tests/Signer/Rsa/Sha512Test.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Signer\\Rsa;\n\nuse Lcobucci\\JWT\\Signer\\InvalidKeyProvided;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Signer\\OpenSSL;\nuse Lcobucci\\JWT\\Signer\\Rsa;\nuse Lcobucci\\JWT\\Signer\\Rsa\\Sha512;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\nuse const OPENSSL_ALGO_SHA512;\n\n#[PHPUnit\\CoversClass(Rsa::class)]\n#[PHPUnit\\CoversClass(Sha512::class)]\n#[PHPUnit\\CoversClass(OpenSSL::class)]\n#[PHPUnit\\CoversClass(InvalidKeyProvided::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\nfinal class Sha512Test extends RsaTestCase\n{\n    protected function algorithm(): Rsa\n    {\n        return new Sha512();\n    }\n\n    protected function algorithmId(): string\n    {\n        return 'RS512';\n    }\n\n    protected function signatureAlgorithm(): int\n    {\n        return OPENSSL_ALGO_SHA512;\n    }\n}\n"
  },
  {
    "path": "tests/SodiumBase64PolyfillTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse Lcobucci\\JWT\\Encoding\\CannotDecodeContent;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function rtrim;\nuse function sodium_base642bin;\n\nuse const SODIUM_BASE64_VARIANT_ORIGINAL;\nuse const SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING;\nuse const SODIUM_BASE64_VARIANT_URLSAFE;\nuse const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING;\n\n#[PHPUnit\\CoversClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\UsesClass(CannotDecodeContent::class)]\nfinal class SodiumBase64PolyfillTest extends TestCase\n{\n    private const string B64    = 'I+o2tVq8ynY=';\n    private const string B64URL = 'lZ-2HIl9dTz_Oy0nAb-2gvKdG0jhHJ36XB2rWAKj8Uo=';\n\n    #[PHPUnit\\Test]\n    public function constantsMatchExtensionOnes(): void\n    {\n        // @phpstan-ignore-next-line\n        self::assertSame(\n            SODIUM_BASE64_VARIANT_ORIGINAL,\n            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL,\n        );\n        // @phpstan-ignore-next-line\n        self::assertSame(\n            SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING,\n            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING,\n        );\n        // @phpstan-ignore-next-line\n        self::assertSame(\n            SODIUM_BASE64_VARIANT_URLSAFE,\n            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE,\n        );\n        // @phpstan-ignore-next-line\n        self::assertSame(\n            SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,\n            SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING,\n        );\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('base64Variants')]\n    public function bin2base64(string $encoded, string $binary, int $variant): void\n    {\n        self::assertSame($encoded, SodiumBase64Polyfill::bin2base64($binary, $variant));\n        self::assertSame($encoded, SodiumBase64Polyfill::bin2base64Fallback($binary, $variant));\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('base64Variants')]\n    public function base642binFallback(string $encoded, string $binary, int $variant): void\n    {\n        self::assertSame($binary, SodiumBase64Polyfill::base642bin($encoded, $variant));\n        self::assertSame($binary, SodiumBase64Polyfill::base642binFallback($encoded, $variant));\n    }\n\n    /** @return iterable<array{string, string, int}> */\n    public static function base64Variants(): iterable\n    {\n        $binary = sodium_base642bin(self::B64, SODIUM_BASE64_VARIANT_ORIGINAL, '');\n\n        yield [self::B64, $binary, SODIUM_BASE64_VARIANT_ORIGINAL];\n        yield [rtrim(self::B64, '='), $binary, SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING];\n\n        $urlBinary = sodium_base642bin(self::B64URL, SODIUM_BASE64_VARIANT_URLSAFE, '');\n\n        yield [self::B64URL, $urlBinary, SODIUM_BASE64_VARIANT_URLSAFE];\n        yield [rtrim(self::B64URL, '='), $urlBinary, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING];\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('invalidBase64')]\n    public function sodiumBase642BinRaisesExceptionOnInvalidBase64(string $content, int $variant): void\n    {\n        $this->expectException(CannotDecodeContent::class);\n\n        SodiumBase64Polyfill::base642bin($content, $variant);\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('invalidBase64')]\n    public function fallbackBase642BinRaisesExceptionOnInvalidBase64(string $content, int $variant): void\n    {\n        $this->expectException(CannotDecodeContent::class);\n\n        SodiumBase64Polyfill::base642binFallback($content, $variant);\n    }\n\n    /** @return iterable<string, array{string, int}> */\n    public static function invalidBase64(): iterable\n    {\n        yield 'UTF-8 content' => ['ááá', SODIUM_BASE64_VARIANT_ORIGINAL];\n\n        yield 'b64Url variant against original (padded)' => [\n            self::B64URL,\n            SODIUM_BASE64_VARIANT_ORIGINAL,\n        ];\n\n        yield 'b64Url variant against original (not padded)' => [\n            rtrim(self::B64URL, '='),\n            SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING,\n        ];\n    }\n}\n"
  },
  {
    "path": "tests/TimeFractionPrecisionTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Encoding;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Token\\Plain;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(Configuration::class)]\n#[PHPUnit\\CoversClass(Encoding\\JoseEncoder::class)]\n#[PHPUnit\\CoversClass(Encoding\\ChainedFormatter::class)]\n#[PHPUnit\\CoversClass(Encoding\\MicrosecondBasedDateConversion::class)]\n#[PHPUnit\\CoversClass(Encoding\\UnifyAudience::class)]\n#[PHPUnit\\CoversClass(Token\\Builder::class)]\n#[PHPUnit\\CoversClass(Token\\Parser::class)]\n#[PHPUnit\\CoversClass(Token\\Plain::class)]\n#[PHPUnit\\CoversClass(Token\\DataSet::class)]\n#[PHPUnit\\CoversClass(Token\\Signature::class)]\n#[PHPUnit\\CoversClass(InMemory::class)]\n#[PHPUnit\\CoversClass(SodiumBase64Polyfill::class)]\nfinal class TimeFractionPrecisionTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('datesWithPotentialRoundingIssues')]\n    public function timeFractionsPrecisionsAreRespected(string $timeFraction): void\n    {\n        $config = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n\n        $issuedAt = DateTimeImmutable::createFromFormat('U.u', $timeFraction);\n\n        self::assertInstanceOf(DateTimeImmutable::class, $issuedAt);\n        self::assertSame($timeFraction, $issuedAt->format('U.u'));\n\n        $token = $config->builder()\n            ->issuedAt($issuedAt)\n            ->getToken($config->signer(), $config->signingKey());\n\n        $parsedToken = $config->parser()->parse($token->toString());\n\n        self::assertInstanceOf(Plain::class, $parsedToken);\n        self::assertSame($timeFraction, $parsedToken->claims()->get('iat')->format('U.u'));\n    }\n\n    /** @return iterable<string[]> */\n    public static function datesWithPotentialRoundingIssues(): iterable\n    {\n        yield ['1613938511.017448'];\n        yield ['1613938511.023691'];\n        yield ['1613938511.018045'];\n        yield ['1616074725.008455'];\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('timeFractionConversions')]\n    public function typeConversionDoesNotCauseParsingErrors(float|int|string $issuedAt, string $timeFraction): void\n    {\n        $encoder = new Encoding\\JoseEncoder();\n        $headers = $encoder->base64UrlEncode($encoder->jsonEncode(['typ' => 'JWT', 'alg' => 'none']));\n        $claims  = $encoder->base64UrlEncode($encoder->jsonEncode(['iat' => $issuedAt]));\n\n        $config      = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n        $parsedToken = $config->parser()->parse($headers . '.' . $claims . '.cHJpdmF0ZQ');\n\n        self::assertInstanceOf(Token\\Plain::class, $parsedToken);\n        self::assertSame($timeFraction, $parsedToken->claims()->get('iat')->format('U.u'));\n    }\n\n    /** @return iterable<array{0: float|int|string, 1: string}> */\n    public static function timeFractionConversions(): iterable\n    {\n        yield [1616481863.528781890869140625, '1616481863.528782'];\n        yield [1616497608.0510409, '1616497608.051041'];\n        yield [1616536852.1000001, '1616536852.100000'];\n        yield [1616457346.3878131, '1616457346.387813'];\n        yield [1616457346.0, '1616457346.000000'];\n\n        yield [1616457346, '1616457346.000000'];\n\n        yield ['1616481863.528781890869140625', '1616481863.528782'];\n        yield ['1616497608.0510409', '1616497608.051041'];\n        yield ['1616536852.1000001', '1616536852.100000'];\n        yield ['1616457346.3878131', '1616457346.387813'];\n        yield ['1616457346.0', '1616457346.000000'];\n        yield ['1616457346', '1616457346.000000'];\n    }\n}\n"
  },
  {
    "path": "tests/Token/BuilderTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Token;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Encoder;\nuse Lcobucci\\JWT\\Encoding\\MicrosecondBasedDateConversion;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\Token\\Builder;\nuse Lcobucci\\JWT\\Token\\DataSet;\nuse Lcobucci\\JWT\\Token\\Plain;\nuse Lcobucci\\JWT\\Token\\RegisteredClaimGiven;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse Lcobucci\\JWT\\Token\\Signature;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse SplObjectStorage;\n\n#[PHPUnit\\CoversClass(Builder::class)]\n#[PHPUnit\\CoversClass(RegisteredClaimGiven::class)]\n#[PHPUnit\\UsesClass(MicrosecondBasedDateConversion::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\n#[PHPUnit\\UsesClass(Plain::class)]\n#[PHPUnit\\UsesClass(Signature::class)]\n#[PHPUnit\\UsesClass(DataSet::class)]\nfinal class BuilderTest extends TestCase\n{\n    private Encoder&MockObject $encoder;\n    private Signer&MockObject $signer;\n\n    #[PHPUnit\\Before]\n    public function initializeDependencies(): void\n    {\n        $this->encoder = $this->createMock(Encoder::class);\n        $this->signer  = $this->createMock(Signer::class);\n        $this->signer->method('algorithmId')->willReturn('RS256');\n    }\n\n    #[PHPUnit\\Test]\n    public function withClaimShouldRaiseExceptionWhenTryingToConfigureARegisteredClaim(): void\n    {\n        $this->encoder->expects($this->never())->method(self::anything());\n        $this->signer->expects($this->never())->method(self::anything());\n\n        $builder = Builder::new($this->encoder, new MicrosecondBasedDateConversion());\n\n        $this->expectException(RegisteredClaimGiven::class);\n        $this->expectExceptionMessage(\n            'Builder#withClaim() is meant to be used for non-registered claims, '\n            . 'check the documentation on how to set claim \"iss\"',\n        );\n\n        $builder->withClaim(RegisteredClaims::ISSUER, 'me');\n    }\n\n    #[PHPUnit\\Test]\n    public function getTokenShouldReturnACompletelyConfigureToken(): void\n    {\n        $issuedAt   = new DateTimeImmutable('@1487285080');\n        $notBefore  = DateTimeImmutable::createFromFormat('U.u', '1487285080.000123');\n        $expiration = DateTimeImmutable::createFromFormat('U.u', '1487285080.123456');\n\n        self::assertInstanceOf(DateTimeImmutable::class, $notBefore);\n        self::assertInstanceOf(DateTimeImmutable::class, $expiration);\n\n        $this->encoder->expects(self::exactly(2))\n                     ->method('jsonEncode')\n                      ->willReturnOnConsecutiveCalls('1', '2');\n\n        $this->encoder->expects(self::exactly(3))\n                      ->method('base64UrlEncode')\n                      ->willReturnArgument(0);\n\n        $this->signer->expects($this->once())\n            ->method('sign')\n            ->with('1.2')\n            ->willReturn('3');\n\n        $builder = Builder::new($this->encoder, new MicrosecondBasedDateConversion());\n        $token   = $builder->identifiedBy('123456')\n                           ->issuedBy('https://issuer.com')\n                           ->issuedAt($issuedAt)\n                           ->canOnlyBeUsedAfter($notBefore)\n                           ->expiresAt($expiration)\n                           ->relatedTo('subject')\n                           ->permittedFor('test1')\n                           ->permittedFor('test2')\n                           ->permittedFor('test2') // should not be added since it's duplicated\n                           ->withClaim('test', 123)\n                           ->withHeader('userId', 2)\n                           ->getToken($this->signer, InMemory::plainText('123'));\n\n        self::assertSame('JWT', $token->headers()->get('typ'));\n        self::assertSame('RS256', $token->headers()->get('alg'));\n        self::assertSame(2, $token->headers()->get('userId'));\n        self::assertSame(123, $token->claims()->get('test'));\n        self::assertSame($issuedAt, $token->claims()->get(RegisteredClaims::ISSUED_AT));\n        self::assertSame($notBefore, $token->claims()->get(RegisteredClaims::NOT_BEFORE));\n        self::assertSame($expiration, $token->claims()->get(RegisteredClaims::EXPIRATION_TIME));\n        self::assertSame('123456', $token->claims()->get(RegisteredClaims::ID));\n        self::assertSame('https://issuer.com', $token->claims()->get(RegisteredClaims::ISSUER));\n        self::assertSame('subject', $token->claims()->get(RegisteredClaims::SUBJECT));\n        self::assertSame(['test1', 'test2'], $token->claims()->get(RegisteredClaims::AUDIENCE));\n        self::assertSame('3', $token->signature()->toString());\n    }\n\n    #[PHPUnit\\Test]\n    public function immutability(): void\n    {\n        $this->encoder->expects($this->never())->method(self::anything());\n        $this->signer->expects($this->never())->method(self::anything());\n\n        $map           = new SplObjectStorage();\n        $builder       = Builder::new($this->encoder, new MicrosecondBasedDateConversion());\n        $map[$builder] = true;\n        $builder       = $builder->identifiedBy('123456');\n        $map[$builder] = true;\n        $builder       = $builder->issuedBy('https://issuer.com');\n        $map[$builder] = true;\n        $builder       = $builder->issuedAt(new DateTimeImmutable());\n        $map[$builder] = true;\n        $builder       = $builder->canOnlyBeUsedAfter(new DateTimeImmutable());\n        $map[$builder] = true;\n        $builder       = $builder->expiresAt(new DateTimeImmutable());\n        $map[$builder] = true;\n        $builder       = $builder->relatedTo('subject');\n        $map[$builder] = true;\n        $builder       = $builder->permittedFor('test1');\n        $map[$builder] = true;\n        $builder       = $builder->withClaim('test', 123);\n        $map[$builder] = true;\n        $builder       = $builder->withHeader('userId', 2);\n        $map[$builder] = true;\n\n        self::assertCount(10, $map);\n    }\n}\n"
  },
  {
    "path": "tests/Token/DataSetTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Token;\n\nuse Lcobucci\\JWT\\Token\\DataSet;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(DataSet::class)]\nfinal class DataSetTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    public function getShouldReturnTheConfiguredValue(): void\n    {\n        $set = new DataSet(['one' => 1], 'one=1');\n\n        self::assertSame(1, $set->get('one'));\n    }\n\n    #[PHPUnit\\Test]\n    public function getShouldReturnTheFallbackValueWhenItWasGiven(): void\n    {\n        $set = new DataSet(['one' => 1], 'one=1');\n\n        self::assertSame(2, $set->get('two', 2));\n    }\n\n    #[PHPUnit\\Test]\n    public function getShouldReturnNullWhenFallbackValueWasNotGiven(): void\n    {\n        $set = new DataSet(['one' => 1], 'one=1');\n\n        self::assertNull($set->get('two'));\n    }\n\n    #[PHPUnit\\Test]\n    public function hasShouldReturnTrueWhenItemWasConfigured(): void\n    {\n        $set = new DataSet(['one' => 1], 'one=1');\n\n        self::assertTrue($set->has('one'));\n    }\n\n    #[PHPUnit\\Test]\n    public function hasShouldReturnFalseWhenItemWasNotConfigured(): void\n    {\n        $set = new DataSet(['one' => 1], 'one=1');\n\n        self::assertFalse($set->has('two'));\n    }\n\n    #[PHPUnit\\Test]\n    public function allShouldReturnAllConfiguredItems(): void\n    {\n        $items = ['one' => 1, 'two' => 2];\n        $set   = new DataSet($items, 'one=1');\n\n        self::assertSame($items, $set->all());\n    }\n\n    #[PHPUnit\\Test]\n    public function toStringShouldReturnTheEncodedData(): void\n    {\n        $set = new DataSet(['one' => 1], 'one=1');\n\n        self::assertSame('one=1', $set->toString());\n    }\n}\n"
  },
  {
    "path": "tests/Token/ParserTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Token;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Decoder;\nuse Lcobucci\\JWT\\Token\\DataSet;\nuse Lcobucci\\JWT\\Token\\InvalidTokenStructure;\nuse Lcobucci\\JWT\\Token\\Parser;\nuse Lcobucci\\JWT\\Token\\Plain;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse Lcobucci\\JWT\\Token\\Signature;\nuse Lcobucci\\JWT\\Token\\UnsupportedHeaderFound;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\nuse RuntimeException;\n\n#[PHPUnit\\CoversClass(Parser::class)]\n#[PHPUnit\\CoversClass(InvalidTokenStructure::class)]\n#[PHPUnit\\CoversClass(UnsupportedHeaderFound::class)]\n#[PHPUnit\\UsesClass(Plain::class)]\n#[PHPUnit\\UsesClass(DataSet::class)]\n#[PHPUnit\\UsesClass(Signature::class)]\nfinal class ParserTest extends TestCase\n{\n    protected Decoder&MockObject $decoder;\n\n    #[PHPUnit\\Before]\n    public function createDependencies(): void\n    {\n        $this->decoder = $this->createMock(Decoder::class);\n    }\n\n    private function createParser(): Parser\n    {\n        return new Parser($this->decoder);\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustRaiseExceptionWhenTokenDoesNotHaveThreeParts(): void\n    {\n        $this->decoder->expects($this->never())->method(self::anything());\n\n        $parser = $this->createParser();\n\n        $this->expectException(InvalidTokenStructure::class);\n        $this->expectExceptionMessage('The JWT string must have two dots');\n\n        $parser->parse('.');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustRaiseExceptionWhenTokenDoesNotHaveHeaders(): void\n    {\n        $this->decoder->expects($this->never())->method(self::anything());\n\n        $parser = $this->createParser();\n\n        $this->expectException(InvalidTokenStructure::class);\n        $this->expectExceptionMessage('The JWT string is missing the Header part');\n\n        $parser->parse('.b.c');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustRaiseExceptionWhenTokenDoesNotHaveClaims(): void\n    {\n        $this->decoder->expects($this->never())->method(self::anything());\n\n        $parser = $this->createParser();\n\n        $this->expectException(InvalidTokenStructure::class);\n        $this->expectExceptionMessage('The JWT string is missing the Claim part');\n\n        $parser->parse('a..c');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustRaiseExceptionWhenTokenDoesNotHaveSignature(): void\n    {\n        $this->decoder->expects($this->never())->method(self::anything());\n\n        $parser = $this->createParser();\n\n        $this->expectException(InvalidTokenStructure::class);\n        $this->expectExceptionMessage('The JWT string is missing the Signature part');\n\n        $parser->parse('a.b.');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustRaiseExceptionWhenHeaderCannotBeDecoded(): void\n    {\n        $this->decoder\n            ->expects($this->once())\n            ->method('base64UrlDecode')\n            ->with('a')\n            ->willReturn('b');\n\n        $this->decoder\n            ->expects($this->once())\n            ->method('jsonDecode')\n            ->with('b')\n            ->willThrowException(new RuntimeException('Nope'));\n\n        $parser = $this->createParser();\n\n        $this->expectException(RuntimeException::class);\n        $this->expectExceptionMessage('Nope');\n\n        $parser->parse('a.b.c');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustRaiseExceptionWhenDealingWithNonArrayHeaders(): void\n    {\n        $this->decoder->expects($this->once())\n            ->method('jsonDecode')\n                      ->willReturn('A very invalid header');\n\n        $parser = $this->createParser();\n\n        $this->expectException(InvalidTokenStructure::class);\n        $this->expectExceptionMessage('headers must be an array');\n\n        $parser->parse('a.a.a');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustRaiseExceptionWhenDealingWithHeadersThatHaveEmptyStringKeys(): void\n    {\n        $this->decoder->expects($this->once())\n            ->method('jsonDecode')\n            ->willReturn(['' => 'foo']);\n\n        $parser = $this->createParser();\n\n        $this->expectException(InvalidTokenStructure::class);\n        $this->expectExceptionMessage('headers must be an array');\n\n        $parser->parse('a.a.a');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustRaiseExceptionWhenHeaderIsFromAnEncryptedToken(): void\n    {\n        $this->decoder->expects($this->once())\n            ->method('jsonDecode')\n            ->willReturn(['enc' => 'AAA']);\n\n        $parser = $this->createParser();\n\n        $this->expectException(UnsupportedHeaderFound::class);\n        $this->expectExceptionMessage('Encryption is not supported yet');\n\n        $parser->parse('a.a.a');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustRaiseExceptionWhenDealingWithNonArrayClaims(): void\n    {\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnOnConsecutiveCalls(['typ' => 'JWT'], 'A very invalid claim set');\n\n        $parser = $this->createParser();\n\n        $this->expectException(InvalidTokenStructure::class);\n        $this->expectExceptionMessage('claims must be an array');\n\n        $parser->parse('a.a.a');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustRaiseExceptionWhenDealingWithClaimsThatHaveEmptyStringKeys(): void\n    {\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnOnConsecutiveCalls(['typ' => 'JWT'], ['' => 'foo']);\n\n        $parser = $this->createParser();\n\n        $this->expectException(InvalidTokenStructure::class);\n        $this->expectExceptionMessage('claims must be an array');\n\n        $parser->parse('a.a.a');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustReturnAnUnsecuredTokenWhenSignatureIsNotInformed(): void\n    {\n        $this->decoder->expects($this->exactly(3))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n                ['c', 'c_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['typ' => 'JWT', 'alg' => 'none']],\n                ['b_dec', [RegisteredClaims::AUDIENCE => 'test']],\n            ]);\n\n        $parser = $this->createParser();\n        $token  = $parser->parse('a.b.c');\n\n        self::assertInstanceOf(Plain::class, $token);\n\n        $headers   = new DataSet(['typ' => 'JWT', 'alg' => 'none'], 'a');\n        $claims    = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b');\n        $signature = new Signature('c_dec', 'c');\n\n        self::assertEquals($headers, $token->headers());\n        self::assertEquals($claims, $token->claims());\n        self::assertEquals($signature, $token->signature());\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustConfigureTypeToJWTWhenItIsMissing(): void\n    {\n        $this->decoder->expects($this->exactly(3))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n                ['c', 'c_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['alg' => 'none']],\n                ['b_dec', [RegisteredClaims::AUDIENCE => 'test']],\n            ]);\n\n        $parser = $this->createParser();\n        $token  = $parser->parse('a.b.c');\n\n        self::assertInstanceOf(Plain::class, $token);\n\n        $headers   = new DataSet(['typ' => 'JWT', 'alg' => 'none'], 'a');\n        $claims    = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b');\n        $signature = new Signature('c_dec', 'c');\n\n        self::assertEquals($headers, $token->headers());\n        self::assertEquals($claims, $token->claims());\n        self::assertEquals($signature, $token->signature());\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustNotChangeTypeWhenItIsConfigured(): void\n    {\n        $this->decoder->expects($this->exactly(3))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n                ['c', 'c_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['typ' => 'JWS', 'alg' => 'none']],\n                ['b_dec', [RegisteredClaims::AUDIENCE => 'test']],\n            ]);\n\n        $parser = $this->createParser();\n        $token  = $parser->parse('a.b.c');\n\n        self::assertInstanceOf(Plain::class, $token);\n\n        $headers   = new DataSet(['typ' => 'JWS', 'alg' => 'none'], 'a');\n        $claims    = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b');\n        $signature = new Signature('c_dec', 'c');\n\n        self::assertEquals($headers, $token->headers());\n        self::assertEquals($claims, $token->claims());\n        self::assertEquals($signature, $token->signature());\n    }\n\n    #[PHPUnit\\Test]\n    public function parseShouldReplicateClaimValueOnHeaderWhenNeeded(): void\n    {\n        $this->decoder->expects($this->exactly(3))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n                ['c', 'c_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['typ' => 'JWT', 'alg' => 'none', RegisteredClaims::AUDIENCE => 'test']],\n                ['b_dec', [RegisteredClaims::AUDIENCE => 'test']],\n            ]);\n\n        $parser = $this->createParser();\n        $token  = $parser->parse('a.b.c');\n\n        self::assertInstanceOf(Plain::class, $token);\n\n        $headers   = new DataSet(['typ' => 'JWT', 'alg' => 'none', RegisteredClaims::AUDIENCE => 'test'], 'a');\n        $claims    = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b');\n        $signature = new Signature('c_dec', 'c');\n\n        self::assertEquals($headers, $token->headers());\n        self::assertEquals($claims, $token->claims());\n        self::assertEquals($signature, $token->signature());\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustReturnANonSignedTokenWhenSignatureAlgorithmIsMissing(): void\n    {\n        $this->decoder->expects($this->exactly(3))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n                ['c', 'c_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['typ' => 'JWT']],\n                ['b_dec', [RegisteredClaims::AUDIENCE => 'test']],\n            ]);\n\n        $parser = $this->createParser();\n        $token  = $parser->parse('a.b.c');\n\n        self::assertInstanceOf(Plain::class, $token);\n\n        $headers   = new DataSet(['typ' => 'JWT'], 'a');\n        $claims    = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b');\n        $signature = new Signature('c_dec', 'c');\n\n        self::assertEquals($headers, $token->headers());\n        self::assertEquals($claims, $token->claims());\n        self::assertEquals($signature, $token->signature());\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustReturnANonSignedTokenWhenSignatureAlgorithmIsNone(): void\n    {\n        $this->decoder->expects($this->exactly(3))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n                ['c', 'c_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['typ' => 'JWT', 'alg' => 'none']],\n                ['b_dec', [RegisteredClaims::AUDIENCE => 'test']],\n            ]);\n\n        $parser = $this->createParser();\n        $token  = $parser->parse('a.b.c');\n\n        self::assertInstanceOf(Plain::class, $token);\n\n        $headers   = new DataSet(['typ' => 'JWT', 'alg' => 'none'], 'a');\n        $claims    = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b');\n        $signature = new Signature('c_dec', 'c');\n\n        self::assertEquals($headers, $token->headers());\n        self::assertEquals($claims, $token->claims());\n        self::assertEquals($signature, $token->signature());\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustReturnASignedTokenWhenSignatureIsInformed(): void\n    {\n        $this->decoder->expects($this->exactly(3))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n                ['c', 'c_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['typ' => 'JWT', 'alg' => 'HS256']],\n                ['b_dec', [RegisteredClaims::AUDIENCE => 'test']],\n            ]);\n\n        $parser = $this->createParser();\n        $token  = $parser->parse('a.b.c');\n\n        self::assertInstanceOf(Plain::class, $token);\n\n        $headers   = new DataSet(['typ' => 'JWT', 'alg' => 'HS256'], 'a');\n        $claims    = new DataSet([RegisteredClaims::AUDIENCE => ['test']], 'b');\n        $signature = new Signature('c_dec', 'c');\n\n        self::assertEquals($headers, $token->headers());\n        self::assertEquals($claims, $token->claims());\n        self::assertEquals($signature, $token->signature());\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustConvertDateClaimsToObjects(): void\n    {\n        $data = [\n            RegisteredClaims::ISSUED_AT => 1486930663,\n            RegisteredClaims::EXPIRATION_TIME => 1486930757.023055,\n        ];\n\n        $this->decoder->expects($this->exactly(3))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n                ['c', 'c_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['typ' => 'JWT', 'alg' => 'HS256']],\n                ['b_dec', $data],\n            ]);\n\n        $token = $this->createParser()->parse('a.b.c');\n        self::assertInstanceOf(Plain::class, $token);\n\n        $claims = $token->claims();\n\n        self::assertEquals(\n            DateTimeImmutable::createFromFormat('U', '1486930663'),\n            $claims->get(RegisteredClaims::ISSUED_AT),\n        );\n\n        self::assertEquals(\n            DateTimeImmutable::createFromFormat('U.u', '1486930757.023055'),\n            $claims->get(RegisteredClaims::EXPIRATION_TIME),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function parseMustConvertStringDates(): void\n    {\n        $data = [RegisteredClaims::NOT_BEFORE => '1486930757.000000'];\n\n        $this->decoder->expects($this->exactly(3))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n                ['c', 'c_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['typ' => 'JWT', 'alg' => 'HS256']],\n                ['b_dec', $data],\n            ]);\n\n        $token = $this->createParser()->parse('a.b.c');\n        self::assertInstanceOf(Plain::class, $token);\n\n        $claims = $token->claims();\n\n        self::assertEquals(\n            DateTimeImmutable::createFromFormat('U.u', '1486930757.000000'),\n            $claims->get(RegisteredClaims::NOT_BEFORE),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function parseShouldRaiseExceptionOnInvalidDate(): void\n    {\n        $data = [RegisteredClaims::ISSUED_AT => '14/10/2018 10:50:10.10 UTC'];\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['typ' => 'JWT', 'alg' => 'HS256']],\n                ['b_dec', $data],\n            ]);\n\n        $this->expectException(InvalidTokenStructure::class);\n        $this->expectExceptionMessage('Value is not in the allowed date format: 14/10/2018 10:50:10.10 UTC');\n        $this->createParser()->parse('a.b.c');\n    }\n\n    #[PHPUnit\\Test]\n    public function parseShouldRaiseExceptionOnTimestampBeyondDateTimeImmutableRange(): void\n    {\n        $data = [RegisteredClaims::ISSUED_AT => -10000000000 ** 5];\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('base64UrlDecode')\n            ->willReturnMap([\n                ['a', 'a_dec'],\n                ['b', 'b_dec'],\n            ]);\n\n        $this->decoder->expects($this->exactly(2))\n            ->method('jsonDecode')\n            ->willReturnMap([\n                ['a_dec', ['typ' => 'JWT', 'alg' => 'HS256']],\n                ['b_dec', $data],\n            ]);\n\n        $this->expectException(InvalidTokenStructure::class);\n        $this->createParser()->parse('a.b.c');\n    }\n}\n"
  },
  {
    "path": "tests/Token/PlainTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Token;\n\nuse DateTimeImmutable;\nuse Lcobucci\\JWT\\Token\\DataSet;\nuse Lcobucci\\JWT\\Token\\Plain;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse Lcobucci\\JWT\\Token\\Signature;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(Plain::class)]\n#[PHPUnit\\UsesClass(DataSet::class)]\n#[PHPUnit\\UsesClass(Signature::class)]\nfinal class PlainTest extends TestCase\n{\n    private DataSet $headers;\n    private DataSet $claims;\n    private Signature $signature;\n\n    #[PHPUnit\\Before]\n    public function createDependencies(): void\n    {\n        $this->headers   = new DataSet(['alg' => 'none'], 'headers');\n        $this->claims    = new DataSet([], 'claims');\n        $this->signature = new Signature('hash', 'signature');\n    }\n\n    private function createToken(\n        ?DataSet $headers = null,\n        ?DataSet $claims = null,\n        ?Signature $signature = null,\n    ): Plain {\n        return new Plain(\n            $headers ?? $this->headers,\n            $claims ?? $this->claims,\n            $signature ?? $this->signature,\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function signedShouldCreateATokenWithSignature(): void\n    {\n        $token = $this->createToken();\n\n        self::assertSame($this->headers, $token->headers());\n        self::assertSame($this->claims, $token->claims());\n        self::assertSame($this->signature, $token->signature());\n    }\n\n    #[PHPUnit\\Test]\n    public function payloadShouldReturnAStringWithTheEncodedHeadersAndClaims(): void\n    {\n        $token = $this->createToken();\n\n        self::assertSame('headers.claims', $token->payload());\n    }\n\n    #[PHPUnit\\Test]\n    public function isPermittedForShouldReturnFalseWhenNoAudienceIsConfigured(): void\n    {\n        $token = $this->createToken();\n\n        self::assertFalse($token->isPermittedFor('testing'));\n    }\n\n    #[PHPUnit\\Test]\n    public function isPermittedForShouldReturnFalseWhenAudienceDoesNotMatchAsArray(): void\n    {\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::AUDIENCE => ['test', 'test2']], ''),\n        );\n\n        self::assertFalse($token->isPermittedFor('testing'));\n    }\n\n    #[PHPUnit\\Test]\n    public function isPermittedForShouldReturnFalseWhenAudienceTypeDoesNotMatch(): void\n    {\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::AUDIENCE => [10]], ''),\n        );\n\n        self::assertFalse($token->isPermittedFor('10'));\n    }\n\n    #[PHPUnit\\Test]\n    public function isPermittedForShouldReturnTrueWhenAudienceMatchesAsArray(): void\n    {\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::AUDIENCE => ['testing', 'test']], ''),\n        );\n\n        self::assertTrue($token->isPermittedFor('testing'));\n    }\n\n    #[PHPUnit\\Test]\n    public function isIdentifiedByShouldReturnFalseWhenNoIdWasConfigured(): void\n    {\n        $token = $this->createToken();\n\n        self::assertFalse($token->isIdentifiedBy('test'));\n    }\n\n    #[PHPUnit\\Test]\n    public function isIdentifiedByShouldReturnFalseWhenIdDoesNotMatch(): void\n    {\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::ID => 'testing'], ''),\n        );\n\n        self::assertFalse($token->isIdentifiedBy('test'));\n    }\n\n    #[PHPUnit\\Test]\n    public function isIdentifiedByShouldReturnTrueWhenIdMatches(): void\n    {\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::ID => 'test'], ''),\n        );\n\n        self::assertTrue($token->isIdentifiedBy('test'));\n    }\n\n    #[PHPUnit\\Test]\n    public function isRelatedToShouldReturnFalseWhenNoSubjectWasConfigured(): void\n    {\n        $token = $this->createToken();\n\n        self::assertFalse($token->isRelatedTo('test'));\n    }\n\n    #[PHPUnit\\Test]\n    public function isRelatedToShouldReturnFalseWhenSubjectDoesNotMatch(): void\n    {\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::SUBJECT => 'testing'], ''),\n        );\n\n        self::assertFalse($token->isRelatedTo('test'));\n    }\n\n    #[PHPUnit\\Test]\n    public function isRelatedToShouldReturnTrueWhenSubjectMatches(): void\n    {\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::SUBJECT => 'test'], ''),\n        );\n\n        self::assertTrue($token->isRelatedTo('test'));\n    }\n\n    #[PHPUnit\\Test]\n    public function hasBeenIssuedByShouldReturnFalseWhenIssuerIsNotConfigured(): void\n    {\n        $token = $this->createToken();\n\n        self::assertFalse($token->hasBeenIssuedBy('test'));\n    }\n\n    #[PHPUnit\\Test]\n    public function hasBeenIssuedByShouldReturnFalseWhenIssuerTypeDoesNotMatches(): void\n    {\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::ISSUER => 10], ''),\n        );\n\n        self::assertFalse($token->hasBeenIssuedBy('10'));\n    }\n\n    #[PHPUnit\\Test]\n    public function hasBeenIssuedByShouldReturnFalseWhenIssuerIsNotInTheGivenList(): void\n    {\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::ISSUER => 'test'], ''),\n        );\n\n        self::assertFalse($token->hasBeenIssuedBy('testing1', 'testing2'));\n    }\n\n    #[PHPUnit\\Test]\n    public function hasBeenIssuedByShouldReturnTrueWhenIssuerIsInTheGivenList(): void\n    {\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::ISSUER => 'test'], ''),\n        );\n\n        self::assertTrue($token->hasBeenIssuedBy('testing1', 'testing2', 'test'));\n    }\n\n    #[PHPUnit\\Test]\n    public function hasBeenIssuedBeforeShouldReturnTrueWhenIssueTimeIsNotConfigured(): void\n    {\n        $token = $this->createToken();\n\n        self::assertTrue($token->hasBeenIssuedBefore(new DateTimeImmutable()));\n    }\n\n    #[PHPUnit\\Test]\n    public function hasBeenIssuedBeforeShouldReturnTrueWhenIssueTimeIsBeforeThanNow(): void\n    {\n        $now   = new DateTimeImmutable();\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::ISSUED_AT => $now->modify('-100 seconds')], ''),\n        );\n\n        self::assertTrue($token->hasBeenIssuedBefore($now));\n    }\n\n    #[PHPUnit\\Test]\n    public function hasBeenIssuedBeforeShouldReturnTrueWhenIssueTimeIsEqualsToNow(): void\n    {\n        $now   = new DateTimeImmutable();\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::ISSUED_AT => $now], ''),\n        );\n\n        self::assertTrue($token->hasBeenIssuedBefore($now));\n    }\n\n    #[PHPUnit\\Test]\n    public function hasBeenIssuedBeforeShouldReturnFalseWhenIssueTimeIsGreaterThanNow(): void\n    {\n        $now   = new DateTimeImmutable();\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::ISSUED_AT => $now->modify('+100 seconds')], ''),\n        );\n\n        self::assertFalse($token->hasBeenIssuedBefore($now));\n    }\n\n    #[PHPUnit\\Test]\n    public function isMinimumTimeBeforeShouldReturnTrueWhenIssueTimeIsNotConfigured(): void\n    {\n        $token = $this->createToken();\n\n        self::assertTrue($token->isMinimumTimeBefore(new DateTimeImmutable()));\n    }\n\n    #[PHPUnit\\Test]\n    public function isMinimumTimeBeforeShouldReturnTrueWhenNotBeforeClaimIsBeforeThanNow(): void\n    {\n        $now   = new DateTimeImmutable();\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::NOT_BEFORE => $now->modify('-100 seconds')], ''),\n        );\n\n        self::assertTrue($token->isMinimumTimeBefore($now));\n    }\n\n    #[PHPUnit\\Test]\n    public function isMinimumTimeBeforeShouldReturnTrueWhenNotBeforeClaimIsEqualsToNow(): void\n    {\n        $now   = new DateTimeImmutable();\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::NOT_BEFORE => $now], ''),\n        );\n\n        self::assertTrue($token->isMinimumTimeBefore($now));\n    }\n\n    #[PHPUnit\\Test]\n    public function isMinimumTimeBeforeShouldReturnFalseWhenNotBeforeClaimIsGreaterThanNow(): void\n    {\n        $now   = new DateTimeImmutable();\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::NOT_BEFORE => $now->modify('100 seconds')], ''),\n        );\n\n        self::assertFalse($token->isMinimumTimeBefore($now));\n    }\n\n    #[PHPUnit\\Test]\n    public function isExpiredShouldReturnFalseWhenTokenDoesNotExpires(): void\n    {\n        $token = $this->createToken();\n\n        self::assertFalse($token->isExpired(new DateTimeImmutable()));\n    }\n\n    #[PHPUnit\\Test]\n    public function isExpiredShouldReturnFalseWhenTokenIsNotExpired(): void\n    {\n        $now = new DateTimeImmutable();\n\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::EXPIRATION_TIME => $now->modify('+500 seconds')], ''),\n        );\n\n        self::assertFalse($token->isExpired($now));\n    }\n\n    #[PHPUnit\\Test]\n    public function isExpiredShouldReturnTrueWhenExpirationIsEqualsToNow(): void\n    {\n        $now = new DateTimeImmutable();\n\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::EXPIRATION_TIME => $now], ''),\n        );\n\n        self::assertTrue($token->isExpired($now));\n    }\n\n    #[PHPUnit\\Test]\n    public function isExpiredShouldReturnTrueAfterTokenExpires(): void\n    {\n        $now = new DateTimeImmutable();\n\n        $token = $this->createToken(\n            null,\n            new DataSet([RegisteredClaims::EXPIRATION_TIME => $now], ''),\n        );\n\n        self::assertTrue($token->isExpired($now->modify('+10 days')));\n    }\n\n    #[PHPUnit\\Test]\n    public function toStringMustReturnEncodedDataWithEmptySignature(): void\n    {\n        $token = $this->createToken(null, null, new Signature('123', '456'));\n\n        self::assertSame('headers.claims.456', $token->toString());\n    }\n\n    #[PHPUnit\\Test]\n    public function toStringMustReturnEncodedData(): void\n    {\n        $token = $this->createToken();\n\n        self::assertSame('headers.claims.signature', $token->toString());\n    }\n}\n"
  },
  {
    "path": "tests/Token/SignatureTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Token;\n\nuse Lcobucci\\JWT\\Token\\Signature;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(Signature::class)]\nfinal class SignatureTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    public function hashShouldReturnTheHash(): void\n    {\n        $signature = new Signature('test', 'encoded');\n\n        self::assertSame('test', $signature->hash());\n        self::assertSame('encoded', $signature->toString());\n    }\n}\n"
  },
  {
    "path": "tests/UnsignedTokenTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse DateTimeImmutable;\nuse Lcobucci\\Clock\\FrozenClock;\nuse Lcobucci\\JWT\\Configuration;\nuse Lcobucci\\JWT\\Encoding;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\Constraint\\IdentifiedBy;\nuse Lcobucci\\JWT\\Validation\\Constraint\\IssuedBy;\nuse Lcobucci\\JWT\\Validation\\Constraint\\LooseValidAt;\nuse Lcobucci\\JWT\\Validation\\Constraint\\PermittedFor;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse Lcobucci\\JWT\\Validation\\Validator;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function assert;\n\n#[PHPUnit\\CoversClass(Configuration::class)]\n#[PHPUnit\\CoversClass(Encoding\\JoseEncoder::class)]\n#[PHPUnit\\CoversClass(Encoding\\ChainedFormatter::class)]\n#[PHPUnit\\CoversClass(Encoding\\MicrosecondBasedDateConversion::class)]\n#[PHPUnit\\CoversClass(Encoding\\UnifyAudience::class)]\n#[PHPUnit\\CoversClass(Token\\Builder::class)]\n#[PHPUnit\\CoversClass(Token\\Parser::class)]\n#[PHPUnit\\CoversClass(Token\\Plain::class)]\n#[PHPUnit\\CoversClass(Token\\DataSet::class)]\n#[PHPUnit\\CoversClass(Token\\Signature::class)]\n#[PHPUnit\\CoversClass(InMemory::class)]\n#[PHPUnit\\CoversClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(RequiredConstraintsViolated::class)]\n#[PHPUnit\\CoversClass(Validator::class)]\n#[PHPUnit\\CoversClass(IssuedBy::class)]\n#[PHPUnit\\CoversClass(PermittedFor::class)]\n#[PHPUnit\\CoversClass(IdentifiedBy::class)]\n#[PHPUnit\\CoversClass(LooseValidAt::class)]\nclass UnsignedTokenTest extends TestCase\n{\n    public const int CURRENT_TIME = 100000;\n\n    private Configuration $config;\n\n    #[PHPUnit\\Before]\n    public function createConfiguration(): void\n    {\n        $this->config = Configuration::forSymmetricSigner(\n            new KeyDumpSigner(),\n            InMemory::plainText('private'),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function builderCanGenerateAToken(): Token\n    {\n        $user    = ['name' => 'testing', 'email' => 'testing@abc.com'];\n        $builder = $this->config->builder();\n\n        $expiration = new DateTimeImmutable('@' . (self::CURRENT_TIME + 3000));\n\n        $token = $builder->identifiedBy('1')\n                         ->permittedFor('https://client.abc.com')\n                         ->issuedBy('https://api.abc.com')\n                         ->expiresAt($expiration)\n                         ->withClaim('user', $user)\n                         ->getToken($this->config->signer(), $this->config->signingKey());\n\n        self::assertEquals(new Token\\Signature('private', 'cHJpdmF0ZQ'), $token->signature());\n        self::assertEquals(['https://client.abc.com'], $token->claims()->get(Token\\RegisteredClaims::AUDIENCE));\n        self::assertSame('https://api.abc.com', $token->claims()->get(Token\\RegisteredClaims::ISSUER));\n        self::assertEquals($expiration, $token->claims()->get(Token\\RegisteredClaims::EXPIRATION_TIME));\n        self::assertEquals($user, $token->claims()->get('user'));\n\n        return $token;\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function parserCanReadAToken(Token $generated): void\n    {\n        $read = $this->config->parser()->parse($generated->toString());\n        assert($read instanceof Token\\Plain);\n\n        self::assertEquals($generated, $read);\n        self::assertSame('testing', $read->claims()->get('user')['name']);\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function tokenValidationShouldPassWhenEverythingIsFine(Token $generated): void\n    {\n        $clock = new FrozenClock(new DateTimeImmutable('@' . self::CURRENT_TIME));\n\n        $constraints = [\n            new IdentifiedBy('1'),\n            new PermittedFor('https://client.abc.com'),\n            new IssuedBy('https://issuer.abc.com', 'https://api.abc.com'),\n            new LooseValidAt($clock),\n        ];\n\n        self::assertTrue($this->config->validator()->validate($generated, ...$constraints));\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function tokenValidationShouldAllowCustomConstraint(Token $generated): void\n    {\n        self::assertTrue($this->config->validator()->validate($generated, $this->validUserConstraint()));\n    }\n\n    #[PHPUnit\\Test]\n    #[PHPUnit\\Depends('builderCanGenerateAToken')]\n    public function tokenAssertionShouldRaiseExceptionWhenOneOfTheConstraintsFails(Token $generated): void\n    {\n        $constraints = [\n            new IdentifiedBy('1'),\n            new IssuedBy('https://issuer.abc.com'),\n        ];\n\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $this->config->validator()->assert($generated, ...$constraints);\n    }\n\n    private function validUserConstraint(): Constraint\n    {\n        return new class () implements Constraint\n        {\n            public function assert(Token $token): void\n            {\n                if (! $token instanceof Token\\Plain) {\n                    throw new ConstraintViolation();\n                }\n\n                $claims = $token->claims();\n\n                if (! $claims->has('user')) {\n                    throw new ConstraintViolation();\n                }\n\n                $name  = $claims->get('user')['name'] ?? '';\n                $email = $claims->get('user')['email'] ?? '';\n\n                if ($name === '' || $email === '') {\n                    throw new ConstraintViolation();\n                }\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "tests/UnsupportedParser.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests;\n\nuse DateTimeInterface;\nuse Lcobucci\\JWT\\Parser;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Token\\DataSet;\n\nfinal readonly class UnsupportedParser implements Parser\n{\n    public function parse(string $jwt): Token\n    {\n        return new class () implements Token {\n            public function headers(): DataSet\n            {\n                return new DataSet([], '');\n            }\n\n            public function isPermittedFor(string $audience): bool\n            {\n                return false;\n            }\n\n            public function isIdentifiedBy(string $id): bool\n            {\n                return false;\n            }\n\n            public function isRelatedTo(string $subject): bool\n            {\n                return false;\n            }\n\n            public function hasBeenIssuedBy(string ...$issuers): bool\n            {\n                return false;\n            }\n\n            public function hasBeenIssuedBefore(DateTimeInterface $now): bool\n            {\n                return false;\n            }\n\n            public function isMinimumTimeBefore(DateTimeInterface $now): bool\n            {\n                return false;\n            }\n\n            public function isExpired(DateTimeInterface $now): bool\n            {\n                return false;\n            }\n\n            public function toString(): string\n            {\n                return 'unsupported-parser';\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/ConstraintTestCase.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse Closure;\nuse Lcobucci\\JWT\\Builder;\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Token\\DataSet;\nuse Lcobucci\\JWT\\Token\\Plain;\nuse Lcobucci\\JWT\\Token\\Signature;\nuse Lcobucci\\JWT\\UnencryptedToken;\nuse PHPUnit\\Framework\\TestCase;\n\nabstract class ConstraintTestCase extends TestCase\n{\n    /**\n     * @param array<non-empty-string, mixed> $claims\n     * @param array<non-empty-string, mixed> $headers\n     */\n    protected function buildToken(\n        array $claims = [],\n        array $headers = [],\n        ?Signature $signature = null,\n    ): Plain {\n        return new Plain(\n            new DataSet($headers, ''),\n            new DataSet($claims, ''),\n            $signature ?? new Signature('sig+hash', 'sig+encoded'),\n        );\n    }\n\n    protected function issueToken(Signer $signer, Signer\\Key $key, ?Closure $customization = null): UnencryptedToken\n    {\n        return (new JwtFacade())->issue(\n            $signer,\n            $key,\n            $customization ?? static fn (Builder $builder) => $builder,\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/HasClaimTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint\\CannotValidateARegisteredClaim;\nuse Lcobucci\\JWT\\Validation\\Constraint\\HasClaim;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(HasClaim::class)]\n#[PHPUnit\\CoversClass(CannotValidateARegisteredClaim::class)]\n#[PHPUnit\\UsesClass(Token\\DataSet::class)]\n#[PHPUnit\\UsesClass(Token\\Plain::class)]\n#[PHPUnit\\UsesClass(Token\\Signature::class)]\nfinal class HasClaimTest extends ConstraintTestCase\n{\n    /** @param non-empty-string $claim */\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('registeredClaims')]\n    public function registeredClaimsCannotBeValidatedUsingThisConstraint(string $claim): void\n    {\n        $this->expectException(CannotValidateARegisteredClaim::class);\n        $this->expectExceptionMessage(\n            'The claim \"' . $claim . '\" is a registered claim, another constraint must be used to validate its value',\n        );\n\n        new HasClaim($claim);\n    }\n\n    /** @return iterable<non-empty-string, array{non-empty-string}> */\n    public static function registeredClaims(): iterable\n    {\n        foreach (Token\\RegisteredClaims::ALL as $claim) {\n            yield $claim => [$claim];\n        }\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenClaimIsNotSet(): void\n    {\n        $constraint = new HasClaim('claimId');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token does not have the claim \"claimId\"');\n\n        $constraint->assert($this->buildToken());\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenTokenIsNotAPlainToken(): void\n    {\n        $token      = self::createStub(Token::class);\n        $constraint = new HasClaim('claimId');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('You should pass a plain token');\n\n        $constraint->assert($token);\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionWhenClaimMatches(): void\n    {\n        $token      = $this->buildToken(['claimId' => 'claimValue']);\n        $constraint = new HasClaim('claimId');\n\n        $constraint->assert($token);\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/HasClaimWithValueTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint\\CannotValidateARegisteredClaim;\nuse Lcobucci\\JWT\\Validation\\Constraint\\HasClaimWithValue;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(HasClaimWithValue::class)]\n#[PHPUnit\\CoversClass(CannotValidateARegisteredClaim::class)]\n#[PHPUnit\\UsesClass(Token\\DataSet::class)]\n#[PHPUnit\\UsesClass(Token\\Plain::class)]\n#[PHPUnit\\UsesClass(Token\\Signature::class)]\nfinal class HasClaimWithValueTest extends ConstraintTestCase\n{\n    /** @param non-empty-string $claim */\n    #[PHPUnit\\Test]\n    #[PHPUnit\\DataProvider('registeredClaims')]\n    public function registeredClaimsCannotBeValidatedUsingThisConstraint(string $claim): void\n    {\n        $this->expectException(CannotValidateARegisteredClaim::class);\n        $this->expectExceptionMessage(\n            'The claim \"' . $claim . '\" is a registered claim, another constraint must be used to validate its value',\n        );\n\n        new HasClaimWithValue($claim, 'testing');\n    }\n\n    /** @return iterable<non-empty-string, array{non-empty-string}> */\n    public static function registeredClaims(): iterable\n    {\n        foreach (Token\\RegisteredClaims::ALL as $claim) {\n            yield $claim => [$claim];\n        }\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenClaimIsNotSet(): void\n    {\n        $constraint = new HasClaimWithValue('claimId', 'claimValue');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token does not have the claim \"claimId\"');\n\n        $constraint->assert($this->buildToken());\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenClaimValueDoesNotMatch(): void\n    {\n        $constraint = new HasClaimWithValue('claimId', 'claimValue');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The claim \"claimId\" does not have the expected value');\n\n        $constraint->assert($this->buildToken(['claimId' => 'Some wrong value']));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenTokenIsNotAPlainToken(): void\n    {\n        $constraint = new HasClaimWithValue('claimId', 'claimValue');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('You should pass a plain token');\n\n        $constraint->assert(self::createStub(Token::class));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionWhenClaimMatches(): void\n    {\n        $token      = $this->buildToken(['claimId' => 'claimValue']);\n        $constraint = new HasClaimWithValue('claimId', 'claimValue');\n\n        $constraint->assert($token);\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/IdentifiedByTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse Lcobucci\\JWT\\Validation\\Constraint\\IdentifiedBy;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(IdentifiedBy::class)]\n#[PHPUnit\\UsesClass(Token\\DataSet::class)]\n#[PHPUnit\\UsesClass(Token\\Plain::class)]\n#[PHPUnit\\UsesClass(Token\\Signature::class)]\nfinal class IdentifiedByTest extends ConstraintTestCase\n{\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenIdIsNotSet(): void\n    {\n        $constraint = new IdentifiedBy('123456');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token is not identified with the expected ID');\n\n        $constraint->assert($this->buildToken());\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenIdDoesNotMatch(): void\n    {\n        $constraint = new IdentifiedBy('123456');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token is not identified with the expected ID');\n\n        $constraint->assert($this->buildToken([RegisteredClaims::ID => 15]));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionWhenIdMatches(): void\n    {\n        $token = $this->buildToken([RegisteredClaims::ID => '123456']);\n\n        $constraint = new IdentifiedBy('123456');\n\n        $constraint->assert($token);\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/IssuedByTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse Lcobucci\\JWT\\Validation\\Constraint\\IssuedBy;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(IssuedBy::class)]\n#[PHPUnit\\UsesClass(Token\\DataSet::class)]\n#[PHPUnit\\UsesClass(Token\\Plain::class)]\n#[PHPUnit\\UsesClass(Token\\Signature::class)]\nfinal class IssuedByTest extends ConstraintTestCase\n{\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenIssuerIsNotSet(): void\n    {\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token was not issued by the given issuers');\n\n        $constraint = new IssuedBy('test.com', 'test.net');\n        $constraint->assert($this->buildToken());\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenIssuerValueDoesNotMatch(): void\n    {\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token was not issued by the given issuers');\n\n        $constraint = new IssuedBy('test.com', 'test.net');\n        $constraint->assert($this->buildToken([RegisteredClaims::ISSUER => 'example.com']));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenIssuerTypeValueDoesNotMatch(): void\n    {\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token was not issued by the given issuers');\n\n        $constraint = new IssuedBy('test.com', '123');\n        $constraint->assert($this->buildToken([RegisteredClaims::ISSUER => 123]));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionWhenIssuerMatches(): void\n    {\n        $token      = $this->buildToken([RegisteredClaims::ISSUER => 'test.com']);\n        $constraint = new IssuedBy('test.com', 'test.net');\n\n        $constraint->assert($token);\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/LooseValidAtTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse DateInterval;\nuse Lcobucci\\Clock\\Clock;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\Constraint\\LooseValidAt;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(Constraint\\LeewayCannotBeNegative::class)]\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(LooseValidAt::class)]\n#[PHPUnit\\UsesClass(Token\\DataSet::class)]\n#[PHPUnit\\UsesClass(Token\\Plain::class)]\n#[PHPUnit\\UsesClass(Token\\Signature::class)]\nfinal class LooseValidAtTest extends ValidAtTestCase\n{\n    protected function buildValidAtConstraint(Clock $clock, ?DateInterval $leeway = null): Constraint\n    {\n        return new LooseValidAt($clock, $leeway);\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionWhenTokenDoesNotHaveTimeClaims(): void\n    {\n        $token      = $this->buildToken();\n        $constraint = $this->buildValidAtConstraint($this->clock);\n\n        $constraint->assert($token);\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/PermittedForTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse Lcobucci\\JWT\\Validation\\Constraint\\PermittedFor;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(PermittedFor::class)]\n#[PHPUnit\\UsesClass(Token\\DataSet::class)]\n#[PHPUnit\\UsesClass(Token\\Plain::class)]\n#[PHPUnit\\UsesClass(Token\\Signature::class)]\nfinal class PermittedForTest extends ConstraintTestCase\n{\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenAudienceIsNotSet(): void\n    {\n        $constraint = new PermittedFor('test.com');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token is not allowed to be used by this audience');\n\n        $constraint->assert($this->buildToken());\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenAudienceValueDoesNotMatch(): void\n    {\n        $constraint = new PermittedFor('test.com');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token is not allowed to be used by this audience');\n\n        $constraint->assert($this->buildToken([RegisteredClaims::AUDIENCE => ['aa.com']]));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenAudienceTypeDoesNotMatch(): void\n    {\n        $constraint = new PermittedFor('123');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token is not allowed to be used by this audience');\n\n        $constraint->assert($this->buildToken([RegisteredClaims::AUDIENCE => [123]]));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionWhenAudienceMatches(): void\n    {\n        $token      = $this->buildToken([RegisteredClaims::AUDIENCE => ['aa.com', 'test.com']]);\n        $constraint = new PermittedFor('test.com');\n\n        $constraint->assert($token);\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/RelatedToTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse Lcobucci\\JWT\\Validation\\Constraint\\RelatedTo;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(RelatedTo::class)]\n#[PHPUnit\\UsesClass(Token\\DataSet::class)]\n#[PHPUnit\\UsesClass(Token\\Plain::class)]\n#[PHPUnit\\UsesClass(Token\\Signature::class)]\nfinal class RelatedToTest extends ConstraintTestCase\n{\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenSubjectIsNotSet(): void\n    {\n        $constraint = new RelatedTo('user-auth');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token is not related to the expected subject');\n\n        $constraint->assert($this->buildToken());\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenSubjectDoesNotMatch(): void\n    {\n        $constraint = new RelatedTo('user-auth');\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token is not related to the expected subject');\n\n        $constraint->assert($this->buildToken([RegisteredClaims::SUBJECT => 'password-recovery']));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionWhenSubjectMatches(): void\n    {\n        $token      = $this->buildToken([RegisteredClaims::SUBJECT => 'user-auth']);\n        $constraint = new RelatedTo('user-auth');\n\n        $constraint->assert($token);\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/SignedWithOneInSetTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse DateTimeImmutable;\nuse Lcobucci\\Clock\\FrozenClock;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Encoding\\UnifyAudience;\nuse Lcobucci\\JWT\\Encoding\\UnixTimestampDates;\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Tests\\Signer\\FakeSigner;\nuse Lcobucci\\JWT\\Token\\Builder;\nuse Lcobucci\\JWT\\Token\\DataSet;\nuse Lcobucci\\JWT\\Token\\Parser;\nuse Lcobucci\\JWT\\Token\\Plain;\nuse Lcobucci\\JWT\\Token\\Signature;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWithOneInSet;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWithUntilDate;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\nuse const PHP_EOL;\n\n#[PHPUnit\\CoversClass(SignedWithOneInSet::class)]\n#[PHPUnit\\CoversClass(SignedWithUntilDate::class)]\n#[PHPUnit\\CoversClass(SignedWith::class)]\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\n#[PHPUnit\\UsesClass(JwtFacade::class)]\n#[PHPUnit\\UsesClass(ChainedFormatter::class)]\n#[PHPUnit\\UsesClass(JoseEncoder::class)]\n#[PHPUnit\\UsesClass(UnifyAudience::class)]\n#[PHPUnit\\UsesClass(UnixTimestampDates::class)]\n#[PHPUnit\\UsesClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\UsesClass(Builder::class)]\n#[PHPUnit\\UsesClass(DataSet::class)]\n#[PHPUnit\\UsesClass(Plain::class)]\n#[PHPUnit\\UsesClass(Signature::class)]\n#[PHPUnit\\UsesClass(Parser::class)]\nfinal class SignedWithOneInSetTest extends ConstraintTestCase\n{\n    #[PHPUnit\\Test]\n    public function exceptionShouldBeRaisedWhenSignatureIsNotVerifiedByAllConstraints(): void\n    {\n        $clock  = new FrozenClock(new DateTimeImmutable('2023-11-19 22:20:00'));\n        $signer = new FakeSigner('123');\n\n        $constraint = new SignedWithOneInSet(\n            new SignedWithUntilDate($signer, InMemory::plainText('b'), $clock->now(), $clock),\n            new SignedWithUntilDate($signer, InMemory::plainText('c'), $clock->now()->modify('-2 minutes'), $clock),\n        );\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage(\n            'It was not possible to verify the signature of the token, reasons:'\n            . PHP_EOL . '- Token signature mismatch'\n            . PHP_EOL . '- This constraint was only usable until 2023-11-19T22:18:00+00:00',\n        );\n\n        $token = $this->issueToken($signer, InMemory::plainText('a'));\n        $constraint->assert($token);\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionsWhenSignatureIsVerifiedByAtLeastOneConstraint(): void\n    {\n        $clock  = new FrozenClock(new DateTimeImmutable('2023-11-19 22:20:00'));\n        $signer = new FakeSigner('123');\n\n        $constraint = new SignedWithOneInSet(\n            new SignedWithUntilDate($signer, InMemory::plainText('b'), $clock->now(), $clock),\n            new SignedWithUntilDate($signer, InMemory::plainText('c'), $clock->now()->modify('-2 minutes'), $clock),\n            new SignedWithUntilDate($signer, InMemory::plainText('a'), $clock->now(), $clock),\n        );\n\n        $token = $this->issueToken($signer, InMemory::plainText('a'));\n        $constraint->assert($token);\n\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/SignedWithTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse Lcobucci\\JWT\\Signer;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Token\\Signature;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\n\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(SignedWith::class)]\n#[PHPUnit\\UsesClass(Signer\\Key\\InMemory::class)]\n#[PHPUnit\\UsesClass(Token\\DataSet::class)]\n#[PHPUnit\\UsesClass(Token\\Plain::class)]\n#[PHPUnit\\UsesClass(Token\\Signature::class)]\nfinal class SignedWithTest extends ConstraintTestCase\n{\n    private Signer&MockObject $signer;\n    private Signer\\Key $key;\n    private Signature $signature;\n\n    #[PHPUnit\\Before]\n    public function createDependencies(): void\n    {\n        $this->signer = $this->createMock(Signer::class);\n        $this->signer->method('algorithmId')->willReturn('RS256');\n\n        $this->key       = Signer\\Key\\InMemory::plainText('123');\n        $this->signature = new Signature('1234', '5678');\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenTokenIsNotAPlainToken(): void\n    {\n        $this->signer->expects($this->never())->method(self::anything());\n\n        $constraint = new SignedWith($this->signer, $this->key);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('You should pass a plain token');\n\n        $constraint->assert(self::createStub(Token::class));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenSignerIsNotTheSame(): void\n    {\n        $token = $this->buildToken([], ['alg' => 'test'], $this->signature);\n\n        $this->signer->expects($this->never())->method('verify');\n\n        $constraint = new SignedWith($this->signer, $this->key);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('Token signer mismatch');\n\n        $constraint->assert($token);\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenSignatureIsInvalid(): void\n    {\n        $token = $this->buildToken([], ['alg' => 'RS256'], $this->signature);\n\n        $this->signer->expects($this->once())\n                     ->method('verify')\n                     ->with($this->signature->hash(), $token->payload(), $this->key)\n                     ->willReturn(false);\n\n        $constraint = new SignedWith($this->signer, $this->key);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('Token signature mismatch');\n\n        $constraint->assert($token);\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionWhenSignatureIsValid(): void\n    {\n        $token = $this->buildToken([], ['alg' => 'RS256'], $this->signature);\n\n        $this->signer->expects($this->once())\n                     ->method('verify')\n                     ->with($this->signature->hash(), $token->payload(), $this->key)\n                     ->willReturn(true);\n\n        $constraint = new SignedWith($this->signer, $this->key);\n\n        $constraint->assert($token);\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/SignedWithUntilDateTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse DateTimeImmutable;\nuse Lcobucci\\Clock\\FrozenClock;\nuse Lcobucci\\JWT\\Encoding\\ChainedFormatter;\nuse Lcobucci\\JWT\\Encoding\\JoseEncoder;\nuse Lcobucci\\JWT\\Encoding\\UnifyAudience;\nuse Lcobucci\\JWT\\Encoding\\UnixTimestampDates;\nuse Lcobucci\\JWT\\JwtFacade;\nuse Lcobucci\\JWT\\Signer\\Key\\InMemory;\nuse Lcobucci\\JWT\\SodiumBase64Polyfill;\nuse Lcobucci\\JWT\\Tests\\Signer\\FakeSigner;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Token\\Builder;\nuse Lcobucci\\JWT\\Token\\DataSet;\nuse Lcobucci\\JWT\\Token\\Plain;\nuse Lcobucci\\JWT\\Token\\Signature;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWith;\nuse Lcobucci\\JWT\\Validation\\Constraint\\SignedWithUntilDate;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(SignedWithUntilDate::class)]\n#[PHPUnit\\CoversClass(SignedWith::class)]\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\UsesClass(DataSet::class)]\n#[PHPUnit\\UsesClass(Plain::class)]\n#[PHPUnit\\UsesClass(Signature::class)]\n#[PHPUnit\\UsesClass(InMemory::class)]\n#[PHPUnit\\UsesClass(JwtFacade::class)]\n#[PHPUnit\\UsesClass(ChainedFormatter::class)]\n#[PHPUnit\\UsesClass(JoseEncoder::class)]\n#[PHPUnit\\UsesClass(UnifyAudience::class)]\n#[PHPUnit\\UsesClass(UnixTimestampDates::class)]\n#[PHPUnit\\UsesClass(SodiumBase64Polyfill::class)]\n#[PHPUnit\\UsesClass(Builder::class)]\n#[PHPUnit\\UsesClass(Token\\Parser::class)]\nfinal class SignedWithUntilDateTest extends ConstraintTestCase\n{\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenConstraintUsageIsNotValidAnymore(): void\n    {\n        $clock = new FrozenClock(new DateTimeImmutable('2023-11-19 22:45:10'));\n\n        $constraint = new SignedWithUntilDate(\n            new FakeSigner('1'),\n            InMemory::plainText('a'),\n            $clock->now()->modify('-1 hour'),\n            $clock,\n        );\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('This constraint was only usable until 2023-11-19T21:45:10+00:00');\n\n        $constraint->assert($this->issueToken(new FakeSigner('1'), InMemory::plainText('a')));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenTokenIsNotAPlainToken(): void\n    {\n        $clock = new FrozenClock(new DateTimeImmutable('2023-11-19 22:45:10'));\n\n        $constraint = new SignedWithUntilDate(new FakeSigner('1'), InMemory::plainText('a'), $clock->now(), $clock);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('You should pass a plain token');\n\n        $constraint->assert(self::createStub(Token::class));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenSignerIsNotTheSame(): void\n    {\n        $clock = new FrozenClock(new DateTimeImmutable('2023-11-19 22:45:10'));\n        $key   = InMemory::plainText('a');\n\n        $constraint = new SignedWithUntilDate(new FakeSigner('1'), $key, $clock->now(), $clock);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('Token signer mismatch');\n\n        $constraint->assert($this->issueToken(new FakeSigner('2'), $key));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenSignatureIsInvalid(): void\n    {\n        $clock  = new FrozenClock(new DateTimeImmutable('2023-11-19 22:45:10'));\n        $signer = new FakeSigner('1');\n\n        $constraint = new SignedWithUntilDate($signer, InMemory::plainText('a'), $clock->now(), $clock);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('Token signature mismatch');\n\n        $constraint->assert($this->issueToken($signer, InMemory::plainText('b')));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionWhenSignatureIsValid(): void\n    {\n        $clock = new FrozenClock(new DateTimeImmutable('2023-11-19 22:45:10'));\n\n        $signer = new FakeSigner('1');\n        $key    = InMemory::plainText('a');\n\n        $constraint = new SignedWithUntilDate($signer, $key, $clock->now(), $clock);\n        $constraint->assert($this->issueToken($signer, $key));\n\n        $this->addToAssertionCount(1);\n    }\n\n    #[PHPUnit\\Test]\n    public function clockShouldBeOptional(): void\n    {\n        $signer = new FakeSigner('1');\n        $key    = InMemory::plainText('a');\n\n        $constraint = new SignedWithUntilDate($signer, $key, new DateTimeImmutable('+10 seconds'));\n        $constraint->assert($this->issueToken($signer, $key));\n\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/StrictValidAtTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse DateInterval;\nuse Lcobucci\\Clock\\Clock;\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\Constraint\\StrictValidAt;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\CoversClass(Constraint\\LeewayCannotBeNegative::class)]\n#[PHPUnit\\CoversClass(StrictValidAt::class)]\n#[PHPUnit\\UsesClass(Token\\DataSet::class)]\n#[PHPUnit\\UsesClass(Token\\Plain::class)]\n#[PHPUnit\\UsesClass(Token\\Signature::class)]\nfinal class StrictValidAtTest extends ValidAtTestCase\n{\n    protected function buildValidAtConstraint(Clock $clock, ?DateInterval $leeway = null): Constraint\n    {\n        return new StrictValidAt($clock, $leeway);\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenTokenIsNotAPlainToken(): void\n    {\n        $constraint = $this->buildValidAtConstraint($this->clock);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('You should pass a plain token');\n\n        $constraint->assert(self::createStub(Token::class));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenIatClaimIsMissing(): void\n    {\n        $constraint = $this->buildValidAtConstraint($this->clock);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('\"Issued At\" claim missing');\n\n        $constraint->assert($this->buildToken());\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenNbfClaimIsMissing(): void\n    {\n        $now    = $this->clock->now();\n        $claims = [\n            RegisteredClaims::ISSUED_AT => $now->modify('-5 seconds'),\n        ];\n\n        $constraint = $this->buildValidAtConstraint($this->clock);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('\"Not Before\" claim missing');\n\n        $constraint->assert($this->buildToken($claims));\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenExpClaimIsMissing(): void\n    {\n        $now    = $this->clock->now();\n        $claims = [\n            RegisteredClaims::ISSUED_AT => $now->modify('-5 seconds'),\n            RegisteredClaims::NOT_BEFORE => $now->modify('-5 seconds'),\n        ];\n\n        $constraint = $this->buildValidAtConstraint($this->clock);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('\"Expiration Time\" claim missing');\n\n        $constraint->assert($this->buildToken($claims));\n    }\n}\n"
  },
  {
    "path": "tests/Validation/Constraint/ValidAtTestCase.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation\\Constraint;\n\nuse DateInterval;\nuse DateTimeImmutable;\nuse Lcobucci\\Clock\\Clock;\nuse Lcobucci\\Clock\\FrozenClock;\nuse Lcobucci\\JWT\\Token\\RegisteredClaims;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\Constraint\\LeewayCannotBeNegative;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\n\nabstract class ValidAtTestCase extends ConstraintTestCase\n{\n    protected Clock $clock;\n\n    #[PHPUnit\\Before]\n    final public function createDependencies(): void\n    {\n        $this->clock = new FrozenClock(new DateTimeImmutable());\n    }\n\n    abstract protected function buildValidAtConstraint(Clock $clock, ?DateInterval $leeway = null): Constraint;\n\n    #[PHPUnit\\Test]\n    final public function constructShouldRaiseExceptionOnNegativeLeeway(): void\n    {\n        $leeway         = new DateInterval('PT30S');\n        $leeway->invert = 1;\n\n        $this->expectException(LeewayCannotBeNegative::class);\n        $this->expectExceptionMessage('Leeway cannot be negative');\n\n        $this->buildValidAtConstraint($this->clock, $leeway);\n    }\n\n    #[PHPUnit\\Test]\n    final public function assertShouldRaiseExceptionWhenTokenIsExpired(): void\n    {\n        $now = $this->clock->now();\n\n        $claims = [\n            RegisteredClaims::ISSUED_AT => $now->modify('-20 seconds'),\n            RegisteredClaims::NOT_BEFORE => $now->modify('-10 seconds'),\n            RegisteredClaims::EXPIRATION_TIME => $now->modify('-10 seconds'),\n        ];\n\n        $constraint = $this->buildValidAtConstraint($this->clock);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token is expired');\n\n        $constraint->assert($this->buildToken($claims));\n    }\n\n    #[PHPUnit\\Test]\n    final public function assertShouldRaiseExceptionWhenMinimumTimeIsNotMet(): void\n    {\n        $now = $this->clock->now();\n\n        $claims = [\n            RegisteredClaims::ISSUED_AT => $now->modify('-20 seconds'),\n            RegisteredClaims::NOT_BEFORE => $now->modify('+40 seconds'),\n            RegisteredClaims::EXPIRATION_TIME => $now->modify('+60 seconds'),\n        ];\n\n        $constraint = $this->buildValidAtConstraint($this->clock);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token cannot be used yet');\n\n        $constraint->assert($this->buildToken($claims));\n    }\n\n    #[PHPUnit\\Test]\n    final public function assertShouldRaiseExceptionWhenTokenWasIssuedInTheFuture(): void\n    {\n        $now = $this->clock->now();\n\n        $claims = [\n            RegisteredClaims::ISSUED_AT => $now->modify('+20 seconds'),\n            RegisteredClaims::NOT_BEFORE => $now->modify('+40 seconds'),\n            RegisteredClaims::EXPIRATION_TIME => $now->modify('+60 seconds'),\n        ];\n\n        $constraint = $this->buildValidAtConstraint($this->clock);\n\n        $this->expectException(ConstraintViolation::class);\n        $this->expectExceptionMessage('The token was issued in the future');\n\n        $constraint->assert($this->buildToken($claims));\n    }\n\n    #[PHPUnit\\Test]\n    final public function assertShouldNotRaiseExceptionWhenLeewayIsUsed(): void\n    {\n        $now = $this->clock->now();\n\n        $claims = [\n            RegisteredClaims::ISSUED_AT => $now->modify('+5 seconds'),\n            RegisteredClaims::NOT_BEFORE => $now->modify('+5 seconds'),\n            RegisteredClaims::EXPIRATION_TIME => $now->modify('-5 seconds'),\n        ];\n\n        $constraint = $this->buildValidAtConstraint($this->clock, new DateInterval('PT6S'));\n        $constraint->assert($this->buildToken($claims));\n\n        $this->addToAssertionCount(1);\n    }\n\n    #[PHPUnit\\Test]\n    final public function assertShouldNotRaiseExceptionWhenTokenIsUsedInTheRightMoment(): void\n    {\n        $constraint = $this->buildValidAtConstraint($this->clock);\n        $now        = $this->clock->now();\n\n        $token = $this->buildToken(\n            [\n                RegisteredClaims::ISSUED_AT => $now->modify('-40 seconds'),\n                RegisteredClaims::NOT_BEFORE => $now->modify('-20 seconds'),\n                RegisteredClaims::EXPIRATION_TIME => $now->modify('+60 seconds'),\n            ],\n        );\n\n        $constraint->assert($token);\n        $this->addToAssertionCount(1);\n\n        $token = $this->buildToken(\n            [\n                RegisteredClaims::ISSUED_AT => $now,\n                RegisteredClaims::NOT_BEFORE => $now,\n                RegisteredClaims::EXPIRATION_TIME => $now->modify('+60 seconds'),\n            ],\n        );\n\n        $constraint->assert($token);\n        $this->addToAssertionCount(1);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/ConstraintViolationTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation;\n\nuse Lcobucci\\JWT\\Validation\\Constraint\\IdentifiedBy;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(ConstraintViolation::class)]\n#[PHPUnit\\UsesClass(IdentifiedBy::class)]\nfinal class ConstraintViolationTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    public function errorShouldConfigureMessageAndConstraint(): void\n    {\n        $violation = ConstraintViolation::error('testing', new IdentifiedBy('token id'));\n\n        self::assertSame('testing', $violation->getMessage());\n        self::assertSame(IdentifiedBy::class, $violation->constraint);\n    }\n}\n"
  },
  {
    "path": "tests/Validation/RequiredConstraintsViolatedTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation;\n\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(RequiredConstraintsViolated::class)]\n#[PHPUnit\\UsesClass(ConstraintViolation::class)]\nfinal class RequiredConstraintsViolatedTest extends TestCase\n{\n    #[PHPUnit\\Test]\n    public function fromViolationsShouldConfigureMessageAndViolationList(): void\n    {\n        $violation = new ConstraintViolation('testing');\n        $exception = RequiredConstraintsViolated::fromViolations($violation);\n\n        self::assertSame(\n            \"The token violates some mandatory constraints, details:\\n- testing\",\n            $exception->getMessage(),\n        );\n\n        self::assertSame([$violation], $exception->violations());\n    }\n}\n"
  },
  {
    "path": "tests/Validation/ValidatorTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Lcobucci\\JWT\\Tests\\Validation;\n\nuse Lcobucci\\JWT\\Token;\nuse Lcobucci\\JWT\\Validation\\Constraint;\nuse Lcobucci\\JWT\\Validation\\ConstraintViolation;\nuse Lcobucci\\JWT\\Validation\\NoConstraintsGiven;\nuse Lcobucci\\JWT\\Validation\\RequiredConstraintsViolated;\nuse Lcobucci\\JWT\\Validation\\Validator;\nuse PHPUnit\\Framework\\Attributes as PHPUnit;\nuse PHPUnit\\Framework\\MockObject\\Stub;\nuse PHPUnit\\Framework\\TestCase;\n\n#[PHPUnit\\CoversClass(Validator::class)]\n#[PHPUnit\\UsesClass(ConstraintViolation::class)]\n#[PHPUnit\\UsesClass(RequiredConstraintsViolated::class)]\nfinal class ValidatorTest extends TestCase\n{\n    private Token&Stub $token;\n\n    #[PHPUnit\\Before]\n    public function createDependencies(): void\n    {\n        $this->token = self::createStub(Token::class);\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenNoConstraintIsGiven(): void\n    {\n        $validator = new Validator();\n\n        $this->expectException(NoConstraintsGiven::class);\n\n        $validator->assert($this->token, ...[]);\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldRaiseExceptionWhenAtLeastOneConstraintFails(): void\n    {\n        $failedConstraint     = $this->createMock(Constraint::class);\n        $successfulConstraint = $this->createMock(Constraint::class);\n\n        $failedConstraint->expects($this->once())\n                         ->method('assert')\n                         ->willThrowException(new ConstraintViolation());\n\n        $successfulConstraint->expects($this->once())\n                             ->method('assert');\n\n        $validator = new Validator();\n\n        $this->expectException(RequiredConstraintsViolated::class);\n        $this->expectExceptionMessage('The token violates some mandatory constraints');\n\n        $validator->assert(\n            $this->token,\n            $failedConstraint,\n            $successfulConstraint,\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function assertShouldNotRaiseExceptionWhenNoConstraintFails(): void\n    {\n        $constraint = $this->createMock(Constraint::class);\n        $constraint->expects($this->once())->method('assert');\n\n        $validator = new Validator();\n\n        $validator->assert($this->token, $constraint);\n        $this->addToAssertionCount(1);\n    }\n\n    #[PHPUnit\\Test]\n    public function validateShouldRaiseExceptionWhenNoConstraintIsGiven(): void\n    {\n        $validator = new Validator();\n\n        $this->expectException(NoConstraintsGiven::class);\n\n        $validator->validate($this->token);\n    }\n\n    #[PHPUnit\\Test]\n    public function validateShouldReturnFalseWhenAtLeastOneConstraintFails(): void\n    {\n        $failedConstraint     = $this->createMock(Constraint::class);\n        $successfulConstraint = $this->createMock(Constraint::class);\n\n        $failedConstraint->expects($this->once())\n                         ->method('assert')\n                         ->willThrowException(new ConstraintViolation());\n\n        $successfulConstraint->expects($this->never())\n                             ->method('assert');\n\n        $validator = new Validator();\n\n        self::assertFalse(\n            $validator->validate(\n                $this->token,\n                $failedConstraint,\n                $successfulConstraint,\n            ),\n        );\n    }\n\n    #[PHPUnit\\Test]\n    public function validateShouldReturnTrueWhenNoConstraintFails(): void\n    {\n        $constraint = $this->createMock(Constraint::class);\n        $constraint->expects($this->once())->method('assert');\n\n        $validator = new Validator();\n        self::assertTrue($validator->validate($this->token, $constraint));\n    }\n}\n"
  },
  {
    "path": "tests/_keys/ecdsa/private.key",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIBGpMoZJ64MMSzuo5JbmXpf9V4qSWdLIl/8RmJLcfn/qoAoGCCqGSM49\nAwEHoUQDQgAE7it/EKmcv9bfpcV1fBreLMRXxWpnd0wxa2iFruiI2tsEdGFTLTsy\nU+GeRqC7zN0aTnTQajarUylKJ3UWr/r1kg==\n-----END EC PRIVATE KEY-----"
  },
  {
    "path": "tests/_keys/ecdsa/private2.key",
    "content": "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIM6G7WZ6SqoPwrHwGXhOJkYD+ErT8dfRvrNifgBQvSb7oAoGCCqGSM49\nAwEHoUQDQgAE09Hkp/u0tIGdzlQ99R/sXCOr9DTZAfLex4D4Po0C1L3qUqHrzZ0m\nB3bAhe+pwEDQ/jqVqdzxhA9i4PqT7F4Aew==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "tests/_keys/ecdsa/private_ec384.key",
    "content": "-----BEGIN EC PARAMETERS-----\nBgUrgQQAIg==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDC9EkM5BmOhEWZIrTnm2zkiEIDKdylYrRMYMyZiyTsSxihwrCoyH1hX\nF2UipTTWtaGgBwYFK4EEACKhZANiAAR86pZ9ZK8EWkFsoBj8idwMJqXtOMJ4GB+6\nGEq1/uypgrnkHbtgWLTRJgWnliurJpHV9SekXwoV0F5nxEgX3pQiYS6T2OtVAVs7\njxuwNTcdDm+a2KsZOb62ns0YXG+CL7o=\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "tests/_keys/ecdsa/private_ec512.key",
    "content": "-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIAD3yO2xWzu9sSrWXsUcsjppnOPGisza3C4cAL5eAV65xAm4Q6o0LP\nD7xIBDhxxqDXHsV9iGwbO0yyxzlLAyc3dIygBwYFK4EEACOhgYkDgYYABABOq2Sn\nfRlHi0Ofxg6JTFN+KLb36FWxSrsSzIstZW6zLIYPS3kAYJF3d+jGWRrcxO0kTTMe\ncg+bMYATIp8/BYKqXQGayqC3zb7Bxa+YnamkXkQGAU6g94TrGVA42+aYhBjklLRb\nF09R1HrIvzhy3unxzowqjKPozFq10VENyzO1TRx7mg==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "tests/_keys/ecdsa/public1.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7it/EKmcv9bfpcV1fBreLMRXxWpn\nd0wxa2iFruiI2tsEdGFTLTsyU+GeRqC7zN0aTnTQajarUylKJ3UWr/r1kg==\n-----END PUBLIC KEY-----"
  },
  {
    "path": "tests/_keys/ecdsa/public2.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdgxRxlhzhHGj+v6S2ikp+33LoGp5\nQWbEWv8BORsr2Ayg6C7deDDRM/s/f0R++4zZqXro1gDTVF5VDv7nE+EfEw==\n-----END PUBLIC KEY-----"
  },
  {
    "path": "tests/_keys/ecdsa/public2_ec512.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBpHZ4gSVGEeYFWsHYNDMSO49wPtsP\n4/yAqywK7D+OQ5P+1yhM3tAUm8wnI/+msJcrVpVf9eGdk8NQGtg9hTro7mEBzrBu\n3aNJqbkN7yXAb95rc19r787XvkxJ3YjJ+BRMZtYKn/1N/YdtkEpJVgt6WdVbsupB\nveMsYYahRoZgEZgFW78=\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "tests/_keys/ecdsa/public3.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE09Hkp/u0tIGdzlQ99R/sXCOr9DTZ\nAfLex4D4Po0C1L3qUqHrzZ0mB3bAhe+pwEDQ/jqVqdzxhA9i4PqT7F4Aew==\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "tests/_keys/ecdsa/public_ec384.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEfOqWfWSvBFpBbKAY/IncDCal7TjCeBgf\nuhhKtf7sqYK55B27YFi00SYFp5YrqyaR1fUnpF8KFdBeZ8RIF96UImEuk9jrVQFb\nO48bsDU3HQ5vmtirGTm+tp7NGFxvgi+6\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "tests/_keys/ecdsa/public_ec512.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQATqtkp30ZR4tDn8YOiUxTfii29+hV\nsUq7EsyLLWVusyyGD0t5AGCRd3foxlka3MTtJE0zHnIPmzGAEyKfPwWCql0Bmsqg\nt82+wcWvmJ2ppF5EBgFOoPeE6xlQONvmmIQY5JS0WxdPUdR6yL84ct7p8c6MKoyj\n6MxatdFRDcsztU0ce5o=\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "tests/_keys/rsa/encrypted-private.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-128-CBC,0D71668CE71033CB9150ED82FC87F4A1\n\nuLzPNDdlHnZ77tAGMHyPYERDMBcdV4SsQJYcSjiHhR2o0dLGTdgOpQrXTHPX4GJF\nLlEWLhAAV9wx2mM/2kHDWB4uZwThtT9/v+RFoW1WbVO/d3lhI9fg4/73/DWAH/7/\nafMRc7ZOVoAmVCESotPx4khCHoE97RdY/JtkLTzc3+peqmL53AbYXrg9rTN1B+ZV\nU3w4ciQS8Uki87zDYIBjYtaOCyMUTvug25CvdssvUMBoc/Jc0xps6/vAyXrnzlGT\npZD0Tst8idswfDi613BhAaxJspeY0AErWA59qJ3eGzbiQq5RDWcbJe/Tz5r/6+NN\nDkvNQ7DaEZ6LpeWX0MUq6/QWfrM8yE95XhjyC1d3LYn32lXHUygbgTFWIgLDoOE6\nnBhu34SWtbLAnqYGewaJFxhlYVS9rb/uvYQg70r5X9Sx6alCQPiPyIv39IItezn2\nHF2GRfE91MPZUeDhdqdvvOlSZVM5KnYc1fhamGAwM48gdDDXe8Czu/JEGoANNvC3\nl/Z1p5RtGF4hrel9WpeX9zQq3pvtfVcVIiWuRUwCOSQytXlieRK37sMuYeggvmjV\nVvaCods3mS/panWg9T/D/deIXjhzNJLvyiJg8+3sY5H4yNe0XpbaAc/ySwt9Rcxy\nFzFQ+5pghLSZgR1uV3AhdcnzXBU2GkYhdGKt2tUsH0UeVQ2BXxTlBFsCOh2dWqcj\ny3suIG65bukDAAWidQ4q3S6ZIMpXBhhCj7nwB5jQ7wSlU3U9So0ndr7zxdUILiMm\nchHi3q5apVZnMGcwv2B33rt4nD7HgGEmRKkCelrSrBATY1ut+T4rCDzKDqDs3jpv\nhYIWrlNPTkJyQz3eWly6Db+FJEfdYGadYJusc7/nOxCh/QmUu8Sh3NhKT6TH0bS7\n1AAqd8H+2hJ9I32Dhd2qwAF7PkNe2LGi+P8tbAtepKGim5w65wnsPePMnrfxumsG\nPeDnMrqeCKy+fME7a/MS5kmEBpmD4BMhVC6/OhFVz8gBty1f8yIEZggHNQN2QK7m\nNIrG+PwqW2w8HoxOlAi2Ix4LTPifrdfsH02U7aM1pgo1rZzD4AOzqvzCaK43H2VB\nBHLeTBGoLEUxXA9C+iGbeQlKXkMC00QKkjK5+nvkvnvePFfsrTQIpuyGufD/MoPb\n6fpwsyHZDxhxMN1PJk1b1lPq2Ui4hXpVNOYd4Q6OQz7bwxTMRX9XQromUlKMMgAT\nedX8v2NdM7Ssy1IwHuGVbDEpZdjoeaWZ1iNRV17i/EaJAqwYDQLfsuHBlzZL1ov1\nxkKVJdL8Y3q80oRAzTQDVdzL/rI44LLAfv609YByCnw29feYJY2W6gV0O7ZSw413\nXUkc5CaEbR1LuG8NtnOOPJV4Tb/hNsIDtvVm7Hl5npBKBe4iVgQ2LNuC2eT69d/z\nuvzgjISlumPiO5ivuYe0QtLPuJSc+/Bl8bPL8gcNQEtqkzj7IftHPPZNs+bJC2uY\nbPjq5KoDNAMF6VHuKHwu48MBYpnXDIg3ZenmJwGRULRBhK6324hDS6NJ7ULTBU2M\nTZCHmg89ySLBfCAspVeo63o/R7bs9a7BP9x2h5uwCBogSvkEwhhPKnboVN45bp9c\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "tests/_keys/rsa/encrypted-public.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwLpbUP8a9yflt5LKUUS3\nNPuRM7yEouPWg0VKeY5AURu4i8bqQ20K5jwfRJ+w05FvlywG4EuxpnpTFTVS2/do\nq3xufzTf/C3KIDOAHEifkdx4140btKxxm4mD9Eu2CQ32adZyScha50KUFlfnAAic\nHb8wYxjFyWo3PAbGYmCQCn2z97Ab0Ar6NR1e+V9f8EL9Orr2f04puKJfQTZdWVDF\nUJR4w7QZ/CPY0LEsiFLW3QQCNraka1mtrLJwPqreBtDEkj8IoISNkrguu/97RQZz\nmiJgBQkVjr6OfqG5WIFr0MzbRZc1/aK9g8ft88nhhQm0E3GqkCxBKTwgA03HtK07\nqQIDAQAB\n-----END PUBLIC KEY-----"
  },
  {
    "path": "tests/_keys/rsa/private.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTvwE87MtgREYL\nTL4aHhQo3ZzogmxxvMUsKnPzyxRs1YrXOSOpwN0npsXarBKKVIUMNLfFODp/vnQn\n2Zp06N8XG59WAOKwvC4MfxLDQkA+JXggzHlkbVoTN+dUkdYIFqSKuAPGwiWToRK2\nSxEhij3rE2FON8jQZvDxZkiP9a4vxJO3OTPQwKredXFiObsXD/c3RtLFhKctjCyH\nOIrP0bQEsee/m7JNtG4ry6BPusN6wb+vJo5ieBYPa3c19akNq6q/nYWhplhkkJSu\naOrL5xXEFzI5TvcvnXR568GVcxK8YLfFkdxpsXGt5rAbeh0h/U5kILEAqv8P9PGT\nZpicKbrnAgMBAAECggEAd3yTQEQHR91/ASVfKPHMQns77eCbPVtekFusbugsMHYY\nEPdHbqVMpvFvOMRc+f5Tzd15ziq6qBdbCJm8lThLm4iU0z1QrpaiDZ8vgUvDYM5Y\nCXoZDli+uZWUTp60/n94fmb0ipZIChScsI2PrzOJWTvobvD/uso8MJydWc8zafQm\nuqYzygOfjFZvU4lSfgzpefhpquy0JUy5TiKRmGUnwLb3TtcsVavjsn4QmNwLYgOF\n2OE+R12ex3pAKTiRE6FcnE1xFIo1GKhBa2Otgw3MDO6Gg+kn8Q4alKz6C6RRlgaH\nR7sYzEfJhsk/GGFTYOzXKQz2lSaStKt9wKCor04RcQKBgQDzPOu5jCTfayUo7xY2\njHtiogHyKLLObt9l3qbwgXnaD6rnxYNvCrA0OMvT+iZXsFZKJkYzJr8ZOxOpPROk\n10WdOaefiwUyL5dypueSwlIDwVm+hI4Bs82MajHtzOozh+73wA+aw5rPs84Uix9w\nVbbwaVR6qP/BV09yJYS5kQ7fmwKBgQDe2xjywX2d2MC+qzRr+LfU+1+gq0jjhBCX\nWHqRN6IECB0xTnXUf9WL/VCoI1/55BhdbbEja+4btYgcXSPmlXBIRKQ4VtFfVmYB\nkPXeD8oZ7LyuNdCsbKNe+x1IHXDe6Wfs3L9ulCfXxeIE84wy3fd66mQahyXV9iD9\nCkuifMqUpQKBgQCiydHlY1LGJ/o9tA2Ewm5Na6mrvOs2V2Ox1NqbObwoYbX62eiF\n53xX5u8bVl5U75JAm+79it/4bd5RtKux9dUETbLOhwcaOFm+hM+VG/IxyzRZ2nMD\n1qcpY2U5BpxzknUvYF3RMTop6edxPk7zKpp9ubCtSu+oINvtxAhY/SkcIwKBgGP1\nupcImyO2GZ5shLL5eNubdSVILwV+M0LveOqyHYXZbd6z5r5OKKcGFKuWUnJwEU22\n6gGNY9wh7M9sJ7JBzX9c6pwqtPcidda2AtJ8GpbOTUOG9/afNBhiYpv6OKqD3w2r\nZmJfKg/qvpqh83zNezgy8nvDqwDxyZI2j/5uIx/RAoGBAMWRmxtv6H2cKhibI/aI\nMTJM4QRjyPNxQqvAQsv+oHUbid06VK3JE+9iQyithjcfNOwnCaoO7I7qAj9QEfJS\nMZQc/W/4DHJebo2kd11yoXPVTXXOuEwLSKCejBXABBY0MPNuPUmiXeU0O3Tyi37J\nTUKzrgcd7NvlA41Y4xKcOqEA\n-----END PRIVATE KEY-----"
  },
  {
    "path": "tests/_keys/rsa/private_512.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAxZT4cHZXf5QfGX1m\noiSKKSC6AeFO8tGIn9C+4x/bEaQAq6f+V5/0+lFG6uboGC7eItPNWfOMmLnrI162\ncOnB1QIDAQABAkBn4OKdfhqSoLWZGS0UolFhPiuNQX/YegFyxLUXhHAQ3VQdUAHs\n4jFT2tviDI1uREdCooKyIQIYOVILrikkc8QBAiEA67ch0VSQvH6A2YO8mKiAUz40\naB3S4bUKdgj9FoLO/OECIQDWlb/G65w4nfXBplhChGm7SKTS+4zfe6SuqVQYsF8P\ndQIgYlCtC0mxYN2G0rLOzAGkHJRaeX7PAZNofJj9LxF6UiECIEMPX3SJ8zNaYhAX\nrSN0gBpwVFo/FMJOwKN49XgVvk91AiEAk/DSNPKv1djAz5nzd4t9I4tOqrbPwWr1\nQOxSsGdKfBg=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "tests/_keys/rsa/public.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA078BPOzLYERGC0y+Gh4U\nKN2c6IJscbzFLCpz88sUbNWK1zkjqcDdJ6bF2qwSilSFDDS3xTg6f750J9madOjf\nFxufVgDisLwuDH8Sw0JAPiV4IMx5ZG1aEzfnVJHWCBakirgDxsIlk6EStksRIYo9\n6xNhTjfI0Gbw8WZIj/WuL8STtzkz0MCq3nVxYjm7Fw/3N0bSxYSnLYwshziKz9G0\nBLHnv5uyTbRuK8ugT7rDesG/ryaOYngWD2t3NfWpDauqv52FoaZYZJCUrmjqy+cV\nxBcyOU73L510eevBlXMSvGC3xZHcabFxreawG3odIf1OZCCxAKr/D/Txk2aYnCm6\n5wIDAQAB\n-----END PUBLIC KEY-----"
  },
  {
    "path": "tests/_keys/rsa/public_512.key",
    "content": "-----BEGIN PUBLIC KEY-----\nMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMWU+HB2V3+UHxl9ZqIkiikgugHhTvLR\niJ/QvuMf2xGkAKun/lef9PpRRurm6Bgu3iLTzVnzjJi56yNetnDpwdUCAwEAAQ==\n-----END PUBLIC KEY-----\n"
  }
]