From 7da857b4d8d927d0732afe565033eca357887098 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 10 Nov 2020 13:44:37 +0100 Subject: [PATCH 001/145] Move to Github Actions --- .gitattributes | 1 - .github/workflows/tests.yml | 45 +++++++++++++++++++++++++++++++++++++ .travis.yml | 32 -------------------------- README.md | 6 ++--- 4 files changed, 48 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml diff --git a/.gitattributes b/.gitattributes index 08a4644cd..b84dedf2d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,7 +7,6 @@ /.gitignore export-ignore /.scrutinizer.yml export-ignore /.styleci.yml export-ignore -/.travis.yml export-ignore /phpstan.neon export-ignore /phpunit.xml.dist export-ignore /CHANGELOG.md export-ignore diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..f980c283c --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,45 @@ +name: tests + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + php: [7.2, 7.3, 7.4] + stability: [prefer-lowest, prefer-stable] + + name: PHP ${{ matrix.php }} - ${{ matrix.stability }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip + coverage: none + + - name: Install dependencies + run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress + + - name: Run PHPStan + run: vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests + + - name: Execute tests + run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover + + - name: Code coverage + if: ${{ github.ref == 'master' }} + run: | + wget https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cf720e69e..000000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -language: php - -dist: bionic -sudo: false - -cache: - directories: - - vendor - -env: - - DEPENDENCIES="" - - DEPENDENCIES="--prefer-lowest --prefer-stable" - -php: - - 7.2 - - 7.3 - - 7.4 - -install: - - composer update --no-interaction --prefer-dist $DEPENDENCIES - -script: - - vendor/bin/phpunit --coverage-clover=coverage.clover - - vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests - -after_script: - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover coverage.clover - -branches: - only: - - master diff --git a/README.md b/README.md index b51e6f8f3..c76cf45ef 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Latest Version](http://img.shields.io/packagist/v/league/oauth2-server.svg?style=flat-square)](https://github.com/thephpleague/oauth2-server/releases) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) -[![Build Status](https://img.shields.io/travis/thephpleague/oauth2-server/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/oauth2-server) +[![Build Status](https://github.com/thephpleague/oauth2-server/workflows/tests/badge.svg)](https://github.com/thephpleague/oauth2-server/actions) [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/oauth2-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-server/code-structure) [![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-server) [![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-server.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-server) @@ -59,9 +59,9 @@ vendor/bin/phpunit vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests ``` -## Continous Integration +## Continuous Integration -We use [Travis CI](https://travis-ci.org/), [Scrutinizer](https://scrutinizer-ci.com/), and [StyleCI](https://styleci.io/) for continuous integration. Check out [our](https://github.com/thephpleague/oauth2-server/blob/master/.travis.yml) [configuration](https://github.com/thephpleague/oauth2-server/blob/master/.scrutinizer.yml) [files](https://github.com/thephpleague/oauth2-server/blob/master/.styleci.yml) if you'd like to know more. +We use [Github Actions](https://github.com/features/actions), [Scrutinizer](https://scrutinizer-ci.com/), and [StyleCI](https://styleci.io/) for continuous integration. Check out [our](https://github.com/thephpleague/oauth2-server/blob/master/.github/workflows/tests.yml) [configuration](https://github.com/thephpleague/oauth2-server/blob/master/.scrutinizer.yml) [files](https://github.com/thephpleague/oauth2-server/blob/master/.styleci.yml) if you'd like to know more. ## Community Integrations From c95a4f0d4f27c4faa711f576e650e85e0cb8c412 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 10 Nov 2020 14:01:07 +0100 Subject: [PATCH 002/145] PHP 8 Support --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 658cc6553..0bae02857 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": ">=7.2.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.3.1", + "lcobucci/jwt": "^3.4 || ^4.0", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "ext-json": "*" @@ -16,7 +16,7 @@ "phpunit/phpunit": "^8.5.4 || ^9.1.3", "laminas/laminas-diactoros": "^2.3.0", "phpstan/phpstan": "^0.11.19", - "phpstan/phpstan-phpunit": "^0.11.2", + "phpstan/phpstan-phpunit": "^0.12.6", "roave/security-advisories": "dev-master" }, "repositories": [ From fbbf05369383e482b0341280e3853b67ada3844a Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 12 Nov 2020 18:50:08 +0100 Subject: [PATCH 003/145] Use proper if in workflow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f980c283c..e9f2343bb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,7 +39,7 @@ jobs: run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover - name: Code coverage - if: ${{ github.ref == 'master' }} + if: github.ref == 'refs/heads/master' run: | wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover From a9f39f0280458a1f317d8e0ac56b41d44b3fdd2e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 18 Nov 2020 00:34:03 +0000 Subject: [PATCH 004/145] Update composer dependencies --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 0bae02857..7cc4e8bd6 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": ">=7.2.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.4 || ^4.0", + "lcobucci/jwt": "^3.4@dev || ^4.0@dev", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "ext-json": "*" @@ -15,8 +15,8 @@ "require-dev": { "phpunit/phpunit": "^8.5.4 || ^9.1.3", "laminas/laminas-diactoros": "^2.3.0", - "phpstan/phpstan": "^0.11.19", - "phpstan/phpstan-phpunit": "^0.12.6", + "phpstan/phpstan": "^0.12.56", + "phpstan/phpstan-phpunit": "^0.12.16", "roave/security-advisories": "dev-master" }, "repositories": [ From 3916ee7d92b46f824813c8cb8683b1c491f51a55 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 18 Nov 2020 00:55:31 +0000 Subject: [PATCH 005/145] Add version 8 to php versions to test --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e9f2343bb..d82fe6203 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: true matrix: - php: [7.2, 7.3, 7.4] + php: [7.2, 7.3, 7.4, 8.0] stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} From 531eb4d5b76d338df6bb47c899e7dc0f5a056e5a Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 23 Nov 2020 23:22:38 +0000 Subject: [PATCH 006/145] Update composer dependencies for examples --- examples/composer.json | 2 +- examples/composer.lock | 167 +++++++++++++++++++++++++++-------------- 2 files changed, 113 insertions(+), 56 deletions(-) diff --git a/examples/composer.json b/examples/composer.json index e1c44efc8..de2bd7162 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -4,7 +4,7 @@ }, "require-dev": { "league/event": "^2.2", - "lcobucci/jwt": "^3.3", + "lcobucci/jwt": "^3.4@dev || ^4.0@beta", "psr/http-message": "^1.0", "defuse/php-encryption": "^2.2", "laminas/laminas-diactoros": "^2.1.2" diff --git a/examples/composer.lock b/examples/composer.lock index 6de13b69f..d732e6f0b 100644 --- a/examples/composer.lock +++ b/examples/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5bcbefe6cdff10a268399b1d138647ea", + "content-hash": "afc9a862edd0bfe93a224a28b1abf75d", "packages": [ { "name": "nikic/fast-route", @@ -54,29 +54,29 @@ }, { "name": "pimple/pimple", - "version": "v3.2.3", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/silexphp/Pimple.git", - "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" + "reference": "e55d12f9d6a0e7f9c85992b73df1267f46279930" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", - "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/e55d12f9d6a0e7f9c85992b73df1267f46279930", + "reference": "e55d12f9d6a0e7f9c85992b73df1267f46279930", "shasum": "" }, "require": { - "php": ">=5.3.0", + "php": "^7.2.5", "psr/container": "^1.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3.2" + "symfony/phpunit-bridge": "^3.4|^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "3.3.x-dev" } }, "autoload": { @@ -95,12 +95,12 @@ } ], "description": "Pimple, a simple Dependency Injection Container", - "homepage": "http://pimple.sensiolabs.org", + "homepage": "https://pimple.symfony.com", "keywords": [ "container", "dependency injection" ], - "time": "2018-01-21T07:42:36+00:00" + "time": "2020-03-03T09:12:48+00:00" }, { "name": "psr/container", @@ -341,46 +341,50 @@ }, { "name": "laminas/laminas-diactoros", - "version": "2.2.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "064f0b20e832bb232d0311f915c7422fef1b1857" + "reference": "4ff7400c1c12e404144992ef43c8b733fd9ad516" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/064f0b20e832bb232d0311f915c7422fef1b1857", - "reference": "064f0b20e832bb232d0311f915c7422fef1b1857", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/4ff7400c1c12e404144992ef43c8b733fd9ad516", + "reference": "4ff7400c1c12e404144992ef43c8b733fd9ad516", "shasum": "" }, "require": { "laminas/laminas-zendframework-bridge": "^1.0", - "php": "^7.1", + "php": "^7.3 || ~8.0.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.0" }, + "conflict": { + "phpspec/prophecy": "<1.9.0" + }, "provide": { "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "replace": { - "zendframework/zend-diactoros": "self.version" + "zendframework/zend-diactoros": "^2.2.1" }, "require-dev": { "ext-curl": "*", "ext-dom": "*", + "ext-gd": "*", "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.5.0", + "http-interop/http-factory-tests": "^0.8.0", "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "dev-master", - "phpunit/phpunit": "^7.0.2" + "php-http/psr7-integration-tests": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.1.x-dev", - "dev-develop": "2.2.x-dev", - "dev-release-1.8": "1.8.x-dev" + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" } }, "autoload": { @@ -416,37 +420,34 @@ "http", "laminas", "psr", + "psr-17", "psr-7" ], - "time": "2019-12-31T16:41:56+00:00" + "time": "2020-11-18T18:39:28+00:00" }, { "name": "laminas/laminas-zendframework-bridge", - "version": "1.0.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/laminas/laminas-zendframework-bridge.git", - "reference": "32d7095e436a31b8d98e485a5c63d70df74915a8" + "reference": "6ede70583e101030bcace4dcddd648f760ddf642" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/32d7095e436a31b8d98e485a5c63d70df74915a8", - "reference": "32d7095e436a31b8d98e485a5c63d70df74915a8", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/6ede70583e101030bcace4dcddd648f760ddf642", + "reference": "6ede70583e101030bcace4dcddd648f760ddf642", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^5.6 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1", + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1 || ^9.3", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev", - "dev-develop": "1.1.x-dev" - }, "laminas": { "module": "Laminas\\ZendFrameworkBridge" } @@ -470,38 +471,92 @@ "laminas", "zf" ], - "time": "2019-12-31T15:24:03+00:00" + "time": "2020-09-14T14:23:00+00:00" + }, + { + "name": "lcobucci/clock", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/clock.git", + "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "infection/infection": "^0.17", + "lcobucci/coding-standard": "^6.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/php-code-coverage": "9.1.4", + "phpunit/phpunit": "9.3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "description": "Yet another clock abstraction", + "time": "2020-08-27T18:56:02+00:00" }, { "name": "lcobucci/jwt", - "version": "3.3.1", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18" + "reference": "144de5ad99590cac0139a688c26bbf42539aeed9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", - "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/144de5ad99590cac0139a688c26bbf42539aeed9", + "reference": "144de5ad99590cac0139a688c26bbf42539aeed9", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-openssl": "*", - "php": "^5.6 || ^7.0" + "lcobucci/clock": "^2.0", + "php": "^7.4 || ^8.0" }, "require-dev": { - "mikey179/vfsstream": "~1.5", - "phpmd/phpmd": "~2.2", - "phpunit/php-invoker": "~1.1", - "phpunit/phpunit": "^5.7 || ^7.3", - "squizlabs/php_codesniffer": "~2.3" + "infection/infection": "^0.20", + "lcobucci/coding-standard": "^6.0", + "mikey179/vfsstream": "^1.6", + "phpbench/phpbench": "^0.17", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/php-invoker": "^3.1", + "phpunit/phpunit": "^9.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -515,7 +570,7 @@ ], "authors": [ { - "name": "Luís Otávio Cobucci Oblonczyk", + "name": "Luís Cobucci", "email": "lcobucci@gmail.com", "role": "Developer" } @@ -525,7 +580,7 @@ "JWS", "jwt" ], - "time": "2019-05-24T18:30:49+00:00" + "time": "2020-11-23T04:40:16+00:00" }, { "name": "league/event", @@ -579,20 +634,20 @@ }, { "name": "paragonie/random_compat", - "version": "v9.99.99", + "version": "v9.99.100", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", "shasum": "" }, "require": { - "php": "^7" + "php": ">= 7" }, "require-dev": { "phpunit/phpunit": "4.*|5.*", @@ -620,7 +675,7 @@ "pseudorandom", "random" ], - "time": "2018-07-02T15:55:56+00:00" + "time": "2020-10-15T08:29:30+00:00" }, { "name": "psr/http-factory", @@ -677,7 +732,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "lcobucci/jwt": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": [], From f140ce6ef1f27e0f1a8c3b21a6f7213ef858e7ac Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 23 Nov 2020 23:24:03 +0000 Subject: [PATCH 007/145] Update composer dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7cc4e8bd6..238aad49f 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": ">=7.2.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.4@dev || ^4.0@dev", + "lcobucci/jwt": "^3.4@dev || ^4.0@beta", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "ext-json": "*" From f8f832004cb86943df7cfb738e3392c939161da6 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 23 Nov 2020 23:25:39 +0000 Subject: [PATCH 008/145] Update to use version 4 API of JWT package --- .../BearerTokenValidator.php | 82 ++++++++++++------- src/Entities/Traits/AccessTokenTrait.php | 39 +++++++-- 2 files changed, 84 insertions(+), 37 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 213ecf893..46797b778 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -9,17 +9,25 @@ namespace League\OAuth2\Server\AuthorizationValidators; -use BadMethodCallException; -use InvalidArgumentException; -use Lcobucci\JWT\Parser; +use DateTimeImmutable; +use DateTimeZone; +use Lcobucci\Clock\FrozenClock; +use Lcobucci\Clock\SystemClock; +use Lcobucci\JWT\Configuration; +use Lcobucci\JWT\Encoding\CannotDecodeContent; +use Lcobucci\JWT\Signer\Key\InMemory; +use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; -use Lcobucci\JWT\ValidationData; +use Lcobucci\JWT\Token\InvalidTokenStructure; +use Lcobucci\JWT\Token\UnsupportedHeaderFound; +use Lcobucci\JWT\Validation\Constraint\SignedWith; +use Lcobucci\JWT\Validation\Constraint\ValidAt; +use Lcobucci\JWT\Validation\RequiredConstraintsViolated; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use Psr\Http\Message\ServerRequestInterface; -use RuntimeException; class BearerTokenValidator implements AuthorizationValidatorInterface { @@ -35,6 +43,11 @@ class BearerTokenValidator implements AuthorizationValidatorInterface */ protected $publicKey; + /** + * @var Configuration + */ + private $jwtConfiguration; + /** * @param AccessTokenRepositoryInterface $accessTokenRepository */ @@ -51,6 +64,21 @@ public function __construct(AccessTokenRepositoryInterface $accessTokenRepositor public function setPublicKey(CryptKey $key) { $this->publicKey = $key; + + $this->initJwtConfiguration(); + } + + private function initJwtConfiguration() + { + $this->jwtConfiguration = Configuration::forSymmetricSigner( + new Sha256(), + InMemory::empty() + ); + + $this->jwtConfiguration->setValidationConstraints( + new ValidAt(new SystemClock(new DateTimeZone(date_default_timezone_get()))), + new SignedWith(new Sha256(), LocalFileReference::file($this->publicKey->getKeyPath())) + ); } /** @@ -67,40 +95,38 @@ public function validateAuthorization(ServerRequestInterface $request) try { // Attempt to parse and validate the JWT - $token = (new Parser())->parse($jwt); - try { - if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) { - throw OAuthServerException::accessDenied('Access token could not be verified'); - } - } catch (BadMethodCallException $exception) { - throw OAuthServerException::accessDenied('Access token is not signed', null, $exception); - } + $token = $this->jwtConfiguration->parser()->parse($jwt); - // Ensure access token hasn't expired - $data = new ValidationData(); - $data->setCurrentTime(\time()); + /*$this->jwtConfiguration->setValidationConstraints( + new SignedWith( + new Sha256(), + LocalFileReference::file($this->publicKey->getKeyPath()) + ) + );*/ - if ($token->validate($data) === false) { - throw OAuthServerException::accessDenied('Access token is invalid'); + $constraints = $this->jwtConfiguration->validationConstraints(); + + try { + $this->jwtConfiguration->validator()->assert($token, ...$constraints); + } catch (RequiredConstraintsViolated $exception) { + throw OAuthServerException::accessDenied('Access token could not be verified'); } - } catch (InvalidArgumentException $exception) { - // JWT couldn't be parsed so return the request as is + } catch (CannotDecodeContent | InvalidTokenStructure | UnsupportedHeaderFound $exception) { throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception); - } catch (RuntimeException $exception) { - // JWT couldn't be parsed so return the request as is - throw OAuthServerException::accessDenied('Error while decoding to JSON', null, $exception); } + $claims = $token->claims(); + // Check if token has been revoked - if ($this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'))) { + if ($this->accessTokenRepository->isAccessTokenRevoked($claims->get('jti'))) { throw OAuthServerException::accessDenied('Access token has been revoked'); } // Return the request with additional attributes return $request - ->withAttribute('oauth_access_token_id', $token->getClaim('jti')) - ->withAttribute('oauth_client_id', $token->getClaim('aud')) - ->withAttribute('oauth_user_id', $token->getClaim('sub')) - ->withAttribute('oauth_scopes', $token->getClaim('scopes')); + ->withAttribute('oauth_access_token_id', $claims->get('jti')) + ->withAttribute('oauth_client_id', $claims->get('aud')) + ->withAttribute('oauth_user_id', $claims->get('sub')) + ->withAttribute('oauth_scopes', $claims->get('scopes')); } } diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 48e3d1ac4..285e93909 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -10,8 +10,9 @@ namespace League\OAuth2\Server\Entities\Traits; use DateTimeImmutable; -use Lcobucci\JWT\Builder; -use Lcobucci\JWT\Signer\Key; +use Lcobucci\JWT\Configuration; +use Lcobucci\JWT\Signer\Key\InMemory; +use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token; use League\OAuth2\Server\CryptKey; @@ -25,6 +26,11 @@ trait AccessTokenTrait */ private $privateKey; + /** + * @var Configuration + */ + private $jwtConfiguration; + /** * Set the private key used to encrypt this access token. */ @@ -33,6 +39,19 @@ public function setPrivateKey(CryptKey $privateKey) $this->privateKey = $privateKey; } + public function initJwtConfiguration() + { + $privateKeyPassPhrase = $this->privateKey->getPassPhrase(); + + $verificationKey = empty($privateKeyPassPhrase) ? InMemory::empty() : $privateKeyPassPhrase; + + $this->jwtConfiguration = Configuration::forAsymmetricSigner( + new Sha256(), + LocalFileReference::file($this->privateKey->getKeyPath()), + $verificationKey + ); + } + /** * Generate a JWT from the access token * @@ -40,17 +59,19 @@ public function setPrivateKey(CryptKey $privateKey) * * @return Token */ - private function convertToJWT(CryptKey $privateKey) + private function convertToJWT() { - return (new Builder()) + $this->initJwtConfiguration(); + + return $this->jwtConfiguration->builder() ->permittedFor($this->getClient()->getIdentifier()) ->identifiedBy($this->getIdentifier()) - ->issuedAt(\time()) - ->canOnlyBeUsedAfter(\time()) - ->expiresAt($this->getExpiryDateTime()->getTimestamp()) + ->issuedAt(new DateTimeImmutable()) + ->canOnlyBeUsedAfter(new DateTimeImmutable()) + ->expiresAt($this->getExpiryDateTime()) ->relatedTo((string) $this->getUserIdentifier()) ->withClaim('scopes', $this->getScopes()) - ->getToken(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase())); + ->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey()); } /** @@ -58,7 +79,7 @@ private function convertToJWT(CryptKey $privateKey) */ public function __toString() { - return (string) $this->convertToJWT($this->privateKey); + return $this->convertToJWT()->toString(); } /** From 32d7d7a283ec368adc74d6f0318c2fcf8fc0cfaa Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 23 Nov 2020 23:26:25 +0000 Subject: [PATCH 009/145] Update tests to use version 4 of JWT package --- .../BearerTokenValidatorTest.php | 67 ++++++++++++++++--- tests/Grant/AuthCodeGrantTest.php | 25 +++++-- tests/Grant/ImplicitGrantTest.php | 13 +++- .../ResponseTypes/BearerResponseTypeTest.php | 4 +- 4 files changed, 92 insertions(+), 17 deletions(-) diff --git a/tests/AuthorizationValidators/BearerTokenValidatorTest.php b/tests/AuthorizationValidators/BearerTokenValidatorTest.php index c95c60531..e1e491a5a 100644 --- a/tests/AuthorizationValidators/BearerTokenValidatorTest.php +++ b/tests/AuthorizationValidators/BearerTokenValidatorTest.php @@ -2,33 +2,82 @@ namespace LeagueTests\AuthorizationValidators; +use DateInterval; +use DateTimeZone; +use Exception; +use DateTimeImmutable; use Laminas\Diactoros\ServerRequest; -use Lcobucci\JWT\Builder; +use Lcobucci\Clock\FrozenClock; +use Lcobucci\Clock\SystemClock; +use Lcobucci\JWT\Configuration; +use Lcobucci\JWT\Signer\Key\InMemory; +use Lcobucci\JWT\Signer\Key\LocalFileReference; +use Lcobucci\JWT\Signer\Rsa\Sha256; +use Lcobucci\JWT\Validation\Constraint\SignedWith; +use Lcobucci\JWT\Validation\Constraint\ValidAt; use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator; use League\OAuth2\Server\CryptKey; +use League\OAuth2\Server\Entities\AccessTokenEntityInterface; +use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use PHPUnit\Framework\TestCase; +use ReflectionClass; class BearerTokenValidatorTest extends TestCase { - public function testThrowExceptionWhenAccessTokenIsNotSigned() + + private $jwtConfiguration; + + public function testBearerTokenValidatorAcceptsValidToken() + { + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + + $bearerTokenValidator = new BearerTokenValidator($accessTokenRepositoryMock); + $bearerTokenValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); + + $bearerTokenValidatorReflection = new ReflectionClass(BearerTokenValidator::class); + $jwtConfiguration = $bearerTokenValidatorReflection->getProperty('jwtConfiguration'); + $jwtConfiguration->setAccessible(true); + + $validJwt = $jwtConfiguration->getValue($bearerTokenValidator)->builder() + ->permittedFor('client-id') + ->identifiedBy('token-id') + ->issuedAt(new DateTimeImmutable()) + ->canOnlyBeUsedAfter(new DateTimeImmutable()) + ->expiresAt((new DateTimeImmutable())->add(new DateInterval('PT1H'))) + ->relatedTo('user-id') + ->withClaim('scopes', 'scope1 scope2 scope3 scope4') + ->getToken(new Sha256(), LocalFileReference::file( __DIR__ . '/../Stubs/private.key')); + + $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $validJwt->toString())); + + $response = $bearerTokenValidator->validateAuthorization($request); + + $this->assertArrayHasKey('authorization', $response->getHeaders()); + } + + public function testBearerTokenValidatorRejectsExpiredToken() { $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $bearerTokenValidator = new BearerTokenValidator($accessTokenRepositoryMock); $bearerTokenValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); - $unsignedJwt = (new Builder()) + $bearerTokenValidatorReflection = new ReflectionClass(BearerTokenValidator::class); + $jwtConfiguration = $bearerTokenValidatorReflection->getProperty('jwtConfiguration'); + $jwtConfiguration->setAccessible(true); + + $expiredJwt = $jwtConfiguration->getValue($bearerTokenValidator)->builder() ->permittedFor('client-id') - ->identifiedBy('token-id', true) - ->issuedAt(\time()) - ->canOnlyBeUsedAfter(\time()) - ->expiresAt(\time()) + ->identifiedBy('token-id') + ->issuedAt(new DateTimeImmutable()) + ->canOnlyBeUsedAfter(new DateTimeImmutable()) + ->expiresAt((new DateTimeImmutable())->sub(new DateInterval('PT1H'))) ->relatedTo('user-id') ->withClaim('scopes', 'scope1 scope2 scope3 scope4') - ->getToken(); + ->getToken(new Sha256(), LocalFileReference::file( __DIR__ . '/../Stubs/private.key')); - $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $unsignedJwt)); + $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $expiredJwt->toString())); $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); $this->expectExceptionCode(9); diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 28527aecf..a080da317 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -1715,8 +1715,16 @@ public function testAuthCodeRepositoryUniqueConstraintCheck() $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); - $authCodeRepository->expects($this->at(0))->method('persistNewAuthCode')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); - $authCodeRepository->expects($this->at(1))->method('persistNewAuthCode'); + $matcher = $this->exactly(2); + + $authCodeRepository + ->expects($matcher) + ->method('persistNewAuthCode') + ->willReturnCallback(function () use ($matcher) { + if ($matcher->getInvocationCount() === 1) { + throw UniqueTokenIdentifierConstraintViolationException::create(); + } + }); $grant = new AuthCodeGrant( $authCodeRepository, @@ -1797,8 +1805,17 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck() $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); - $refreshTokenRepositoryMock->expects($this->at(0))->method('persistNewRefreshToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); - $refreshTokenRepositoryMock->expects($this->at(1))->method('persistNewRefreshToken'); + + $matcher = $this->exactly(2); + + $refreshTokenRepositoryMock + ->expects($matcher) + ->method('persistNewRefreshToken') + ->willReturnCallback(function () use ($matcher) { + if ($matcher->getInvocationCount() === 1) { + throw UniqueTokenIdentifierConstraintViolationException::create(); + } + }); $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 99fa3f57c..546450384 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -276,8 +276,17 @@ public function testAccessTokenRepositoryUniqueConstraintCheck() /** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */ $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); - $accessTokenRepositoryMock->expects($this->at(0))->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); - $accessTokenRepositoryMock->expects($this->at(1))->method('persistNewAccessToken')->willReturnSelf(); + + $matcher = $this->exactly(2); + + $accessTokenRepositoryMock + ->expects($matcher) + ->method('persistNewAccessToken') + ->willReturnCallback(function () use ($matcher) { + if ($matcher->getInvocationCount() === 1) { + throw UniqueTokenIdentifierConstraintViolationException::create(); + } + }); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index 6dc24ff3b..a02b9a613 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -147,7 +147,7 @@ public function testDetermineAccessTokenInHeaderValidToken() $request = $authorizationValidator->validateAuthorization($request); $this->assertEquals('abcdef', $request->getAttribute('oauth_access_token_id')); - $this->assertEquals('clientName', $request->getAttribute('oauth_client_id')); + $this->assertContains('clientName', $request->getAttribute('oauth_client_id')); $this->assertEquals('123', $request->getAttribute('oauth_user_id')); $this->assertEquals([], $request->getAttribute('oauth_scopes')); } @@ -281,7 +281,7 @@ public function testDetermineMissingBearerInHeader() $authorizationValidator->validateAuthorization($request); } catch (OAuthServerException $e) { $this->assertEquals( - 'Error while decoding to JSON', + 'Error while decoding from JSON', $e->getHint() ); } From 6b6878b15e006d1f0d8b5b37c9877b8e4ff3f0ca Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 23 Nov 2020 23:28:01 +0000 Subject: [PATCH 010/145] Temp removal of fail fast --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d82fe6203..4115fa99f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: - fail-fast: true + fail-fast: false matrix: php: [7.2, 7.3, 7.4, 8.0] stability: [prefer-lowest, prefer-stable] From ebd78d3fd5ad9fb929bbee18ff5d17b004193280 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 23 Nov 2020 23:31:20 +0000 Subject: [PATCH 011/145] Temp remove phpstan checks --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4115fa99f..1365facbd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,8 +32,8 @@ jobs: - name: Install dependencies run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress - - name: Run PHPStan - run: vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests + #- name: Run PHPStan + # run: vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests - name: Execute tests run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover From 109795b2a6c3f5fd9dccaeda39abf739e6ea7940 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 00:10:04 +0000 Subject: [PATCH 012/145] Fix config file for phpunit --- phpunit.xml.dist | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 92564086b..a3e34084d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,19 +6,9 @@ ./tests/ - - + + src - - src/ResponseTypes/DefaultTemplates - src/TemplateRenderer - - - - - - - + + From 9a581739f74148eb5c2ea1c96fee4c03a964c90e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 00:14:28 +0000 Subject: [PATCH 013/145] Change to use plainText instead of empty --- src/AuthorizationValidators/BearerTokenValidator.php | 4 +--- src/Entities/Traits/AccessTokenTrait.php | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 46797b778..2b772d149 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -9,9 +9,7 @@ namespace League\OAuth2\Server\AuthorizationValidators; -use DateTimeImmutable; use DateTimeZone; -use Lcobucci\Clock\FrozenClock; use Lcobucci\Clock\SystemClock; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Encoding\CannotDecodeContent; @@ -72,7 +70,7 @@ private function initJwtConfiguration() { $this->jwtConfiguration = Configuration::forSymmetricSigner( new Sha256(), - InMemory::empty() + InMemory::plainText('') ); $this->jwtConfiguration->setValidationConstraints( diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 285e93909..fbb84eec8 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -43,7 +43,7 @@ public function initJwtConfiguration() { $privateKeyPassPhrase = $this->privateKey->getPassPhrase(); - $verificationKey = empty($privateKeyPassPhrase) ? InMemory::empty() : $privateKeyPassPhrase; + $verificationKey = empty($privateKeyPassPhrase) ? InMemory::plainText('') : $privateKeyPassPhrase; $this->jwtConfiguration = Configuration::forAsymmetricSigner( new Sha256(), From 7efa91cf486b7e9d6851818290654ad0b7356439 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 16:53:19 +0000 Subject: [PATCH 014/145] Fix backwards compatibility for aud --- .../BearerTokenValidator.php | 21 ++++++++++++------- .../ResponseTypes/BearerResponseTypeTest.php | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 2b772d149..4f6d93e7b 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -95,13 +95,6 @@ public function validateAuthorization(ServerRequestInterface $request) // Attempt to parse and validate the JWT $token = $this->jwtConfiguration->parser()->parse($jwt); - /*$this->jwtConfiguration->setValidationConstraints( - new SignedWith( - new Sha256(), - LocalFileReference::file($this->publicKey->getKeyPath()) - ) - );*/ - $constraints = $this->jwtConfiguration->validationConstraints(); try { @@ -123,8 +116,20 @@ public function validateAuthorization(ServerRequestInterface $request) // Return the request with additional attributes return $request ->withAttribute('oauth_access_token_id', $claims->get('jti')) - ->withAttribute('oauth_client_id', $claims->get('aud')) + ->withAttribute('oauth_client_id', $this->convertSingleRecordAudToString($claims->get('aud'))) ->withAttribute('oauth_user_id', $claims->get('sub')) ->withAttribute('oauth_scopes', $claims->get('scopes')); } + + /** + * Convert single record arrays into strings to ensure backwards compatibility between v4 and v3.x of lcobucci/jwt + * + * @param $aud + * + * @return array|string + */ + private function convertSingleRecordAudToString($aud) + { + return count($aud) === 1 ? $aud[0] : $aud; + } } diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index a02b9a613..a57820d00 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -147,7 +147,7 @@ public function testDetermineAccessTokenInHeaderValidToken() $request = $authorizationValidator->validateAuthorization($request); $this->assertEquals('abcdef', $request->getAttribute('oauth_access_token_id')); - $this->assertContains('clientName', $request->getAttribute('oauth_client_id')); + $this->assertEquals('clientName', $request->getAttribute('oauth_client_id')); $this->assertEquals('123', $request->getAttribute('oauth_user_id')); $this->assertEquals([], $request->getAttribute('oauth_scopes')); } From ad2a1be9dcc3b7c7ca2a4fa89eaabdc16e2a19ff Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 16:59:48 +0000 Subject: [PATCH 015/145] Drop support for PHP 7.2 --- composer.json | 4 ++-- src/AuthorizationValidators/BearerTokenValidator.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 238aad49f..d2ee11fce 100644 --- a/composer.json +++ b/composer.json @@ -4,10 +4,10 @@ "homepage": "https://oauth2.thephpleague.com/", "license": "MIT", "require": { - "php": ">=7.2.0", + "php": "^7.3 || ^8.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.4@dev || ^4.0@beta", + "lcobucci/jwt": "^3.4@dev || ^4.0@dev", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "ext-json": "*" diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 4f6d93e7b..5c0e7e00d 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -130,6 +130,6 @@ public function validateAuthorization(ServerRequestInterface $request) */ private function convertSingleRecordAudToString($aud) { - return count($aud) === 1 ? $aud[0] : $aud; + return is_countable($aud) && count($aud) === 1 ? $aud[0] : $aud; } } From e85da747d5cc14983fb361959ec0d3f99b61729e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 17:02:26 +0000 Subject: [PATCH 016/145] Fix style CI issues --- src/AuthorizationValidators/BearerTokenValidator.php | 4 ++-- .../BearerTokenValidatorTest.php | 10 ---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 5c0e7e00d..a14cec494 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -74,7 +74,7 @@ private function initJwtConfiguration() ); $this->jwtConfiguration->setValidationConstraints( - new ValidAt(new SystemClock(new DateTimeZone(date_default_timezone_get()))), + new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), new SignedWith(new Sha256(), LocalFileReference::file($this->publicKey->getKeyPath())) ); } @@ -130,6 +130,6 @@ public function validateAuthorization(ServerRequestInterface $request) */ private function convertSingleRecordAudToString($aud) { - return is_countable($aud) && count($aud) === 1 ? $aud[0] : $aud; + return \is_countable($aud) && \count($aud) === 1 ? $aud[0] : $aud; } } diff --git a/tests/AuthorizationValidators/BearerTokenValidatorTest.php b/tests/AuthorizationValidators/BearerTokenValidatorTest.php index e1e491a5a..0fafcbc5d 100644 --- a/tests/AuthorizationValidators/BearerTokenValidatorTest.php +++ b/tests/AuthorizationValidators/BearerTokenValidatorTest.php @@ -3,22 +3,12 @@ namespace LeagueTests\AuthorizationValidators; use DateInterval; -use DateTimeZone; -use Exception; use DateTimeImmutable; use Laminas\Diactoros\ServerRequest; -use Lcobucci\Clock\FrozenClock; -use Lcobucci\Clock\SystemClock; -use Lcobucci\JWT\Configuration; -use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; -use Lcobucci\JWT\Validation\Constraint\SignedWith; -use Lcobucci\JWT\Validation\Constraint\ValidAt; use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator; use League\OAuth2\Server\CryptKey; -use League\OAuth2\Server\Entities\AccessTokenEntityInterface; -use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use PHPUnit\Framework\TestCase; use ReflectionClass; From 5dd01888056e9cbf5ebfa83427afc2d6b370eecc Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 17:05:49 +0000 Subject: [PATCH 017/145] Drop support for PHP 7.2 --- .github/workflows/tests.yml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1365facbd..18037949c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php: [7.2, 7.3, 7.4, 8.0] + php: [7.3, 7.4, 8.0] stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} diff --git a/README.md b/README.md index c76cf45ef..6c886faaa 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,11 @@ This library was created by Alex Bilbie. Find him on Twitter at [@alexbilbie](ht ## Requirements -The following versions of PHP are supported: +The latest version of this package supports the following versions of PHP: -* PHP 7.2 * PHP 7.3 * PHP 7.4 +* PHP 8.0 The `openssl` and `json` extensions are also required. From b6266f1e0576720d1e342fa2fa87037e8056df96 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 17:07:13 +0000 Subject: [PATCH 018/145] StyleCI fixes --- tests/AuthorizationValidators/BearerTokenValidatorTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/AuthorizationValidators/BearerTokenValidatorTest.php b/tests/AuthorizationValidators/BearerTokenValidatorTest.php index 0fafcbc5d..9cb8942e9 100644 --- a/tests/AuthorizationValidators/BearerTokenValidatorTest.php +++ b/tests/AuthorizationValidators/BearerTokenValidatorTest.php @@ -37,7 +37,7 @@ public function testBearerTokenValidatorAcceptsValidToken() ->expiresAt((new DateTimeImmutable())->add(new DateInterval('PT1H'))) ->relatedTo('user-id') ->withClaim('scopes', 'scope1 scope2 scope3 scope4') - ->getToken(new Sha256(), LocalFileReference::file( __DIR__ . '/../Stubs/private.key')); + ->getToken(new Sha256(), LocalFileReference::file(__DIR__ . '/../Stubs/private.key')); $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $validJwt->toString())); @@ -65,7 +65,7 @@ public function testBearerTokenValidatorRejectsExpiredToken() ->expiresAt((new DateTimeImmutable())->sub(new DateInterval('PT1H'))) ->relatedTo('user-id') ->withClaim('scopes', 'scope1 scope2 scope3 scope4') - ->getToken(new Sha256(), LocalFileReference::file( __DIR__ . '/../Stubs/private.key')); + ->getToken(new Sha256(), LocalFileReference::file(__DIR__ . '/../Stubs/private.key')); $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $expiredJwt->toString())); From 309cff6f2c01f57c3aca2bd9e66b2498cc088d89 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 17:09:22 +0000 Subject: [PATCH 019/145] StyleCI fixes --- tests/AuthorizationValidators/BearerTokenValidatorTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/AuthorizationValidators/BearerTokenValidatorTest.php b/tests/AuthorizationValidators/BearerTokenValidatorTest.php index 9cb8942e9..705368e4c 100644 --- a/tests/AuthorizationValidators/BearerTokenValidatorTest.php +++ b/tests/AuthorizationValidators/BearerTokenValidatorTest.php @@ -15,9 +15,6 @@ class BearerTokenValidatorTest extends TestCase { - - private $jwtConfiguration; - public function testBearerTokenValidatorAcceptsValidToken() { $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); From a29ccf7c03db22b22d787f0f42f68cd9d1227bd4 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 17:32:14 +0000 Subject: [PATCH 020/145] Set minimum stability to dev temporarily --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d2ee11fce..56d98e5da 100644 --- a/composer.json +++ b/composer.json @@ -68,5 +68,6 @@ "psr-4": { "LeagueTests\\": "tests/" } - } + }, + "minimum-stability": "dev" } From 89091b74185f5947e95ecd41412f60d98dfdabb0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 17:39:29 +0000 Subject: [PATCH 021/145] remove minimum-stabiity --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 56d98e5da..d2ee11fce 100644 --- a/composer.json +++ b/composer.json @@ -68,6 +68,5 @@ "psr-4": { "LeagueTests\\": "tests/" } - }, - "minimum-stability": "dev" + } } From 4764e40611fe8355764f50b51cf669daea0282ea Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 20:48:00 +0000 Subject: [PATCH 022/145] Add BC check --- .github/workflows/backwards-compatibility.yml | 21 +++++++++++++++++++ composer.json | 3 ++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/backwards-compatibility.yml diff --git a/.github/workflows/backwards-compatibility.yml b/.github/workflows/backwards-compatibility.yml new file mode 100644 index 000000000..e15e132b5 --- /dev/null +++ b/.github/workflows/backwards-compatibility.yml @@ -0,0 +1,21 @@ +name: "Backwards compatibility check" + +on: + pull_request: + +jobs: + bc-check: + name: "Backwards compatibility check" + + runs-on: "ubuntu-latest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 0 + + - name: "Backwards Compatibility Check" + uses: docker://nyholm/roave-bc-check-ga + with: + args: --from=${{ github.event.pull_request.base.sha }} \ No newline at end of file diff --git a/composer.json b/composer.json index d2ee11fce..a1074bff7 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "laminas/laminas-diactoros": "^2.3.0", "phpstan/phpstan": "^0.12.56", "phpstan/phpstan-phpunit": "^0.12.16", - "roave/security-advisories": "dev-master" + "roave/security-advisories": "dev-master", + "roave/backward-compatibility-check": "^5.0" }, "repositories": [ { From cb32154346206b637d82eccbe239667937c8950e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 20:51:38 +0000 Subject: [PATCH 023/145] Update dependencies for BC check --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a1074bff7..b8c0f7304 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "phpstan/phpstan": "^0.12.56", "phpstan/phpstan-phpunit": "^0.12.16", "roave/security-advisories": "dev-master", - "roave/backward-compatibility-check": "^5.0" + "roave/backward-compatibility-check": "^4.4 || ^5.0" }, "repositories": [ { From d600718860ced495b34237cbfd8095722372d8ae Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 21:25:02 +0000 Subject: [PATCH 024/145] Make OAuthServerException constructor final --- src/AuthorizationValidators/BearerTokenValidator.php | 2 +- src/Entities/Traits/AccessTokenTrait.php | 2 -- src/Exception/OAuthServerException.php | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index a14cec494..2469a5cf7 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -124,7 +124,7 @@ public function validateAuthorization(ServerRequestInterface $request) /** * Convert single record arrays into strings to ensure backwards compatibility between v4 and v3.x of lcobucci/jwt * - * @param $aud + * @param mixed $aud * * @return array|string */ diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index fbb84eec8..bf8069d21 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -55,8 +55,6 @@ public function initJwtConfiguration() /** * Generate a JWT from the access token * - * @param CryptKey $privateKey - * * @return Token */ private function convertToJWT() diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 31dcf2ea5..e55bf1548 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -57,7 +57,7 @@ class OAuthServerException extends Exception * @param null|string $redirectUri A HTTP URI to redirect the user back to * @param Throwable $previous Previous exception */ - public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null) + final public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->httpStatusCode = $httpStatusCode; From 9abc6b82be56195a49df339bda8e45ebb9d85539 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 21:27:52 +0000 Subject: [PATCH 025/145] Revert final for constructor --- src/Exception/OAuthServerException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index e55bf1548..31dcf2ea5 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -57,7 +57,7 @@ class OAuthServerException extends Exception * @param null|string $redirectUri A HTTP URI to redirect the user back to * @param Throwable $previous Previous exception */ - final public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null) + public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->httpStatusCode = $httpStatusCode; From 89084a5abc12ab9864b1638f8201680911abeea0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 21:30:04 +0000 Subject: [PATCH 026/145] Remove PHPStan --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 6c886faaa..0c8869c52 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/oauth2-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-server/code-structure) [![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-server) [![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-server.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-server) -[![PHPStan](https://img.shields.io/badge/PHPStan-enabled-brightgreen.svg?style=flat-square)](https://github.com/phpstan/phpstan) `league/oauth2-server` is a standards compliant implementation of an [OAuth 2.0](https://tools.ietf.org/html/rfc6749) authorization server written in PHP which makes working with OAuth 2.0 trivial. You can easily configure an OAuth 2.0 server to protect your API with access tokens, or allow clients to request new access tokens and refresh them. @@ -52,11 +51,10 @@ You can contribute to the documentation in the [gh-pages branch](https://github. ## Testing -The library uses [PHPUnit](https://phpunit.de/) for unit tests and [PHPStan](https://github.com/phpstan/phpstan) for static analysis of the code. +The library uses [PHPUnit](https://phpunit.de/) for unit tests. ``` vendor/bin/phpunit -vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests ``` ## Continuous Integration From de7de426a5b70596735d4607504e31e20a4484e4 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 21:31:22 +0000 Subject: [PATCH 027/145] Remove commented phpstan from actions --- .github/workflows/tests.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 18037949c..14df99517 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,9 +32,6 @@ jobs: - name: Install dependencies run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress - #- name: Run PHPStan - # run: vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests - - name: Execute tests run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover From 85b8efb39d74ddb22e0f015e71c2e99fd954f549 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 24 Nov 2020 21:53:39 +0000 Subject: [PATCH 028/145] Remove backwards compatibility check --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index b8c0f7304..d2ee11fce 100644 --- a/composer.json +++ b/composer.json @@ -17,8 +17,7 @@ "laminas/laminas-diactoros": "^2.3.0", "phpstan/phpstan": "^0.12.56", "phpstan/phpstan-phpunit": "^0.12.16", - "roave/security-advisories": "dev-master", - "roave/backward-compatibility-check": "^4.4 || ^5.0" + "roave/security-advisories": "dev-master" }, "repositories": [ { From 3fcfe2c405c69ef376e3a63f0faf16b9f7f74b8d Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Nov 2020 11:05:29 +0000 Subject: [PATCH 029/145] Update dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d2ee11fce..cd06d578c 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": "^7.3 || ^8.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.4@dev || ^4.0@dev", + "lcobucci/jwt": "^3.4 || ^4.0", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "ext-json": "*" From 51cf94e6b3fa3137ece90031f5cf7e1e7aba5bf2 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Nov 2020 11:07:23 +0000 Subject: [PATCH 030/145] Remove backwards compat test --- .github/workflows/backwards-compatibility.yml | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/workflows/backwards-compatibility.yml diff --git a/.github/workflows/backwards-compatibility.yml b/.github/workflows/backwards-compatibility.yml deleted file mode 100644 index e15e132b5..000000000 --- a/.github/workflows/backwards-compatibility.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: "Backwards compatibility check" - -on: - pull_request: - -jobs: - bc-check: - name: "Backwards compatibility check" - - runs-on: "ubuntu-latest" - - steps: - - name: "Checkout" - uses: "actions/checkout@v2" - with: - fetch-depth: 0 - - - name: "Backwards Compatibility Check" - uses: docker://nyholm/roave-bc-check-ga - with: - args: --from=${{ github.event.pull_request.base.sha }} \ No newline at end of file From c968b00bf1a0f67ec215d90cb5257be740ae8e4e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Nov 2020 11:12:16 +0000 Subject: [PATCH 031/145] Reinstate BC checker --- .github/workflows/backwards-compatibility.yml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/backwards-compatibility.yml diff --git a/.github/workflows/backwards-compatibility.yml b/.github/workflows/backwards-compatibility.yml new file mode 100644 index 000000000..e15e132b5 --- /dev/null +++ b/.github/workflows/backwards-compatibility.yml @@ -0,0 +1,21 @@ +name: "Backwards compatibility check" + +on: + pull_request: + +jobs: + bc-check: + name: "Backwards compatibility check" + + runs-on: "ubuntu-latest" + + steps: + - name: "Checkout" + uses: "actions/checkout@v2" + with: + fetch-depth: 0 + + - name: "Backwards Compatibility Check" + uses: docker://nyholm/roave-bc-check-ga + with: + args: --from=${{ github.event.pull_request.base.sha }} \ No newline at end of file From d50709c4234b43793b5bf5214ed835c821c48131 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Nov 2020 11:17:23 +0000 Subject: [PATCH 032/145] Update change log for version 8.2.0 --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbd2cbb69..f49eba4b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [8.2.0] - released 2020-11-25 ### Added - Add a `getRedirectUri` function to the `OAuthServerException` class (PR #1123) +- Support for PHP 8.0 (PR #1146) + +### Removed +- Removed support for PHP 7.2 (PR #1146) ### Fixed - Fix typo in parameter hint. `code_challenged` changed to `code_challenge`. Thrown by Auth Code Grant when the code challenge does not match the regex. (PR #1130) @@ -505,7 +511,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.1.1...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.0...HEAD +[8.2.0]: https://github.com/thephpleague/oauth2-server/compare/8.1.1...8.2.0 [8.1.1]: https://github.com/thephpleague/oauth2-server/compare/8.1.0...8.1.1 [8.1.0]: https://github.com/thephpleague/oauth2-server/compare/8.0.0...8.1.0 [8.0.0]: https://github.com/thephpleague/oauth2-server/compare/7.4.0...8.0.0 From 0419bf4a9cf584a3550077a840f608be3bc130be Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Nov 2020 11:23:14 +0000 Subject: [PATCH 033/145] Update dependencies --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index cd06d578c..e0617eb66 100644 --- a/composer.json +++ b/composer.json @@ -13,9 +13,9 @@ "ext-json": "*" }, "require-dev": { - "phpunit/phpunit": "^8.5.4 || ^9.1.3", - "laminas/laminas-diactoros": "^2.3.0", - "phpstan/phpstan": "^0.12.56", + "phpunit/phpunit": "^9.4.3", + "laminas/laminas-diactoros": "^2.5.0", + "phpstan/phpstan": "^0.12.57", "phpstan/phpstan-phpunit": "^0.12.16", "roave/security-advisories": "dev-master" }, From 682dc07e6b4fbf43c8efe9cae2fb07a1b7340336 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Nov 2020 11:29:29 +0000 Subject: [PATCH 034/145] Update examples dependencies --- examples/composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/composer.json b/examples/composer.json index de2bd7162..d265472e6 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -1,13 +1,13 @@ { "require": { - "slim/slim": "^3.0.0" + "slim/slim": "^3.12.3" }, "require-dev": { "league/event": "^2.2", - "lcobucci/jwt": "^3.4@dev || ^4.0@beta", - "psr/http-message": "^1.0", - "defuse/php-encryption": "^2.2", - "laminas/laminas-diactoros": "^2.1.2" + "lcobucci/jwt": "^3.4 || ^4.0", + "psr/http-message": "^1.0.1", + "defuse/php-encryption": "^2.2.1", + "laminas/laminas-diactoros": "^2.5.0" }, "autoload": { "psr-4": { From 8e89f55945a31acb385a3ac8cb9e3c0bcd9e8c82 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Nov 2020 23:23:25 +0000 Subject: [PATCH 035/145] Add docblocks for functions --- examples/composer.lock | 30 +++++++++---------- .../BearerTokenValidator.php | 3 ++ src/Entities/Traits/AccessTokenTrait.php | 3 ++ 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/composer.lock b/examples/composer.lock index d732e6f0b..f7affe0e9 100644 --- a/examples/composer.lock +++ b/examples/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "afc9a862edd0bfe93a224a28b1abf75d", + "content-hash": "1f38bc4bb33ddc5527b3097d1118b227", "packages": [ { "name": "nikic/fast-route", @@ -54,24 +54,24 @@ }, { "name": "pimple/pimple", - "version": "v3.3.0", + "version": "v3.3.1", "source": { "type": "git", "url": "https://github.com/silexphp/Pimple.git", - "reference": "e55d12f9d6a0e7f9c85992b73df1267f46279930" + "reference": "21e45061c3429b1e06233475cc0e1f6fc774d5b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/e55d12f9d6a0e7f9c85992b73df1267f46279930", - "reference": "e55d12f9d6a0e7f9c85992b73df1267f46279930", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/21e45061c3429b1e06233475cc0e1f6fc774d5b0", + "reference": "21e45061c3429b1e06233475cc0e1f6fc774d5b0", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.2.5", "psr/container": "^1.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3.4|^4.4|^5.0" + "symfony/phpunit-bridge": "^5.0" }, "type": "library", "extra": { @@ -100,7 +100,7 @@ "container", "dependency injection" ], - "time": "2020-03-03T09:12:48+00:00" + "time": "2020-11-24T20:35:42+00:00" }, { "name": "psr/container", @@ -522,16 +522,16 @@ }, { "name": "lcobucci/jwt", - "version": "dev-master", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "144de5ad99590cac0139a688c26bbf42539aeed9" + "reference": "6d8665ccd924dc076a9b65d1ea8abe21d68f6958" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/144de5ad99590cac0139a688c26bbf42539aeed9", - "reference": "144de5ad99590cac0139a688c26bbf42539aeed9", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/6d8665ccd924dc076a9b65d1ea8abe21d68f6958", + "reference": "6d8665ccd924dc076a9b65d1ea8abe21d68f6958", "shasum": "" }, "require": { @@ -580,7 +580,7 @@ "JWS", "jwt" ], - "time": "2020-11-23T04:40:16+00:00" + "time": "2020-11-25T02:06:12+00:00" }, { "name": "league/event", @@ -732,9 +732,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "lcobucci/jwt": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": [], diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 2469a5cf7..9a279de4f 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -66,6 +66,9 @@ public function setPublicKey(CryptKey $key) $this->initJwtConfiguration(); } + /** + * Initialise the JWT configuration. + */ private function initJwtConfiguration() { $this->jwtConfiguration = Configuration::forSymmetricSigner( diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index bf8069d21..26007188f 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -39,6 +39,9 @@ public function setPrivateKey(CryptKey $privateKey) $this->privateKey = $privateKey; } + /** + * Initialise the JWT Configuration. + */ public function initJwtConfiguration() { $privateKeyPassPhrase = $this->privateKey->getPassPhrase(); From 97cca39bac323b39e4ddce86538d2555e67af57b Mon Sep 17 00:00:00 2001 From: sephster Date: Thu, 26 Nov 2020 11:09:48 +0000 Subject: [PATCH 036/145] Pass Key object instead of string to JWT configuration --- src/Entities/Traits/AccessTokenTrait.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 26007188f..d60ff9f1c 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -46,7 +46,9 @@ public function initJwtConfiguration() { $privateKeyPassPhrase = $this->privateKey->getPassPhrase(); - $verificationKey = empty($privateKeyPassPhrase) ? InMemory::plainText('') : $privateKeyPassPhrase; + $verificationKey = empty($privateKeyPassPhrase) ? + InMemory::plainText('') : + InMemory::plainText($this->privateKey->getPassPhrase()); $this->jwtConfiguration = Configuration::forAsymmetricSigner( new Sha256(), From 28ced051ae4b6bd6a6b7b3ff6abb0d3bde552dcc Mon Sep 17 00:00:00 2001 From: sephster Date: Thu, 26 Nov 2020 11:11:34 +0000 Subject: [PATCH 037/145] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f49eba4b1..5304cee74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [8.2.1] - released 2020-11-26 +### Fixed +- If you have a password on your private key, it is now passed correctly to the JWT configuration object. (PR #XXXX) + ## [8.2.0] - released 2020-11-25 ### Added - Add a `getRedirectUri` function to the `OAuthServerException` class (PR #1123) From a88fcac219dcff73233b3141fbff8cf6c1ff01f0 Mon Sep 17 00:00:00 2001 From: sephster Date: Thu, 26 Nov 2020 11:16:49 +0000 Subject: [PATCH 038/145] Update changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5304cee74..f6b6e6dff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [8.2.1] - released 2020-11-26 ### Fixed -- If you have a password on your private key, it is now passed correctly to the JWT configuration object. (PR #XXXX) +- If you have a password on your private key, it is now passed correctly to the JWT configuration object. (PR #1159) ## [8.2.0] - released 2020-11-25 ### Added @@ -515,7 +515,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.0...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.1...HEAD +[8.2.1]: https://github.com/thephpleague/oauth2-server/compare/8.2.0...8.2.1 [8.2.0]: https://github.com/thephpleague/oauth2-server/compare/8.1.1...8.2.0 [8.1.1]: https://github.com/thephpleague/oauth2-server/compare/8.1.0...8.1.1 [8.1.0]: https://github.com/thephpleague/oauth2-server/compare/8.0.0...8.1.0 From 8a3f409e0275a6d78d12fee83711e22ae7d467c0 Mon Sep 17 00:00:00 2001 From: Brady Miller Date: Sun, 29 Nov 2020 23:09:56 -0800 Subject: [PATCH 039/145] fix for passphrase with private key --- src/Entities/Traits/AccessTokenTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index d60ff9f1c..e263ffab1 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -52,7 +52,7 @@ public function initJwtConfiguration() $this->jwtConfiguration = Configuration::forAsymmetricSigner( new Sha256(), - LocalFileReference::file($this->privateKey->getKeyPath()), + LocalFileReference::file($this->privateKey->getKeyPath(), $this->privateKey->getPassPhrase()), $verificationKey ); } From 1caddca400d7efeb8f6720a0ad7b2530b842d3ea Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 30 Nov 2020 09:57:50 +0000 Subject: [PATCH 040/145] Handle null passphrases --- src/Entities/Traits/AccessTokenTrait.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index e263ffab1..2846debe4 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -44,16 +44,10 @@ public function setPrivateKey(CryptKey $privateKey) */ public function initJwtConfiguration() { - $privateKeyPassPhrase = $this->privateKey->getPassPhrase(); - - $verificationKey = empty($privateKeyPassPhrase) ? - InMemory::plainText('') : - InMemory::plainText($this->privateKey->getPassPhrase()); - $this->jwtConfiguration = Configuration::forAsymmetricSigner( new Sha256(), - LocalFileReference::file($this->privateKey->getKeyPath(), $this->privateKey->getPassPhrase()), - $verificationKey + LocalFileReference::file($this->privateKey->getKeyPath(), $this->privateKey->getPassPhrase() ?: ''), + InMemory::plainText('') ); } From 65cb74cb33735d83d1d76b67cc5590ea9274b6ac Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 30 Nov 2020 10:01:30 +0000 Subject: [PATCH 041/145] Changed to use null coalesce --- src/Entities/Traits/AccessTokenTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 2846debe4..dc5263f7e 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -46,7 +46,7 @@ public function initJwtConfiguration() { $this->jwtConfiguration = Configuration::forAsymmetricSigner( new Sha256(), - LocalFileReference::file($this->privateKey->getKeyPath(), $this->privateKey->getPassPhrase() ?: ''), + LocalFileReference::file($this->privateKey->getKeyPath(), $this->privateKey->getPassPhrase() ?? ''), InMemory::plainText('') ); } From 80352b8f3b10c2e835f9908839f3487d42b7eacd Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 30 Nov 2020 10:06:34 +0000 Subject: [PATCH 042/145] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6b6e6dff..1688e4ca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [8.2.2] - released 2020-11-30 +- Fix issue where the private key passphrase isn't correctly passed to JWT library (PR #1164) + ## [8.2.1] - released 2020-11-26 ### Fixed - If you have a password on your private key, it is now passed correctly to the JWT configuration object. (PR #1159) From e1f0c0d54a3825db67ed029300e792c8283e9cf1 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 30 Nov 2020 10:10:02 +0000 Subject: [PATCH 043/145] Fix format error for changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1688e4ca8..96b34e8b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ## [8.2.2] - released 2020-11-30 +### Fixed - Fix issue where the private key passphrase isn't correctly passed to JWT library (PR #1164) ## [8.2.1] - released 2020-11-26 From e87248ffcd905c3dd91ca58d002caa0ec1381c2c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 30 Nov 2020 10:10:52 +0000 Subject: [PATCH 044/145] Update changelog links --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96b34e8b6..4a2615b5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -519,7 +519,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.1...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.2...HEAD +[8.2.2]: https://github.com/thephpleague/oauth2-server/compare/8.2.1...8.2.2 [8.2.1]: https://github.com/thephpleague/oauth2-server/compare/8.2.0...8.2.1 [8.2.0]: https://github.com/thephpleague/oauth2-server/compare/8.1.1...8.2.0 [8.1.1]: https://github.com/thephpleague/oauth2-server/compare/8.1.0...8.1.1 From c1f4a6f22c7850eb89705967f3e0f675a9b8904e Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 2 Dec 2020 00:08:04 +0000 Subject: [PATCH 045/145] Re-Add PHP 7.2 Support and fix code coverage reports --- .github/workflows/tests.yml | 4 ++-- CHANGELOG.md | 3 +++ README.md | 1 + composer.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 14df99517..b79e3081f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php: [7.3, 7.4, 8.0] + php: [7.2, 7.3, 7.4, 8.0] stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} @@ -27,7 +27,7 @@ jobs: with: php-version: ${{ matrix.php }} extensions: dom, curl, libxml, mbstring, zip - coverage: none + coverage: pcov - name: Install dependencies run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a2615b5f..33a80ec41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [8.2.3] - released 2020-12-02 +### Added +- Re-added support for PHP 7.2 (PR #XXXX) ## [8.2.2] - released 2020-11-30 ### Fixed diff --git a/README.md b/README.md index 0c8869c52..ecd65944e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ This library was created by Alex Bilbie. Find him on Twitter at [@alexbilbie](ht The latest version of this package supports the following versions of PHP: +* PHP 7.2 * PHP 7.3 * PHP 7.4 * PHP 8.0 diff --git a/composer.json b/composer.json index e0617eb66..2e37dc6ef 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "homepage": "https://oauth2.thephpleague.com/", "license": "MIT", "require": { - "php": "^7.3 || ^8.0", + "php": "^7.2 || ^8.0", "ext-openssl": "*", "league/event": "^2.2", "lcobucci/jwt": "^3.4 || ^4.0", From 3bfdfff92ff8edd950929cc3c22588c1150d4e91 Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 2 Dec 2020 00:14:51 +0000 Subject: [PATCH 046/145] Change dependencies for PHP 7.2 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 2e37dc6ef..e4dd7ac44 100644 --- a/composer.json +++ b/composer.json @@ -13,8 +13,8 @@ "ext-json": "*" }, "require-dev": { - "phpunit/phpunit": "^9.4.3", - "laminas/laminas-diactoros": "^2.5.0", + "phpunit/phpunit": "^8.5.13 || ^9.4.3", + "laminas/laminas-diactoros": "^2.4.1", "phpstan/phpstan": "^0.12.57", "phpstan/phpstan-phpunit": "^0.12.16", "roave/security-advisories": "dev-master" From 2308a65c52d85da21717a07da0b1da3cb44309eb Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 2 Dec 2020 00:22:38 +0000 Subject: [PATCH 047/145] Remove is_countable function --- src/AuthorizationValidators/BearerTokenValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 9a279de4f..97a848108 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -133,6 +133,6 @@ public function validateAuthorization(ServerRequestInterface $request) */ private function convertSingleRecordAudToString($aud) { - return \is_countable($aud) && \count($aud) === 1 ? $aud[0] : $aud; + return \is_array($aud) && \count($aud) === 1 ? $aud[0] : $aud; } } From c5e52846cce109843d190099f53743d5ceb4b71b Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 2 Dec 2020 11:26:29 +0000 Subject: [PATCH 048/145] Downgrade phpunit to v8 --- composer.json | 2 +- phpunit.xml.dist | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index e4dd7ac44..6d01aecd7 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "ext-json": "*" }, "require-dev": { - "phpunit/phpunit": "^8.5.13 || ^9.4.3", + "phpunit/phpunit": "^8.5.13", "laminas/laminas-diactoros": "^2.4.1", "phpstan/phpstan": "^0.12.57", "phpstan/phpstan-phpunit": "^0.12.16", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a3e34084d..5f8851b28 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,9 +6,9 @@ ./tests/ - - + + src - - + + From 646686da8df911293e72b3d0b5deb8ac6d82f4ea Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Dec 2020 12:06:42 +0000 Subject: [PATCH 049/145] Remove code coverage from php 8.0 as phpunit cannot support --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b79e3081f..5eb13e038 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,7 +36,7 @@ jobs: run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover - name: Code coverage - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != 8.0 run: | wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover From 59bd4f7272997b02c83b644ff1c2e9a72f29e5b8 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Dec 2020 12:16:58 +0000 Subject: [PATCH 050/145] Push debug message --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5eb13e038..0185d8b4f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,6 +37,7 @@ jobs: - name: Code coverage if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != 8.0 + echo "::debug::The matrix is set to ${{ matrix.php }}" run: | wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover From 88a30cb38d528f7e4af7820a9baa702bcf32b6d0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Dec 2020 12:18:50 +0000 Subject: [PATCH 051/145] Fix debug message --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0185d8b4f..6e3894608 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,7 +37,7 @@ jobs: - name: Code coverage if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != 8.0 - echo "::debug::The matrix is set to ${{ matrix.php }}" run: | + echo "::debug::The matrix is set to ${{ matrix.php }}" wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover From 90e5fcd65ca4935258dd9e1713080d6f109ff636 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Dec 2020 12:21:21 +0000 Subject: [PATCH 052/145] Fix code coverage restriction for PHP 8 --- .github/workflows/tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6e3894608..e666894e4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,8 +36,7 @@ jobs: run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover - name: Code coverage - if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != 8.0 + if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != 8 run: | - echo "::debug::The matrix is set to ${{ matrix.php }}" wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover From ead76513c0598dcfcd5d5700b20933059baf3c81 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Dec 2020 22:55:19 +0000 Subject: [PATCH 053/145] Add debug --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e666894e4..2a6f77374 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,5 +38,6 @@ jobs: - name: Code coverage if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != 8 run: | + echo "::debug::The matrix is set to -${{ matrix.php }}-" wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover From d76a8b8d1a583fa1edf81a2dcdd002e77eabc3f2 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Dec 2020 23:00:21 +0000 Subject: [PATCH 054/145] Fix for excluding php 8 code coverage --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2a6f77374..5fe4782ab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,7 +36,7 @@ jobs: run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover - name: Code coverage - if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != 8 + if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != '8' run: | echo "::debug::The matrix is set to -${{ matrix.php }}-" wget https://scrutinizer-ci.com/ocular.phar From 3d5160d984aa2c659b7aff75d4c6690b4e195e0d Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Dec 2020 23:07:44 +0000 Subject: [PATCH 055/145] further trials on workflow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5fe4782ab..7a2b10f12 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,7 +36,7 @@ jobs: run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover - name: Code coverage - if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != '8' + if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != '8.0' run: | echo "::debug::The matrix is set to -${{ matrix.php }}-" wget https://scrutinizer-ci.com/ocular.phar From f7e785d7de8c49bde3757915b64816548375f9d6 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Dec 2020 23:15:04 +0000 Subject: [PATCH 056/145] Tweaking of github flow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7a2b10f12..192077084 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,7 +36,7 @@ jobs: run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover - name: Code coverage - if: github.ref == 'refs/heads/master' && ${{ matrix.php }} != '8.0' + if: ${{ github.ref == 'refs/heads/master' && matrix.php != 8.0 }} run: | echo "::debug::The matrix is set to -${{ matrix.php }}-" wget https://scrutinizer-ci.com/ocular.phar From 5907279fab8bb277408ebf5689705bd2b8463dcc Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Dec 2020 23:17:55 +0000 Subject: [PATCH 057/145] Remove debug message --- .github/workflows/tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 192077084..d45dc7625 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,6 +38,5 @@ jobs: - name: Code coverage if: ${{ github.ref == 'refs/heads/master' && matrix.php != 8.0 }} run: | - echo "::debug::The matrix is set to -${{ matrix.php }}-" wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover From f847bd5b00c5107048f688d99bf18b238596688b Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 3 Dec 2020 21:28:46 +0000 Subject: [PATCH 058/145] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33a80ec41..4a6ccbc05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -522,7 +522,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.2...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.3...HEAD +[8.2.3]: https://github.com/thephpleague/oauth2-server/compare/8.2.2...8.2.3 [8.2.2]: https://github.com/thephpleague/oauth2-server/compare/8.2.1...8.2.2 [8.2.1]: https://github.com/thephpleague/oauth2-server/compare/8.2.0...8.2.1 [8.2.0]: https://github.com/thephpleague/oauth2-server/compare/8.1.1...8.2.0 From 70bb329bc79b7965a56f46e293da946c5a976ef1 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 3 Dec 2020 21:32:29 +0000 Subject: [PATCH 059/145] Add pull requests to CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a6ccbc05..aa6452c95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ## [8.2.3] - released 2020-12-02 ### Added -- Re-added support for PHP 7.2 (PR #XXXX) +- Re-added support for PHP 7.2 (PR #1165, #1167) ## [8.2.2] - released 2020-11-30 ### Fixed From 80699bda1a773c0fe3f8355f7ded12b8f93d446b Mon Sep 17 00:00:00 2001 From: sephster Date: Tue, 8 Dec 2020 23:23:09 +0000 Subject: [PATCH 060/145] Revert enforcing of client redirect uri --- src/Grant/AbstractGrant.php | 2 +- src/Grant/AuthCodeGrant.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index ead94db72..c4797292a 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -216,7 +216,7 @@ protected function getClientEntityOrFail($clientId, ServerRequestInterface $requ { $client = $this->clientRepository->getClientEntity($clientId); - if ($client instanceof ClientEntityInterface === false || empty($client->getRedirectUri())) { + if ($client instanceof ClientEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 0f4211172..5cce97e51 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -261,7 +261,8 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) if ($redirectUri !== null) { $this->validateRedirectUri($redirectUri, $client, $request); - } elseif (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1) { + } elseif (empty($client->getRedirectUri()) || + (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); From 44e8b4138d66e4bbb799667501c05ab80a25fab7 Mon Sep 17 00:00:00 2001 From: Symbioquine Date: Tue, 8 Dec 2020 09:46:28 -0800 Subject: [PATCH 061/145] Fix confusing auth behavior when used with a common .htaccess pattern Why: It is common for .htaccess files - including the default one provided by Drupal Core - to coerce the `Authorization` header to the empty string if not specified by the client. Without this fix, that results in a `WWW-Authenticate: Basic` header response which causes the browser to show a useless basic authentication dialog. For more info see https://github.com/thephpleague/oauth2-server/issues/1162 --- src/Exception/OAuthServerException.php | 33 +++++++++++++++++-- tests/Exception/OAuthServerExceptionTest.php | 34 ++++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 31dcf2ea5..64adebf14 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -334,13 +334,12 @@ public function getHttpHeaders() // respond with an HTTP 401 (Unauthorized) status code and // include the "WWW-Authenticate" response header field // matching the authentication scheme used by the client. - // @codeCoverageIgnoreStart - if ($this->errorType === 'invalid_client' && $this->serverRequest->hasHeader('Authorization') === true) { + if ($this->errorType === 'invalid_client' && $this->requestHasAuthorizationHeader()) { $authScheme = \strpos($this->serverRequest->getHeader('Authorization')[0], 'Bearer') === 0 ? 'Bearer' : 'Basic'; $headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"'; } - // @codeCoverageIgnoreEnd + return $headers; } @@ -386,4 +385,32 @@ public function getHint() { return $this->hint; } + + /** + * Check if the request has a non-empty 'Authorization' header value. + * + * Returns true if the header is present and not an empty string, false + * otherwise. + * + * @return bool + */ + private function requestHasAuthorizationHeader() + { + if (!$this->serverRequest->hasHeader('Authorization')) { + return false; + } + + $authorizationHeader = $this->serverRequest->getHeader('Authorization'); + + // Common .htaccess configurations yield an empty string for the + // 'Authorization' header when one is not provided by the client. + // For practical purposes that case should be treated as though the + // header isn't present. + // See https://github.com/thephpleague/oauth2-server/issues/1162 + if (empty($authorizationHeader) || empty($authorizationHeader[0])) { + return false; + } + + return true; + } } diff --git a/tests/Exception/OAuthServerExceptionTest.php b/tests/Exception/OAuthServerExceptionTest.php index 024911884..7ece08032 100644 --- a/tests/Exception/OAuthServerExceptionTest.php +++ b/tests/Exception/OAuthServerExceptionTest.php @@ -29,6 +29,23 @@ public function testInvalidClientExceptionSetsAuthenticateHeader() } } + public function testInvalidClientExceptionSetsBearerAuthenticateHeader() + { + $serverRequest = (new ServerRequest()) + ->withParsedBody([ + 'client_id' => 'foo', + ]) + ->withAddedHeader('Authorization', 'Bearer fakeauthdetails'); + + try { + $this->issueInvalidClientException($serverRequest); + } catch (OAuthServerException $e) { + $response = $e->generateHttpResponse(new Response()); + + $this->assertEquals(['Bearer realm="OAuth"'], $response->getHeader('WWW-Authenticate')); + } + } + public function testInvalidClientExceptionOmitsAuthenticateHeader() { $serverRequest = (new ServerRequest()) @@ -45,6 +62,23 @@ public function testInvalidClientExceptionOmitsAuthenticateHeader() } } + public function testInvalidClientExceptionOmitsAuthenticateHeaderGivenEmptyAuthorizationHeader() + { + $serverRequest = (new ServerRequest()) + ->withParsedBody([ + 'client_id' => 'foo', + ]) + ->withAddedHeader('Authorization', ''); + + try { + $this->issueInvalidClientException($serverRequest); + } catch (OAuthServerException $e) { + $response = $e->generateHttpResponse(new Response()); + + $this->assertFalse($response->hasHeader('WWW-Authenticate')); + } + } + /** * Issue an invalid client exception * From e80ac28d59398d9c5b0fe222972725e2016daa2e Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 9 Dec 2020 11:39:48 +0000 Subject: [PATCH 062/145] Readded changes for v9 to changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 771661a6b..a68f7147f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added (v9) +- A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) +- The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) +- An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110) + ### Fixed (v9) - If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082) From 0ba3f42e82ce8fc2040d556c55709f068f08b736 Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 9 Dec 2020 11:48:55 +0000 Subject: [PATCH 063/145] Add redirect URI to client for RefreshToken test --- tests/Grant/RefreshTokenGrantTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index a2c6ed82f..d6a0d42e2 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -474,7 +474,10 @@ public function testRespondToRequestRevokedToken() public function testRespondToRequestFinalizeScopes() { $client = new ClientEntity(); + $client->setIdentifier('foo'); + $client->setRedirectUri('http://foo/bar'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); From a1fd8f0cc9991039aa66904b04cc1d97c3ae7a09 Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 9 Dec 2020 11:51:27 +0000 Subject: [PATCH 064/145] Apply StyleCI fixes --- src/AuthorizationValidators/BearerTokenValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 2f86c2169..b062c4461 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -17,11 +17,11 @@ use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token\InvalidTokenStructure; -use League\OAuth2\Server\CryptKeyInterface; use Lcobucci\JWT\Token\UnsupportedHeaderFound; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\ValidAt; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; +use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; From cd43b4a2bdcf6afeb066cfc7e5f0370ac1122f71 Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 9 Dec 2020 12:00:23 +0000 Subject: [PATCH 065/145] Update changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa6452c95..d8a75bf26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [8.2.4] - released 2020-12-09 +### Fixed +- Reverted the enforcement of at least one redirect_uri for a client. This change has instead been moved to version 9 (PR #1169) + ## [8.2.3] - released 2020-12-02 ### Added - Re-added support for PHP 7.2 (PR #1165, #1167) @@ -522,7 +526,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.3...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...HEAD +[8.2.4]: https://github.com/thephpleague/oauth2-server/compare/8.2.3...8.2.4 [8.2.3]: https://github.com/thephpleague/oauth2-server/compare/8.2.2...8.2.3 [8.2.2]: https://github.com/thephpleague/oauth2-server/compare/8.2.1...8.2.2 [8.2.1]: https://github.com/thephpleague/oauth2-server/compare/8.2.0...8.2.1 From 622eaa1f28eb4a2dea0cfc7e4f5280fac794e83c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 10 Dec 2020 11:35:44 +0000 Subject: [PATCH 066/145] Update release date for 8.2.4 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8a75bf26..0a9a54bdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] -## [8.2.4] - released 2020-12-09 +## [8.2.4] - released 2020-12-10 ### Fixed - Reverted the enforcement of at least one redirect_uri for a client. This change has instead been moved to version 9 (PR #1169) From c29055cd21d384fe3ca948c034f42fa263f0392c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 11 Dec 2020 12:03:14 +0000 Subject: [PATCH 067/145] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa6452c95..b638bcf58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- The server will now only recognise and handle an authorization header if the value of the header is non-empty. This is to circumvent issues where some common frameworks set this header even if no value is present (PR #1170) + ## [8.2.3] - released 2020-12-02 ### Added - Re-added support for PHP 7.2 (PR #1165, #1167) From 2f62832e74ae02ce6326f189a4333717d2ffbff6 Mon Sep 17 00:00:00 2001 From: Manuel Dimmler Date: Sun, 3 Jan 2021 21:20:08 +0100 Subject: [PATCH 068/145] Default Scope does not work as expected resolves issue #1092 --- src/Grant/AbstractGrant.php | 2 +- tests/Grant/AbstractGrantTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index c4797292a..3802e116f 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -316,7 +316,7 @@ public function validateScopes($scopes, $redirectUri = null) private function convertScopesQueryStringToArray($scopes) { return \array_filter(\explode(self::SCOPE_DELIMITER_STRING, \trim($scopes)), function ($scope) { - return !empty($scope); + return $scope !== ''; }); } diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 93f95fa5c..8e94acae2 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -431,13 +431,13 @@ public function testValidateScopes() { $scope = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->expects($this->exactly(3))->method('getScopeEntityByIdentifier')->willReturn($scope); /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setScopeRepository($scopeRepositoryMock); - $this->assertEquals([$scope], $grantMock->validateScopes('basic ')); + $this->assertEquals([$scope, $scope, $scope], $grantMock->validateScopes('basic test 0 ')); } public function testValidateScopesBadScope() From 15abf4ab7251722e09b49ca3194d79ad3db09ede Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 6 Jan 2021 10:28:23 +0000 Subject: [PATCH 069/145] Restrict code coverage checks to this repo in actions --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d45dc7625..1f1db9e1b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,7 +36,7 @@ jobs: run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover - name: Code coverage - if: ${{ github.ref == 'refs/heads/master' && matrix.php != 8.0 }} + if: ${{ github.ref == 'refs/heads/master' && matrix.php != 8.0 && github.repository == 'thephpleague/oauth2-server' }} run: | wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover From f0ee70dafd7b58124280621e47debda0c23cdbc9 Mon Sep 17 00:00:00 2001 From: Jan Hopman Date: Mon, 8 Feb 2021 17:16:10 +0100 Subject: [PATCH 070/145] Add switch to prevent revoking of refresh tokens. --- src/AuthorizationServer.php | 11 ++++++++++- src/Grant/AbstractGrant.php | 17 +++++++++++++++-- src/Grant/GrantTypeInterface.php | 7 +++++++ src/Grant/RefreshTokenGrant.php | 14 +++++++++----- 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 8b0b2815e..90de52578 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -79,6 +79,11 @@ class AuthorizationServer implements EmitterAwareInterface */ private $defaultScope = ''; + /** + * @var bool + */ + private $revokeRefreshTokens; + /** * New server instance. * @@ -88,6 +93,7 @@ class AuthorizationServer implements EmitterAwareInterface * @param CryptKey|string $privateKey * @param string|Key $encryptionKey * @param null|ResponseTypeInterface $responseType + * @param bool $revokeRefreshTokens */ public function __construct( ClientRepositoryInterface $clientRepository, @@ -95,7 +101,8 @@ public function __construct( ScopeRepositoryInterface $scopeRepository, $privateKey, $encryptionKey, - ResponseTypeInterface $responseType = null + ResponseTypeInterface $responseType = null, + bool $revokeRefreshTokens = true ) { $this->clientRepository = $clientRepository; $this->accessTokenRepository = $accessTokenRepository; @@ -115,6 +122,7 @@ public function __construct( } $this->responseType = $responseType; + $this->revokeRefreshTokens = $revokeRefreshTokens; } /** @@ -136,6 +144,7 @@ public function enableGrantType(GrantTypeInterface $grantType, DateInterval $acc $grantType->setPrivateKey($this->privateKey); $grantType->setEmitter($this->getEmitter()); $grantType->setEncryptionKey($this->encryptionKey); + $grantType->setRevokeRefreshTokens($this->revokeRefreshTokens); $this->enabledGrantTypes[$grantType->getIdentifier()] = $grantType; $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] = $accessTokenTTL; diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index c4797292a..c82286ead 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -92,6 +92,11 @@ abstract class AbstractGrant implements GrantTypeInterface */ protected $defaultScope; + /** + * @var bool + */ + protected $revokeRefreshTokens; + /** * @param ClientRepositoryInterface $clientRepository */ @@ -166,6 +171,14 @@ public function setDefaultScope($scope) $this->defaultScope = $scope; } + /** + * @param bool $revokeRefreshTokens + */ + public function setRevokeRefreshTokens(bool $revokeRefreshTokens) + { + $this->revokeRefreshTokens = $revokeRefreshTokens; + } + /** * Validate the client. * @@ -177,7 +190,7 @@ public function setDefaultScope($scope) */ protected function validateClient(ServerRequestInterface $request) { - list($clientId, $clientSecret) = $this->getClientCredentials($request); + [$clientId, $clientSecret] = $this->getClientCredentials($request); if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); @@ -234,7 +247,7 @@ protected function getClientEntityOrFail($clientId, ServerRequestInterface $requ */ protected function getClientCredentials(ServerRequestInterface $request) { - list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request); + [$basicAuthUser, $basicAuthPassword] = $this->getBasicAuthCredentials($request); $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 41ebeb5ff..d761fb0ab 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -141,4 +141,11 @@ public function setPrivateKey(CryptKey $privateKey); * @param string|Key|null $key */ public function setEncryptionKey($key = null); + + /** + * Enables ability to prevent refresh tokens from being revoked. + * + * @param bool $revokeRefreshTokens + */ + public function setRevokeRefreshTokens(bool $revokeRefreshTokens); } diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 19945f5ce..ea781f53b 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -63,7 +63,9 @@ public function respondToAccessTokenRequest( // Expire old tokens $this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']); - $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']); + if ($this->revokeRefreshTokens) { + $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']); + } // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes); @@ -71,11 +73,13 @@ public function respondToAccessTokenRequest( $responseType->setAccessToken($accessToken); // Issue and persist new refresh token if given - $refreshToken = $this->issueRefreshToken($accessToken); + if ($this->revokeRefreshTokens) { + $refreshToken = $this->issueRefreshToken($accessToken); - if ($refreshToken !== null) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); - $responseType->setRefreshToken($refreshToken); + if ($refreshToken !== null) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); + $responseType->setRefreshToken($refreshToken); + } } return $responseType; From fe20b334b4aed9be8eae20e708a846a51d4057d2 Mon Sep 17 00:00:00 2001 From: janhopman-nhb Date: Thu, 11 Feb 2021 16:28:56 +0100 Subject: [PATCH 071/145] Fix unit test. --- tests/Grant/RefreshTokenGrantTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 48e81f619..f60b9a67d 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -68,6 +68,7 @@ public function testRespondToRequest() $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $grant->setRevokeRefreshTokens(true); $oldRefreshToken = $this->cryptStub->doEncrypt( \json_encode( @@ -181,6 +182,7 @@ public function testRespondToReducedScopes() $grant->setScopeRepository($scopeRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $grant->setRevokeRefreshTokens(true); $oldRefreshToken = $this->cryptStub->doEncrypt( \json_encode( From fbb96cbdbdbb07f456352a88a0b4b7dfdc57c7f7 Mon Sep 17 00:00:00 2001 From: Eugene Borovov Date: Mon, 8 Mar 2021 02:26:03 +0700 Subject: [PATCH 072/145] Separation of token parsing and validation Signed-off-by: Eugene Borovov --- .../BearerTokenValidator.php | 21 ++++++++----------- .../ResponseTypes/BearerResponseTypeTest.php | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 97a848108..bab8919f6 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -12,12 +12,9 @@ use DateTimeZone; use Lcobucci\Clock\SystemClock; use Lcobucci\JWT\Configuration; -use Lcobucci\JWT\Encoding\CannotDecodeContent; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; -use Lcobucci\JWT\Token\InvalidTokenStructure; -use Lcobucci\JWT\Token\UnsupportedHeaderFound; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\ValidAt; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; @@ -95,18 +92,18 @@ public function validateAuthorization(ServerRequestInterface $request) $jwt = \trim((string) \preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0])); try { - // Attempt to parse and validate the JWT + // Attempt to parse the JWT $token = $this->jwtConfiguration->parser()->parse($jwt); + } catch (\Lcobucci\JWT\Exception $exception) { + throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception); + } + try { + // Attempt to validate the JWT $constraints = $this->jwtConfiguration->validationConstraints(); - - try { - $this->jwtConfiguration->validator()->assert($token, ...$constraints); - } catch (RequiredConstraintsViolated $exception) { - throw OAuthServerException::accessDenied('Access token could not be verified'); - } - } catch (CannotDecodeContent | InvalidTokenStructure | UnsupportedHeaderFound $exception) { - throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception); + $this->jwtConfiguration->validator()->assert($token, ...$constraints); + } catch (RequiredConstraintsViolated $exception) { + throw OAuthServerException::accessDenied('Access token could not be verified'); } $claims = $token->claims(); diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index a57820d00..da7242651 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -166,7 +166,7 @@ public function testDetermineAccessTokenInHeaderInvalidJWT() $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); $accessToken->setUserIdentifier(123); - $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $accessToken->setExpiryDateTime((new DateTimeImmutable())->sub(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -184,7 +184,7 @@ public function testDetermineAccessTokenInHeaderInvalidJWT() $authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock); $authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); - $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $json->access_token . 'foo')); + $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $json->access_token)); try { $authorizationValidator->validateAuthorization($request); From 07bdaebb5d63ded6e227da9ba4ad72473fc4cd62 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 14 Mar 2021 17:31:30 +0000 Subject: [PATCH 073/145] Remove deprecated function --- src/AuthorizationValidators/BearerTokenValidator.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 97a848108..8e19cb58b 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -19,6 +19,7 @@ use Lcobucci\JWT\Token\InvalidTokenStructure; use Lcobucci\JWT\Token\UnsupportedHeaderFound; use Lcobucci\JWT\Validation\Constraint\SignedWith; +use Lcobucci\JWT\Validation\Constraint\StrictValidAt; use Lcobucci\JWT\Validation\Constraint\ValidAt; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; use League\OAuth2\Server\CryptKey; @@ -77,7 +78,7 @@ private function initJwtConfiguration() ); $this->jwtConfiguration->setValidationConstraints( - new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), + new StrictValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), new SignedWith(new Sha256(), LocalFileReference::file($this->publicKey->getKeyPath())) ); } From 83af8d179df3816c2ed2a36316c49cd4d835c2f6 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 14 Mar 2021 17:32:25 +0000 Subject: [PATCH 074/145] Update minimum 4.x version for lcobucci/jwt --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6d01aecd7..2d50e88e4 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": "^7.2 || ^8.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.4 || ^4.0", + "lcobucci/jwt": "^3.4 || ^4.1", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "ext-json": "*" From ad4f1a952602685e905a5a48856ab5790d12febd Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Mon, 15 Mar 2021 21:54:41 +0100 Subject: [PATCH 075/145] allowing different ports for loopback redirect uris --- src/Grant/AbstractGrant.php | 12 +--- .../Rfc8252RedirectUriValidator.php | 63 +++++++++++++++++ .../Rfc8252RedirectUriValidatorTest.php | 68 +++++++++++++++++++ 3 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 src/RedirectUriValidators/Rfc8252RedirectUriValidator.php create mode 100644 tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index c4797292a..3737049a8 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -24,6 +24,7 @@ use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; +use League\OAuth2\Server\RedirectUriValidators\Rfc8252RedirectUriValidator; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; @@ -262,15 +263,8 @@ protected function validateRedirectUri( ClientEntityInterface $client, ServerRequestInterface $request ) { - if (\is_string($client->getRedirectUri()) - && (\strcmp($client->getRedirectUri(), $redirectUri) !== 0) - ) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient($request); - } elseif (\is_array($client->getRedirectUri()) - && \in_array($redirectUri, $client->getRedirectUri(), true) === false - ) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); + $validator = new Rfc8252RedirectUriValidator($client); + if (!$validator->validateRedirectUri($redirectUri)) { throw OAuthServerException::invalidClient($request); } } diff --git a/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php b/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php new file mode 100644 index 000000000..e84eefe13 --- /dev/null +++ b/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php @@ -0,0 +1,63 @@ +client = $client; + } + + public function validateRedirectUri(string $redirectUri) { + $parsedUrl = $this->parseUrl($redirectUri); + if ($this->isLoopbackAddress($parsedUrl)) { + return $this->allowDifferentPort($parsedUrl); + } else { + return $this->matchFullUri($redirectUri); + } + } + + private function isLoopbackAddress(array $parsedUri) { + return $parsedUri['scheme'] === 'http' + && (\in_array($parsedUri['host'], ['127.0.0.1', '[::1]'], true)); + } + + private function matchFullUri(string $redirectUri) { + return \in_array($redirectUri, $this->getClientRedirectUris(), true); + } + + private function allowDifferentPort(array $parsedUrl) { + foreach ($this->getClientRedirectUris() as $clientRedirectUri) { + if ($parsedUrl == $this->parseUrl($clientRedirectUri)) { + return true; + } + } + return false; + } + + private function parseUrl(string $url) { + $parsedUrl = parse_url($url); + $parsedUrl['port'] = 80; + + return $parsedUrl; + } + + private function getClientRedirectUris() { + $clientRedirectUri = $this->client->getRedirectUri(); + if (\is_string($clientRedirectUri)) { + return [ $clientRedirectUri ]; + } else if (\is_array($clientRedirectUri)) { + return $clientRedirectUri; + } else { + return []; + } + } + +} diff --git a/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php b/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php new file mode 100644 index 000000000..6a75d5258 --- /dev/null +++ b/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php @@ -0,0 +1,68 @@ +setRedirectUri([ + 'https://example.com:8443/endpoint', + 'https://example.com/different/endpoint', + ]); + $redirectUri = 'https://example.com/endpoint'; + + $validator = new Rfc8252RedirectUriValidator($client); + + $this->assertFalse($validator->validateRedirectUri($redirectUri), + 'Non loopback URI must match in every part'); + } + + public function testValidNonLoopbackUri() + { + $client = new ClientEntity(); + $client->setRedirectUri([ + 'https://example.com:8443/endpoint', + 'https://example.com/different/endpoint', + ]); + $redirectUri = 'https://example.com:8443/endpoint'; + + $validator = new Rfc8252RedirectUriValidator($client); + + $this->assertTrue($validator->validateRedirectUri($redirectUri), + 'Redirect URI must be valid when matching in every part'); + } + + public function testInvalidLoopbackUri() + { + $client = new ClientEntity(); + $client->setRedirectUri('http://127.0.0.1:8443/endpoint'); + + $redirectUri = 'http://127.0.0.1:8443/different/endpoint'; + + $validator = new Rfc8252RedirectUriValidator($client); + + $this->assertFalse($validator->validateRedirectUri($redirectUri), + 'Valid loopback redirect URI can change only the port number'); + } + + public function testValidLoopbackUri() + { + $client = new ClientEntity(); + $client->setRedirectUri('http://127.0.0.1:8443/endpoint'); + + $redirectUri = 'http://127.0.0.1:8080/endpoint'; + + $validator = new Rfc8252RedirectUriValidator($client); + + $this->assertTrue($validator->validateRedirectUri($redirectUri), + 'Loopback redirect URI can change the port number'); + } +} From 12006d8cdf51fb30438c116dde7582527d20c5ff Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Mon, 15 Mar 2021 21:55:57 +0100 Subject: [PATCH 076/145] added test case for ipv6 loopback address --- .../Rfc8252RedirectUriValidatorTest.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php b/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php index 6a75d5258..c3d30286a 100644 --- a/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php +++ b/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php @@ -65,4 +65,17 @@ public function testValidLoopbackUri() $this->assertTrue($validator->validateRedirectUri($redirectUri), 'Loopback redirect URI can change the port number'); } + + public function testValidIpv4LoopbackUri() + { + $client = new ClientEntity(); + $client->setRedirectUri('http://[::1]:8443/endpoint'); + + $redirectUri = 'http://[::1]:8080/endpoint'; + + $validator = new Rfc8252RedirectUriValidator($client); + + $this->assertTrue($validator->validateRedirectUri($redirectUri), + 'Loopback redirect URI can change the port number'); + } } From fdc666d9178290e9e798cdbfec8e9d570c628b0d Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Mon, 15 Mar 2021 22:04:40 +0100 Subject: [PATCH 077/145] code formatting --- .../Rfc8252RedirectUriValidator.php | 27 +++++++++++-------- .../Rfc8252RedirectUriValidatorTest.php | 16 ++++++----- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php b/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php index e84eefe13..6bc0a8408 100644 --- a/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php +++ b/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php @@ -3,19 +3,19 @@ namespace League\OAuth2\Server\RedirectUriValidators; - use League\OAuth2\Server\Entities\ClientEntityInterface; class Rfc8252RedirectUriValidator { - private $client; - public function __construct(ClientEntityInterface $client) { + public function __construct(ClientEntityInterface $client) + { $this->client = $client; } - public function validateRedirectUri(string $redirectUri) { + public function validateRedirectUri(string $redirectUri) + { $parsedUrl = $this->parseUrl($redirectUri); if ($this->isLoopbackAddress($parsedUrl)) { return $this->allowDifferentPort($parsedUrl); @@ -24,40 +24,45 @@ public function validateRedirectUri(string $redirectUri) { } } - private function isLoopbackAddress(array $parsedUri) { + private function isLoopbackAddress(array $parsedUri) + { return $parsedUri['scheme'] === 'http' && (\in_array($parsedUri['host'], ['127.0.0.1', '[::1]'], true)); } - private function matchFullUri(string $redirectUri) { + private function matchFullUri(string $redirectUri) + { return \in_array($redirectUri, $this->getClientRedirectUris(), true); } - private function allowDifferentPort(array $parsedUrl) { + private function allowDifferentPort(array $parsedUrl) + { foreach ($this->getClientRedirectUris() as $clientRedirectUri) { if ($parsedUrl == $this->parseUrl($clientRedirectUri)) { return true; } } + return false; } - private function parseUrl(string $url) { + private function parseUrl(string $url) + { $parsedUrl = parse_url($url); $parsedUrl['port'] = 80; return $parsedUrl; } - private function getClientRedirectUris() { + private function getClientRedirectUris() + { $clientRedirectUri = $this->client->getRedirectUri(); if (\is_string($clientRedirectUri)) { - return [ $clientRedirectUri ]; + return [$clientRedirectUri]; } else if (\is_array($clientRedirectUri)) { return $clientRedirectUri; } else { return []; } } - } diff --git a/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php b/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php index c3d30286a..91f37d67a 100644 --- a/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php +++ b/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php @@ -3,7 +3,6 @@ namespace LeagueTests\RedirectUriValidators; - use League\OAuth2\Server\RedirectUriValidators\Rfc8252RedirectUriValidator; use LeagueTests\Stubs\ClientEntity; use PHPUnit\Framework\TestCase; @@ -21,7 +20,8 @@ public function testInvalidNonLoopbackUri() $validator = new Rfc8252RedirectUriValidator($client); - $this->assertFalse($validator->validateRedirectUri($redirectUri), + $this->assertFalse( + $validator->validateRedirectUri($redirectUri), 'Non loopback URI must match in every part'); } @@ -36,7 +36,8 @@ public function testValidNonLoopbackUri() $validator = new Rfc8252RedirectUriValidator($client); - $this->assertTrue($validator->validateRedirectUri($redirectUri), + $this->assertTrue( + $validator->validateRedirectUri($redirectUri), 'Redirect URI must be valid when matching in every part'); } @@ -49,7 +50,8 @@ public function testInvalidLoopbackUri() $validator = new Rfc8252RedirectUriValidator($client); - $this->assertFalse($validator->validateRedirectUri($redirectUri), + $this->assertFalse( + $validator->validateRedirectUri($redirectUri), 'Valid loopback redirect URI can change only the port number'); } @@ -62,7 +64,8 @@ public function testValidLoopbackUri() $validator = new Rfc8252RedirectUriValidator($client); - $this->assertTrue($validator->validateRedirectUri($redirectUri), + $this->assertTrue( + $validator->validateRedirectUri($redirectUri), 'Loopback redirect URI can change the port number'); } @@ -75,7 +78,8 @@ public function testValidIpv4LoopbackUri() $validator = new Rfc8252RedirectUriValidator($client); - $this->assertTrue($validator->validateRedirectUri($redirectUri), + $this->assertTrue( + $validator->validateRedirectUri($redirectUri), 'Loopback redirect URI can change the port number'); } } From 2f4975581a3e3c1518abbb125bbed86c5546eee4 Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Mon, 15 Mar 2021 22:07:22 +0100 Subject: [PATCH 078/145] code formatting --- .../Rfc8252RedirectUriValidator.php | 10 +++++----- .../Rfc8252RedirectUriValidatorTest.php | 15 ++++++++++----- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php b/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php index 6bc0a8408..7259640fc 100644 --- a/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php +++ b/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php @@ -37,18 +37,18 @@ private function matchFullUri(string $redirectUri) private function allowDifferentPort(array $parsedUrl) { - foreach ($this->getClientRedirectUris() as $clientRedirectUri) { + foreach ($this->getClientRedirectUris() as $clientRedirectUri) { if ($parsedUrl == $this->parseUrl($clientRedirectUri)) { return true; } - } + } - return false; + return false; } private function parseUrl(string $url) { - $parsedUrl = parse_url($url); + $parsedUrl = \parse_url($url); $parsedUrl['port'] = 80; return $parsedUrl; @@ -59,7 +59,7 @@ private function getClientRedirectUris() $clientRedirectUri = $this->client->getRedirectUri(); if (\is_string($clientRedirectUri)) { return [$clientRedirectUri]; - } else if (\is_array($clientRedirectUri)) { + } elseif (\is_array($clientRedirectUri)) { return $clientRedirectUri; } else { return []; diff --git a/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php b/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php index 91f37d67a..6b888d695 100644 --- a/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php +++ b/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php @@ -22,7 +22,8 @@ public function testInvalidNonLoopbackUri() $this->assertFalse( $validator->validateRedirectUri($redirectUri), - 'Non loopback URI must match in every part'); + 'Non loopback URI must match in every part' + ); } public function testValidNonLoopbackUri() @@ -38,7 +39,8 @@ public function testValidNonLoopbackUri() $this->assertTrue( $validator->validateRedirectUri($redirectUri), - 'Redirect URI must be valid when matching in every part'); + 'Redirect URI must be valid when matching in every part' + ); } public function testInvalidLoopbackUri() @@ -52,7 +54,8 @@ public function testInvalidLoopbackUri() $this->assertFalse( $validator->validateRedirectUri($redirectUri), - 'Valid loopback redirect URI can change only the port number'); + 'Valid loopback redirect URI can change only the port number' + ); } public function testValidLoopbackUri() @@ -66,7 +69,8 @@ public function testValidLoopbackUri() $this->assertTrue( $validator->validateRedirectUri($redirectUri), - 'Loopback redirect URI can change the port number'); + 'Loopback redirect URI can change the port number' + ); } public function testValidIpv4LoopbackUri() @@ -80,6 +84,7 @@ public function testValidIpv4LoopbackUri() $this->assertTrue( $validator->validateRedirectUri($redirectUri), - 'Loopback redirect URI can change the port number'); + 'Loopback redirect URI can change the port number' + ); } } From e9ba0cc54c34e0ab539a4a13d7ea3585aee84e5d Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 16 Mar 2021 07:43:12 +0100 Subject: [PATCH 079/145] fixed comments, names and changelog --- CHANGELOG.md | 3 + src/Grant/AbstractGrant.php | 5 +- .../RedirectUriValidator.php | 120 ++++++++++++++++++ .../RedirectUriValidatorInterface.php | 22 ++++ .../Rfc8252RedirectUriValidator.php | 68 ---------- ...rTest.php => RedirectUriValidatorTest.php} | 15 +-- 6 files changed, 155 insertions(+), 78 deletions(-) create mode 100644 src/RedirectUriValidators/RedirectUriValidator.php create mode 100644 src/RedirectUriValidators/RedirectUriValidatorInterface.php delete mode 100644 src/RedirectUriValidators/Rfc8252RedirectUriValidator.php rename tests/RedirectUriValidators/{Rfc8252RedirectUriValidatorTest.php => RedirectUriValidatorTest.php} (83%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9060032a..bc7443471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +The server will now validate redirect uris according to rfc8252 (PR #1203) + ### Fixed - The server will now only recognise and handle an authorization header if the value of the header is non-empty. This is to circumvent issues where some common frameworks set this header even if no value is present (PR #1170) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 3737049a8..7e5201829 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -24,7 +24,7 @@ use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; -use League\OAuth2\Server\RedirectUriValidators\Rfc8252RedirectUriValidator; +use League\OAuth2\Server\RedirectUriValidators\RedirectUriValidator; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; @@ -263,8 +263,9 @@ protected function validateRedirectUri( ClientEntityInterface $client, ServerRequestInterface $request ) { - $validator = new Rfc8252RedirectUriValidator($client); + $validator = new RedirectUriValidator($client); if (!$validator->validateRedirectUri($redirectUri)) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } } diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php new file mode 100644 index 000000000..b476608bb --- /dev/null +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -0,0 +1,120 @@ + + * @copyright Copyright (c) Sebastiano Degan + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\RedirectUriValidators; + +use League\OAuth2\Server\Entities\ClientEntityInterface; + +class RedirectUriValidator implements RedirectUriValidatorInterface +{ + /** + * @var ClientEntityInterface + */ + private $client; + + /** + * New validator instance for the given client + * + * @param ClientEntityInterface $client + */ + public function __construct(ClientEntityInterface $client) + { + $this->client = $client; + } + + /** + * Validates the redirect uri. + * + * @param string $redirectUri + * + * @return bool Return true if valid, false otherwise + */ + public function validateRedirectUri($redirectUri) + { + $parsedUrl = $this->parseUrl($redirectUri); + if ($this->isLoopbackAddress($parsedUrl)) { + return $this->allowDifferentPort($parsedUrl); + } else { + return $this->matchExactUri($redirectUri); + } + } + + /** + * Determine if the given url is a loopback url. + * + * @param array $parsedUri As returned by parseUrl + * + * @return bool + */ + private function isLoopbackAddress(array $parsedUri) + { + return $parsedUri['scheme'] === 'http' + && (\in_array($parsedUri['host'], ['127.0.0.1', '[::1]'], true)); + } + + /** + * Find an exact match among client uris + * + * @param string $redirectUri + * + * @return bool Return true if an exact match is found, false otherwise + */ + private function matchExactUri($redirectUri) + { + return \in_array($redirectUri, $this->getClientRedirectUris(), true); + } + + /** + * Find a match among client uris, allowing for different port numbers + * + * @param array $parsedUrl As returned by parseUrl + * + * @return bool Return true if a match is found, false otherwise + */ + private function allowDifferentPort(array $parsedUrl) + { + foreach ($this->getClientRedirectUris() as $clientRedirectUri) { + if ($parsedUrl == $this->parseUrl($clientRedirectUri)) { + return true; + } + } + + return false; + } + + /** + * Parse an url like \parse_url, excluding the port + * + * @return array + */ + private function parseUrl(string $url) + { + $parsedUrl = \parse_url($url); + unset($parsedUrl['port']); + + return $parsedUrl; + } + + /** + * Retrieve allowed client redirect uris + * + * @return array + */ + private function getClientRedirectUris() + { + $clientRedirectUri = $this->client->getRedirectUri(); + if (\is_string($clientRedirectUri)) { + return [$clientRedirectUri]; + } elseif (\is_array($clientRedirectUri)) { + return $clientRedirectUri; + } else { + return []; + } + } +} diff --git a/src/RedirectUriValidators/RedirectUriValidatorInterface.php b/src/RedirectUriValidators/RedirectUriValidatorInterface.php new file mode 100644 index 000000000..36989582b --- /dev/null +++ b/src/RedirectUriValidators/RedirectUriValidatorInterface.php @@ -0,0 +1,22 @@ + + * @copyright Copyright (c) Sebastiano Degan + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\RedirectUriValidators; + +interface RedirectUriValidatorInterface +{ + /** + * Validates the redirect uri. + * + * @param string $redirectUri + * + * @return bool Return true if valid, false otherwise + */ + public function validateRedirectUri($redirectUri); +} diff --git a/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php b/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php deleted file mode 100644 index 7259640fc..000000000 --- a/src/RedirectUriValidators/Rfc8252RedirectUriValidator.php +++ /dev/null @@ -1,68 +0,0 @@ -client = $client; - } - - public function validateRedirectUri(string $redirectUri) - { - $parsedUrl = $this->parseUrl($redirectUri); - if ($this->isLoopbackAddress($parsedUrl)) { - return $this->allowDifferentPort($parsedUrl); - } else { - return $this->matchFullUri($redirectUri); - } - } - - private function isLoopbackAddress(array $parsedUri) - { - return $parsedUri['scheme'] === 'http' - && (\in_array($parsedUri['host'], ['127.0.0.1', '[::1]'], true)); - } - - private function matchFullUri(string $redirectUri) - { - return \in_array($redirectUri, $this->getClientRedirectUris(), true); - } - - private function allowDifferentPort(array $parsedUrl) - { - foreach ($this->getClientRedirectUris() as $clientRedirectUri) { - if ($parsedUrl == $this->parseUrl($clientRedirectUri)) { - return true; - } - } - - return false; - } - - private function parseUrl(string $url) - { - $parsedUrl = \parse_url($url); - $parsedUrl['port'] = 80; - - return $parsedUrl; - } - - private function getClientRedirectUris() - { - $clientRedirectUri = $this->client->getRedirectUri(); - if (\is_string($clientRedirectUri)) { - return [$clientRedirectUri]; - } elseif (\is_array($clientRedirectUri)) { - return $clientRedirectUri; - } else { - return []; - } - } -} diff --git a/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php b/tests/RedirectUriValidators/RedirectUriValidatorTest.php similarity index 83% rename from tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php rename to tests/RedirectUriValidators/RedirectUriValidatorTest.php index 6b888d695..5b0236670 100644 --- a/tests/RedirectUriValidators/Rfc8252RedirectUriValidatorTest.php +++ b/tests/RedirectUriValidators/RedirectUriValidatorTest.php @@ -1,13 +1,12 @@ assertFalse( $validator->validateRedirectUri($redirectUri), @@ -35,7 +34,7 @@ public function testValidNonLoopbackUri() ]); $redirectUri = 'https://example.com:8443/endpoint'; - $validator = new Rfc8252RedirectUriValidator($client); + $validator = new RedirectUriValidator($client); $this->assertTrue( $validator->validateRedirectUri($redirectUri), @@ -50,7 +49,7 @@ public function testInvalidLoopbackUri() $redirectUri = 'http://127.0.0.1:8443/different/endpoint'; - $validator = new Rfc8252RedirectUriValidator($client); + $validator = new RedirectUriValidator($client); $this->assertFalse( $validator->validateRedirectUri($redirectUri), @@ -65,7 +64,7 @@ public function testValidLoopbackUri() $redirectUri = 'http://127.0.0.1:8080/endpoint'; - $validator = new Rfc8252RedirectUriValidator($client); + $validator = new RedirectUriValidator($client); $this->assertTrue( $validator->validateRedirectUri($redirectUri), @@ -80,7 +79,7 @@ public function testValidIpv4LoopbackUri() $redirectUri = 'http://[::1]:8080/endpoint'; - $validator = new Rfc8252RedirectUriValidator($client); + $validator = new RedirectUriValidator($client); $this->assertTrue( $validator->validateRedirectUri($redirectUri), From aa474035023b2dbf8207dc2cd8eedc0b15adb223 Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 16 Mar 2021 07:46:24 +0100 Subject: [PATCH 080/145] fixed test case name --- tests/RedirectUriValidators/RedirectUriValidatorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/RedirectUriValidators/RedirectUriValidatorTest.php b/tests/RedirectUriValidators/RedirectUriValidatorTest.php index 5b0236670..302432e5e 100644 --- a/tests/RedirectUriValidators/RedirectUriValidatorTest.php +++ b/tests/RedirectUriValidators/RedirectUriValidatorTest.php @@ -72,7 +72,7 @@ public function testValidLoopbackUri() ); } - public function testValidIpv4LoopbackUri() + public function testValidIpv6LoopbackUri() { $client = new ClientEntity(); $client->setRedirectUri('http://[::1]:8443/endpoint'); From d254681a0b53c67f1b139589fc5e131ca61c0e8d Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 16 Mar 2021 09:17:18 +0100 Subject: [PATCH 081/145] removed scalar typehint and updated doc --- src/RedirectUriValidators/RedirectUriValidator.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index b476608bb..a038fb984 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -91,9 +91,11 @@ private function allowDifferentPort(array $parsedUrl) /** * Parse an url like \parse_url, excluding the port * + * @param string $url + * * @return array */ - private function parseUrl(string $url) + private function parseUrl($url) { $parsedUrl = \parse_url($url); unset($parsedUrl['port']); From 5d26b2af6429916eacbb5f53b1e83d4b00427ace Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Thu, 1 Apr 2021 16:31:16 +0200 Subject: [PATCH 082/145] Validate expected type of input parameters --- src/Grant/AbstractGrant.php | 20 +++++-- src/Grant/AuthCodeGrant.php | 6 ++- src/Grant/ImplicitGrant.php | 10 +++- src/Grant/PasswordGrant.php | 4 +- src/Grant/RefreshTokenGrant.php | 2 +- tests/Grant/AbstractGrantTest.php | 90 +++++++++++++++++++++++++++++++ tests/Grant/AuthCodeGrantTest.php | 38 +++++++++++++ 7 files changed, 162 insertions(+), 8 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index c4797292a..f42391708 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -191,6 +191,10 @@ protected function validateClient(ServerRequestInterface $request) $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); if ($redirectUri !== null) { + if (!\is_string($redirectUri)) { + throw OAuthServerException::invalidRequest('redirect_uri'); + } + $this->validateRedirectUri($redirectUri, $client, $request); } @@ -238,12 +242,16 @@ protected function getClientCredentials(ServerRequestInterface $request) $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); - if (\is_null($clientId)) { + if (!\is_string($clientId)) { throw OAuthServerException::invalidRequest('client_id'); } $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword); + if ($clientSecret !== null && !\is_string($clientSecret)) { + throw OAuthServerException::invalidRequest('client_secret'); + } + return [$clientId, $clientSecret]; } @@ -287,10 +295,16 @@ protected function validateRedirectUri( */ public function validateScopes($scopes, $redirectUri = null) { - if (!\is_array($scopes)) { + if ($scopes === null) { + $scopes = []; + } elseif (\is_string($scopes)) { $scopes = $this->convertScopesQueryStringToArray($scopes); } + if (!\is_array($scopes)) { + throw OAuthServerException::invalidRequest('scope'); + } + $validScopes = []; foreach ($scopes as $scopeItem) { @@ -313,7 +327,7 @@ public function validateScopes($scopes, $redirectUri = null) * * @return array */ - private function convertScopesQueryStringToArray($scopes) + private function convertScopesQueryStringToArray(string $scopes) { return \array_filter(\explode(self::SCOPE_DELIMITER_STRING, \trim($scopes)), function ($scope) { return !empty($scope); diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 5cce97e51..474d1026b 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -106,7 +106,7 @@ public function respondToAccessTokenRequest( $encryptedAuthCode = $this->getRequestParameter('code', $request, null); - if ($encryptedAuthCode === null) { + if (!\is_string($encryptedAuthCode)) { throw OAuthServerException::invalidRequest('code'); } @@ -260,6 +260,10 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); if ($redirectUri !== null) { + if (!\is_string($redirectUri)) { + throw OAuthServerException::invalidRequest('redirect_uri'); + } + $this->validateRedirectUri($redirectUri, $client, $request); } elseif (empty($client->getRedirectUri()) || (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) { diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 17f289bfb..21e06ed41 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -120,7 +120,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $this->getServerParameter('PHP_AUTH_USER', $request) ); - if (\is_null($clientId)) { + if (!\is_string($clientId)) { throw OAuthServerException::invalidRequest('client_id'); } @@ -129,6 +129,10 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); if ($redirectUri !== null) { + if (!\is_string($redirectUri)) { + throw OAuthServerException::invalidRequest('redirect_uri'); + } + $this->validateRedirectUri($redirectUri, $client, $request); } elseif (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1 || empty($client->getRedirectUri())) { @@ -147,6 +151,10 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $stateParameter = $this->getQueryStringParameter('state', $request); + if ($stateParameter !== null && !\is_string($stateParameter)) { + throw OAuthServerException::invalidRequest('state'); + } + $authorizationRequest = new AuthorizationRequest(); $authorizationRequest->setGrantTypeId($this->getIdentifier()); $authorizationRequest->setClient($client); diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 7579fd0d2..db4eab9f5 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -84,13 +84,13 @@ protected function validateUser(ServerRequestInterface $request, ClientEntityInt { $username = $this->getRequestParameter('username', $request); - if (\is_null($username)) { + if (!\is_string($username)) { throw OAuthServerException::invalidRequest('username'); } $password = $this->getRequestParameter('password', $request); - if (\is_null($password)) { + if (!\is_string($password)) { throw OAuthServerException::invalidRequest('password'); } diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 19945f5ce..837fead8a 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -92,7 +92,7 @@ public function respondToAccessTokenRequest( protected function validateOldRefreshToken(ServerRequestInterface $request, $clientId) { $encryptedRefreshToken = $this->getRequestParameter('refresh_token', $request); - if (\is_null($encryptedRefreshToken)) { + if (!\is_string($encryptedRefreshToken)) { throw OAuthServerException::invalidRequest('refresh_token'); } diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 93f95fa5c..48307cce3 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -89,6 +89,70 @@ public function testHttpBasicNoColon() $this->assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); } + public function testGetClientCredentialsClientIdNotAString() + { + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + + /** @var AbstractGrant $grantMock */ + $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + $grantMock->setClientRepository($clientRepositoryMock); + + $abstractGrantReflection = new \ReflectionClass($grantMock); + + $serverRequest = new ServerRequest( + [], + [], + null, + 'POST', + 'php://input', + [], + [], + [], + [ + 'client_id' => ['not', 'a', 'string'], + 'client_secret' => 'client_secret', + ] + ); + $getClientCredentialsMethod = $abstractGrantReflection->getMethod('getClientCredentials'); + $getClientCredentialsMethod->setAccessible(true); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $getClientCredentialsMethod->invoke($grantMock, $serverRequest, true, true); + } + + public function testGetClientCredentialsClientSecretNotAString() + { + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + + /** @var AbstractGrant $grantMock */ + $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + $grantMock->setClientRepository($clientRepositoryMock); + + $abstractGrantReflection = new \ReflectionClass($grantMock); + + $serverRequest = new ServerRequest( + [], + [], + null, + 'POST', + 'php://input', + [], + [], + [], + [ + 'client_id' => 'client_id', + 'client_secret' => ['not', 'a', 'string'], + ] + ); + $getClientCredentialsMethod = $abstractGrantReflection->getMethod('getClientCredentials'); + $getClientCredentialsMethod->setAccessible(true); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $getClientCredentialsMethod->invoke($grantMock, $serverRequest, true, true); + } + public function testValidateClientPublic() { $client = new ClientEntity(); @@ -261,6 +325,32 @@ public function testValidateClientInvalidRedirectUriArray() $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } + public function testValidateClientMalformedRedirectUri() + { + $client = new ClientEntity(); + $client->setRedirectUri('http://foo/bar'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + /** @var AbstractGrant $grantMock */ + $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + $grantMock->setClientRepository($clientRepositoryMock); + + $abstractGrantReflection = new \ReflectionClass($grantMock); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'redirect_uri' => ['not', 'a', 'string'], + ]); + + $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); + $validateClientMethod->setAccessible(true); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $validateClientMethod->invoke($grantMock, $serverRequest, true, true); + } + public function testValidateClientBadClient() { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index a080da317..860c6e863 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -1112,6 +1112,44 @@ public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode } } + public function testRespondToAccessTokenRequestWithAuthCodeNotAString() + { + $client = new ClientEntity(); + $client->setRedirectUri('http://foo/bar'); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $grant = new AuthCodeGrant( + $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setEncryptionKey($this->cryptStub->getKey()); + + $request = new ServerRequest( + [], + [], + null, + 'POST', + 'php://input', + [], + [], + [], + [ + 'grant_type' => 'authorization_code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://foo/bar', + 'code' => ['not', 'a', 'string'], + ] + ); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); + } + public function testRespondToAccessTokenRequestExpiredCode() { $client = new ClientEntity(); From 7157bff6fefc3f8f822b48f2aeb16c3c463eaf61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Unger?= Date: Tue, 6 Apr 2021 22:18:57 +0200 Subject: [PATCH 083/145] Added token events --- src/Grant/AuthCodeGrant.php | 6 +++-- src/Grant/ClientCredentialsGrant.php | 3 ++- src/Grant/PasswordGrant.php | 6 +++-- src/Grant/RefreshTokenGrant.php | 6 +++-- src/RequestAccessTokenEvent.php | 40 ++++++++++++++++++++++++++++ src/RequestRefreshTokenEvent.php | 40 ++++++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 src/RequestAccessTokenEvent.php create mode 100644 src/RequestRefreshTokenEvent.php diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 5cce97e51..f72db6387 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -20,7 +20,9 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; +use League\OAuth2\Server\RequestAccessTokenEvent; use League\OAuth2\Server\RequestEvent; +use League\OAuth2\Server\RequestRefreshTokenEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; @@ -162,14 +164,14 @@ public function respondToAccessTokenRequest( // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes); - $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); + $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); // Issue and persist new refresh token if given $refreshToken = $this->issueRefreshToken($accessToken); if ($refreshToken !== null) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); + $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken)); $responseType->setRefreshToken($refreshToken); } diff --git a/src/Grant/ClientCredentialsGrant.php b/src/Grant/ClientCredentialsGrant.php index 691f421bc..d342b269f 100644 --- a/src/Grant/ClientCredentialsGrant.php +++ b/src/Grant/ClientCredentialsGrant.php @@ -13,6 +13,7 @@ use DateInterval; use League\OAuth2\Server\Exception\OAuthServerException; +use League\OAuth2\Server\RequestAccessTokenEvent; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; @@ -52,7 +53,7 @@ public function respondToAccessTokenRequest( $accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $finalizedScopes); // Send event to emitter - $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); + $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); // Inject access token into response type $responseType->setAccessToken($accessToken); diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 7579fd0d2..778ad5cb6 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -17,7 +17,9 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; +use League\OAuth2\Server\RequestAccessTokenEvent; use League\OAuth2\Server\RequestEvent; +use League\OAuth2\Server\RequestRefreshTokenEvent; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; @@ -58,14 +60,14 @@ public function respondToAccessTokenRequest( // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes); - $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); + $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); // Issue and persist new refresh token if given $refreshToken = $this->issueRefreshToken($accessToken); if ($refreshToken !== null) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); + $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken)); $responseType->setRefreshToken($refreshToken); } diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 19945f5ce..bb5e46607 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -15,7 +15,9 @@ use Exception; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; +use League\OAuth2\Server\RequestAccessTokenEvent; use League\OAuth2\Server\RequestEvent; +use League\OAuth2\Server\RequestRefreshTokenEvent; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; @@ -67,14 +69,14 @@ public function respondToAccessTokenRequest( // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes); - $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); + $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); // Issue and persist new refresh token if given $refreshToken = $this->issueRefreshToken($accessToken); if ($refreshToken !== null) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); + $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken)); $responseType->setRefreshToken($refreshToken); } diff --git a/src/RequestAccessTokenEvent.php b/src/RequestAccessTokenEvent.php new file mode 100644 index 000000000..99d17bf36 --- /dev/null +++ b/src/RequestAccessTokenEvent.php @@ -0,0 +1,40 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server; + +use League\OAuth2\Server\Entities\AccessTokenEntityInterface; +use Psr\Http\Message\ServerRequestInterface; + +class RequestAccessTokenEvent extends RequestEvent +{ + /** + * @var AccessTokenEntityInterface + */ + private $accessToken; + + /** + * @param string $name + * @param ServerRequestInterface $request + */ + public function __construct($name, ServerRequestInterface $request, AccessTokenEntityInterface $accessToken) + { + parent::__construct($name, $request); + $this->accessToken = $accessToken; + } + + /** + * @return AccessTokenEntityInterface + * @codeCoverageIgnore + */ + public function getAccessToken() + { + return $this->accessToken; + } +} diff --git a/src/RequestRefreshTokenEvent.php b/src/RequestRefreshTokenEvent.php new file mode 100644 index 000000000..0682e57f5 --- /dev/null +++ b/src/RequestRefreshTokenEvent.php @@ -0,0 +1,40 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server; + +use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; +use Psr\Http\Message\ServerRequestInterface; + +class RequestRefreshTokenEvent extends RequestEvent +{ + /** + * @var RefreshTokenEntityInterface + */ + private $refreshToken; + + /** + * @param string $name + * @param ServerRequestInterface $request + */ + public function __construct($name, ServerRequestInterface $request, RefreshTokenEntityInterface $refreshToken) + { + parent::__construct($name, $request); + $this->refreshToken = $refreshToken; + } + + /** + * @return RefreshTokenEntityInterface + * @codeCoverageIgnore + */ + public function getRefreshToken() + { + return $this->refreshToken; + } +} From e7a69a9b1c43ff6f6816930effc25c9d5229f8ed Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Fri, 9 Apr 2021 21:24:36 +0200 Subject: [PATCH 084/145] clearer function name & doc --- src/RedirectUriValidators/RedirectUriValidator.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index a038fb984..c066fa0cf 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -38,7 +38,7 @@ public function __construct(ClientEntityInterface $client) public function validateRedirectUri($redirectUri) { $parsedUrl = $this->parseUrl($redirectUri); - if ($this->isLoopbackAddress($parsedUrl)) { + if ($this->isLoopbackUri($parsedUrl)) { return $this->allowDifferentPort($parsedUrl); } else { return $this->matchExactUri($redirectUri); @@ -46,13 +46,15 @@ public function validateRedirectUri($redirectUri) } /** - * Determine if the given url is a loopback url. + * According to section 7.3 of rfc8252, loopback uris are: + * - "http://127.0.0.1:{port}/{path}" for IPv4 + * - "http://[::1]:{port}/{path}" for IPv6 * * @param array $parsedUri As returned by parseUrl * * @return bool */ - private function isLoopbackAddress(array $parsedUri) + private function isLoopbackUri(array $parsedUri) { return $parsedUri['scheme'] === 'http' && (\in_array($parsedUri['host'], ['127.0.0.1', '[::1]'], true)); From 108f13b054b2e8dc49743fae0724e1517c6e2deb Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 17 Apr 2021 11:06:31 +0100 Subject: [PATCH 085/145] update changelog to add details of PR 1210 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9060032a..3f473041f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Fixed - The server will now only recognise and handle an authorization header if the value of the header is non-empty. This is to circumvent issues where some common frameworks set this header even if no value is present (PR #1170) +- Added type validation for redirect uri, client ID, client secret, scopes, auth code, state, username, and password inputs (PR #1210) ## [8.2.4] - released 2020-12-10 ### Fixed From 1fb3387b3b956752b6ee81944bed2705ea66f480 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 17 Apr 2021 11:53:14 +0100 Subject: [PATCH 086/145] update changelog to include details for PR 1211 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f473041f..2b31ea9ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Events emitted now include the refresh token and access token payloads (PR #1211) + ### Fixed - The server will now only recognise and handle an authorization header if the value of the header is non-empty. This is to circumvent issues where some common frameworks set this header even if no value is present (PR #1170) - Added type validation for redirect uri, client ID, client secret, scopes, auth code, state, username, and password inputs (PR #1210) From cb17576661ce700ddf7bafde716c1481b6f58e32 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 17 Apr 2021 18:51:13 +0100 Subject: [PATCH 087/145] Remove ValidAt constraint use case --- src/AuthorizationValidators/BearerTokenValidator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 57a0a097e..20dc0857b 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -17,7 +17,6 @@ use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\StrictValidAt; -use Lcobucci\JWT\Validation\Constraint\ValidAt; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; From a60313319786ca17973791dab6cf99f13b43d976 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 18 Apr 2021 23:35:58 +0100 Subject: [PATCH 088/145] Restrict lcobucci/jwt to the 4.0.x branch to ensure backward compat --- composer.json | 2 +- src/AuthorizationValidators/BearerTokenValidator.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 2d50e88e4..ee3eef51b 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": "^7.2 || ^8.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.4 || ^4.1", + "lcobucci/jwt": "^3.4 || ~4.0.0", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "ext-json": "*" diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 20dc0857b..bab8919f6 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -16,7 +16,7 @@ use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Validation\Constraint\SignedWith; -use Lcobucci\JWT\Validation\Constraint\StrictValidAt; +use Lcobucci\JWT\Validation\Constraint\ValidAt; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; @@ -74,7 +74,7 @@ private function initJwtConfiguration() ); $this->jwtConfiguration->setValidationConstraints( - new StrictValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), + new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), new SignedWith(new Sha256(), LocalFileReference::file($this->publicKey->getKeyPath())) ); } From fcf88339d8fb55be22eb63164bef054bb17b907c Mon Sep 17 00:00:00 2001 From: Eugene Borovov Date: Tue, 20 Apr 2021 00:40:51 +0700 Subject: [PATCH 089/145] Validate key with phpseclib3 Signed-off-by: Eugene Borovov --- composer.json | 3 +- src/CryptKey.php | 90 +++++++++++++++++++++++++++--------- tests/Utils/CryptKeyTest.php | 15 ------ 3 files changed, 69 insertions(+), 39 deletions(-) diff --git a/composer.json b/composer.json index ee3eef51b..1a2642aca 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "lcobucci/jwt": "^3.4 || ~4.0.0", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", - "ext-json": "*" + "ext-json": "*", + "phpseclib/phpseclib": "~3.0" }, "require-dev": { "phpunit/phpunit": "^8.5.13", diff --git a/src/CryptKey.php b/src/CryptKey.php index e2077f700..a226d5f97 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -12,12 +12,14 @@ namespace League\OAuth2\Server; use LogicException; +use phpseclib3\Crypt\EC; +use phpseclib3\Crypt\RSA; +use phpseclib3\Exception\NoKeyLoadedException; use RuntimeException; class CryptKey { - const RSA_KEY_PATTERN = - '/^(-----BEGIN (RSA )?(PUBLIC|PRIVATE) KEY-----)\R.*(-----END (RSA )?(PUBLIC|PRIVATE) KEY-----)\R?$/s'; + private const FILE_PREFIX = 'file://'; /** * @var string @@ -36,36 +38,45 @@ class CryptKey */ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = true) { - if ($rsaMatch = \preg_match(static::RSA_KEY_PATTERN, $keyPath)) { - $keyPath = $this->saveKeyToFile($keyPath); - } elseif ($rsaMatch === false) { - throw new \RuntimeException( - \sprintf('PCRE error [%d] encountered during key match attempt', \preg_last_error()) - ); + $this->keyPath = $keyPath; + $this->passPhrase = $passPhrase; + + if (is_file($this->keyPath) && !$this->isFilePath()) { + $this->keyPath = self::FILE_PREFIX . $this->keyPath; } - if (\strpos($keyPath, 'file://') !== 0) { - $keyPath = 'file://' . $keyPath; + if ($this->isFilePath()) { + if (!\file_exists($this->keyPath) || !\is_readable($this->keyPath)) { + throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $this->keyPath)); + } + + $contents = file_get_contents($this->keyPath); + } else { + $contents = $keyPath; } - if (!\file_exists($keyPath) || !\is_readable($keyPath)) { - throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $keyPath)); + if ($this->isValidKey($contents, $this->passPhrase ?? '')) { + if (!$this->isFilePath()) { + $this->keyPath = $this->saveKeyToFile($keyPath); + } + } else { + throw new LogicException('Unable to read key' . ($this->isFilePath() ? " from file $keyPath" : '' )); } if ($keyPermissionsCheck === true) { // Verify the permissions of the key - $keyPathPerms = \decoct(\fileperms($keyPath) & 0777); + $keyPathPerms = \decoct(\fileperms($this->keyPath) & 0777); if (\in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) { - \trigger_error(\sprintf( - 'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s', - $keyPath, - $keyPathPerms - ), E_USER_NOTICE); + \trigger_error( + \sprintf( + 'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s', + $this->keyPath, + $keyPathPerms + ), + E_USER_NOTICE + ); } } - - $this->keyPath = $keyPath; - $this->passPhrase = $passPhrase; } /** @@ -81,7 +92,7 @@ private function saveKeyToFile($key) $keyPath = $tmpDir . '/' . \sha1($key) . '.key'; if (\file_exists($keyPath)) { - return 'file://' . $keyPath; + return self::FILE_PREFIX . $keyPath; } if (\file_put_contents($keyPath, $key) === false) { @@ -96,7 +107,40 @@ private function saveKeyToFile($key) // @codeCoverageIgnoreEnd } - return 'file://' . $keyPath; + return self::FILE_PREFIX . $keyPath; + } + + /** + * Validate key contents. + * + * @param string $contents + * @param string $passPhrase + * + * @return bool + */ + private function isValidKey($contents, $passPhrase) + { + try { + RSA::load($contents, $passPhrase); + return true; + } catch (NoKeyLoadedException $e) {} + + try { + EC::load($contents, $passPhrase); + return true; + } catch (NoKeyLoadedException $e) {} + + return false; + } + + /** + * Checks whether the key is a file. + * + * @return bool + */ + private function isFilePath() + { + return \strpos($this->keyPath, self::FILE_PREFIX) === 0; } /** diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index 667daaf20..d5ab0caed 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -51,19 +51,4 @@ public function testKeyFileCreation() $key->getKeyPath() ); } - - /** - * Test whether we get a RuntimeException if a PCRE error is encountered. - * - * @link https://www.php.net/manual/en/function.preg-last-error.php - */ - public function testPcreErrorExceptions() - { - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessageMatches('/^PCRE error/'); - - new class('foobar foobar foobar') extends CryptKey { - const RSA_KEY_PATTERN = '/(?:\D+|<\d+>)*[!?]/'; - }; - } } From d3dcf232ae205a78db2361020190ea3d185d12a4 Mon Sep 17 00:00:00 2001 From: Eugene Borovov Date: Tue, 20 Apr 2021 01:55:27 +0700 Subject: [PATCH 090/145] Fix code style --- src/CryptKey.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index a226d5f97..cc46ec48d 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -41,7 +41,7 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = $this->keyPath = $keyPath; $this->passPhrase = $passPhrase; - if (is_file($this->keyPath) && !$this->isFilePath()) { + if (\is_file($this->keyPath) && !$this->isFilePath()) { $this->keyPath = self::FILE_PREFIX . $this->keyPath; } @@ -50,7 +50,7 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $this->keyPath)); } - $contents = file_get_contents($this->keyPath); + $contents = \file_get_contents($this->keyPath); } else { $contents = $keyPath; } @@ -60,7 +60,7 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = $this->keyPath = $this->saveKeyToFile($keyPath); } } else { - throw new LogicException('Unable to read key' . ($this->isFilePath() ? " from file $keyPath" : '' )); + throw new LogicException('Unable to read key' . ($this->isFilePath() ? " from file $keyPath" : '')); } if ($keyPermissionsCheck === true) { @@ -122,13 +122,17 @@ private function isValidKey($contents, $passPhrase) { try { RSA::load($contents, $passPhrase); + return true; - } catch (NoKeyLoadedException $e) {} + } catch (NoKeyLoadedException $e) { + } try { EC::load($contents, $passPhrase); + return true; - } catch (NoKeyLoadedException $e) {} + } catch (NoKeyLoadedException $e) { + } return false; } From b4f7aa71903aede612aa873b6454336b51eec376 Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 20 Apr 2021 07:29:58 +0200 Subject: [PATCH 091/145] fixed copyright --- src/RedirectUriValidators/RedirectUriValidator.php | 2 +- src/RedirectUriValidators/RedirectUriValidatorInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index c066fa0cf..05642352f 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -1,7 +1,7 @@ - * @copyright Copyright (c) Sebastiano Degan + * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server diff --git a/src/RedirectUriValidators/RedirectUriValidatorInterface.php b/src/RedirectUriValidators/RedirectUriValidatorInterface.php index 36989582b..d039085ab 100644 --- a/src/RedirectUriValidators/RedirectUriValidatorInterface.php +++ b/src/RedirectUriValidators/RedirectUriValidatorInterface.php @@ -1,7 +1,7 @@ - * @copyright Copyright (c) Sebastiano Degan + * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server From fa7f775cac491b220410d6ff5a22dd1959eef796 Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 20 Apr 2021 07:33:11 +0200 Subject: [PATCH 092/145] removed unnecessary else blocks --- src/RedirectUriValidators/RedirectUriValidator.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index 05642352f..e0cc0f4f6 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -40,9 +40,9 @@ public function validateRedirectUri($redirectUri) $parsedUrl = $this->parseUrl($redirectUri); if ($this->isLoopbackUri($parsedUrl)) { return $this->allowDifferentPort($parsedUrl); - } else { - return $this->matchExactUri($redirectUri); } + + return $this->matchExactUri($redirectUri); } /** @@ -117,8 +117,8 @@ private function getClientRedirectUris() return [$clientRedirectUri]; } elseif (\is_array($clientRedirectUri)) { return $clientRedirectUri; - } else { - return []; } + + return []; } } From 7c446b6311d18d1309f1b110911e2aa75e9432a1 Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 20 Apr 2021 07:52:35 +0200 Subject: [PATCH 093/145] using redirectUri instead of client as constructor argument --- src/Grant/AbstractGrant.php | 2 +- .../RedirectUriValidator.php | 41 +++++++----------- .../RedirectUriValidatorTest.php | 43 +++++++------------ 3 files changed, 31 insertions(+), 55 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 7e5201829..62e74d2cb 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -263,7 +263,7 @@ protected function validateRedirectUri( ClientEntityInterface $client, ServerRequestInterface $request ) { - $validator = new RedirectUriValidator($client); + $validator = new RedirectUriValidator($client->getRedirectUri()); if (!$validator->validateRedirectUri($redirectUri)) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index e0cc0f4f6..7bab28ba2 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -14,18 +14,24 @@ class RedirectUriValidator implements RedirectUriValidatorInterface { /** - * @var ClientEntityInterface + * @var array */ - private $client; + private $allowedRedirectUris; /** - * New validator instance for the given client + * New validator instance for the given uri * - * @param ClientEntityInterface $client + * @param string|array $allowedRedirectUris */ - public function __construct(ClientEntityInterface $client) + public function __construct($allowedRedirectUri) { - $this->client = $client; + if (\is_string($allowedRedirectUri)) { + $this->allowedRedirectUris = [ $allowedRedirectUri ]; + } elseif (\is_array($allowedRedirectUri)) { + $this->allowedRedirectUris = $allowedRedirectUri; + } else { + $this->allowedRedirectUris = []; + } } /** @@ -69,7 +75,7 @@ private function isLoopbackUri(array $parsedUri) */ private function matchExactUri($redirectUri) { - return \in_array($redirectUri, $this->getClientRedirectUris(), true); + return \in_array($redirectUri, $this->allowedRedirectUris, true); } /** @@ -81,8 +87,8 @@ private function matchExactUri($redirectUri) */ private function allowDifferentPort(array $parsedUrl) { - foreach ($this->getClientRedirectUris() as $clientRedirectUri) { - if ($parsedUrl == $this->parseUrl($clientRedirectUri)) { + foreach ($this->allowedRedirectUris as $redirectUri) { + if ($parsedUrl == $this->parseUrl($redirectUri)) { return true; } } @@ -104,21 +110,4 @@ private function parseUrl($url) return $parsedUrl; } - - /** - * Retrieve allowed client redirect uris - * - * @return array - */ - private function getClientRedirectUris() - { - $clientRedirectUri = $this->client->getRedirectUri(); - if (\is_string($clientRedirectUri)) { - return [$clientRedirectUri]; - } elseif (\is_array($clientRedirectUri)) { - return $clientRedirectUri; - } - - return []; - } } diff --git a/tests/RedirectUriValidators/RedirectUriValidatorTest.php b/tests/RedirectUriValidators/RedirectUriValidatorTest.php index 302432e5e..5af657f25 100644 --- a/tests/RedirectUriValidators/RedirectUriValidatorTest.php +++ b/tests/RedirectUriValidators/RedirectUriValidatorTest.php @@ -10,79 +10,66 @@ class RedirectUriValidatorTest extends TestCase { public function testInvalidNonLoopbackUri() { - $client = new ClientEntity(); - $client->setRedirectUri([ + $validator = new RedirectUriValidator([ 'https://example.com:8443/endpoint', 'https://example.com/different/endpoint', ]); - $redirectUri = 'https://example.com/endpoint'; - $validator = new RedirectUriValidator($client); + $invalidRedirectUri = 'https://example.com/endpoint'; $this->assertFalse( - $validator->validateRedirectUri($redirectUri), + $validator->validateRedirectUri($invalidRedirectUri), 'Non loopback URI must match in every part' ); } public function testValidNonLoopbackUri() { - $client = new ClientEntity(); - $client->setRedirectUri([ + $validator = new RedirectUriValidator([ 'https://example.com:8443/endpoint', 'https://example.com/different/endpoint', ]); - $redirectUri = 'https://example.com:8443/endpoint'; - $validator = new RedirectUriValidator($client); + $validRedirectUri = 'https://example.com:8443/endpoint'; $this->assertTrue( - $validator->validateRedirectUri($redirectUri), + $validator->validateRedirectUri($validRedirectUri), 'Redirect URI must be valid when matching in every part' ); } public function testInvalidLoopbackUri() { - $client = new ClientEntity(); - $client->setRedirectUri('http://127.0.0.1:8443/endpoint'); + $validator = new RedirectUriValidator('http://127.0.0.1:8443/endpoint'); - $redirectUri = 'http://127.0.0.1:8443/different/endpoint'; - - $validator = new RedirectUriValidator($client); + $invalidRedirectUri = 'http://127.0.0.1:8443/different/endpoint'; $this->assertFalse( - $validator->validateRedirectUri($redirectUri), + $validator->validateRedirectUri($invalidRedirectUri), 'Valid loopback redirect URI can change only the port number' ); } public function testValidLoopbackUri() { - $client = new ClientEntity(); - $client->setRedirectUri('http://127.0.0.1:8443/endpoint'); - - $redirectUri = 'http://127.0.0.1:8080/endpoint'; + $validator = new RedirectUriValidator('http://127.0.0.1:8443/endpoint'); - $validator = new RedirectUriValidator($client); + $validRedirectUri = 'http://127.0.0.1:8080/endpoint'; $this->assertTrue( - $validator->validateRedirectUri($redirectUri), + $validator->validateRedirectUri($validRedirectUri), 'Loopback redirect URI can change the port number' ); } public function testValidIpv6LoopbackUri() { - $client = new ClientEntity(); - $client->setRedirectUri('http://[::1]:8443/endpoint'); - - $redirectUri = 'http://[::1]:8080/endpoint'; + $validator = new RedirectUriValidator('http://[::1]:8443/endpoint'); - $validator = new RedirectUriValidator($client); + $validRedirectUri = 'http://[::1]:8080/endpoint'; $this->assertTrue( - $validator->validateRedirectUri($redirectUri), + $validator->validateRedirectUri($validRedirectUri), 'Loopback redirect URI can change the port number' ); } From 18904e56b2f600e2b31856e8b340b4c30b085ca2 Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 20 Apr 2021 07:54:29 +0200 Subject: [PATCH 094/145] using strict array equality --- src/RedirectUriValidators/RedirectUriValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index 7bab28ba2..79436fd69 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -88,7 +88,7 @@ private function matchExactUri($redirectUri) private function allowDifferentPort(array $parsedUrl) { foreach ($this->allowedRedirectUris as $redirectUri) { - if ($parsedUrl == $this->parseUrl($redirectUri)) { + if ($parsedUrl === $this->parseUrl($redirectUri)) { return true; } } From 736ad06935f98b2781572d17449e1b4b94f4c83c Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 20 Apr 2021 07:56:50 +0200 Subject: [PATCH 095/145] fixed chagelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc7443471..c4c94360d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] -### Fixed +### Added The server will now validate redirect uris according to rfc8252 (PR #1203) ### Fixed From 86274adb8f8aa2d26ff819e020e6c7c166ee7f97 Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 20 Apr 2021 08:05:22 +0200 Subject: [PATCH 096/145] clearer function names --- .../RedirectUriValidator.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index 79436fd69..97501ce76 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -9,8 +9,6 @@ namespace League\OAuth2\Server\RedirectUriValidators; -use League\OAuth2\Server\Entities\ClientEntityInterface; - class RedirectUriValidator implements RedirectUriValidatorInterface { /** @@ -43,9 +41,9 @@ public function __construct($allowedRedirectUri) */ public function validateRedirectUri($redirectUri) { - $parsedUrl = $this->parseUrl($redirectUri); + $parsedUrl = $this->parseUrlAndRemovePort($redirectUri); if ($this->isLoopbackUri($parsedUrl)) { - return $this->allowDifferentPort($parsedUrl); + return $this->matchUriExcludingPort($parsedUrl); } return $this->matchExactUri($redirectUri); @@ -56,7 +54,7 @@ public function validateRedirectUri($redirectUri) * - "http://127.0.0.1:{port}/{path}" for IPv4 * - "http://[::1]:{port}/{path}" for IPv6 * - * @param array $parsedUri As returned by parseUrl + * @param array $parsedUri As returned by parseUrlAndRemovePort * * @return bool */ @@ -81,14 +79,14 @@ private function matchExactUri($redirectUri) /** * Find a match among client uris, allowing for different port numbers * - * @param array $parsedUrl As returned by parseUrl + * @param array $parsedUrl As returned by parseUrlAndRemovePort * * @return bool Return true if a match is found, false otherwise */ - private function allowDifferentPort(array $parsedUrl) + private function matchUriExcludingPort(array $parsedUrl) { foreach ($this->allowedRedirectUris as $redirectUri) { - if ($parsedUrl === $this->parseUrl($redirectUri)) { + if ($parsedUrl === $this->parseUrlAndRemovePort($redirectUri)) { return true; } } @@ -103,7 +101,7 @@ private function allowDifferentPort(array $parsedUrl) * * @return array */ - private function parseUrl($url) + private function parseUrlAndRemovePort($url) { $parsedUrl = \parse_url($url); unset($parsedUrl['port']); From 3d5a9b26ad3565ec7088c0cdd778f4a9867dde3d Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 20 Apr 2021 08:15:28 +0200 Subject: [PATCH 097/145] minor refactoring to improve clarity --- .../RedirectUriValidator.php | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index 97501ce76..f4ccf55ce 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -41,9 +41,8 @@ public function __construct($allowedRedirectUri) */ public function validateRedirectUri($redirectUri) { - $parsedUrl = $this->parseUrlAndRemovePort($redirectUri); - if ($this->isLoopbackUri($parsedUrl)) { - return $this->matchUriExcludingPort($parsedUrl); + if ($this->isLoopbackUri($redirectUri)) { + return $this->matchUriExcludingPort($redirectUri); } return $this->matchExactUri($redirectUri); @@ -54,18 +53,20 @@ public function validateRedirectUri($redirectUri) * - "http://127.0.0.1:{port}/{path}" for IPv4 * - "http://[::1]:{port}/{path}" for IPv6 * - * @param array $parsedUri As returned by parseUrlAndRemovePort + * @param string $redirectUri * * @return bool */ - private function isLoopbackUri(array $parsedUri) + private function isLoopbackUri($redirectUri) { - return $parsedUri['scheme'] === 'http' - && (\in_array($parsedUri['host'], ['127.0.0.1', '[::1]'], true)); + $parsedUrl = parse_url($redirectUri); + + return $parsedUrl['scheme'] === 'http' + && (\in_array($parsedUrl['host'], ['127.0.0.1', '[::1]'], true)); } /** - * Find an exact match among client uris + * Find an exact match among allowed uris * * @param string $redirectUri * @@ -77,14 +78,16 @@ private function matchExactUri($redirectUri) } /** - * Find a match among client uris, allowing for different port numbers + * Find a match among allowed uris, allowing for different port numbers * - * @param array $parsedUrl As returned by parseUrlAndRemovePort + * @param string $redirectUri * * @return bool Return true if a match is found, false otherwise */ - private function matchUriExcludingPort(array $parsedUrl) + private function matchUriExcludingPort($redirectUri) { + $parsedUrl = $this->parseUrlAndRemovePort($redirectUri); + foreach ($this->allowedRedirectUris as $redirectUri) { if ($parsedUrl === $this->parseUrlAndRemovePort($redirectUri)) { return true; From a28efcfab96d27e8d0c79f0082a9786cf7686d57 Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 20 Apr 2021 08:19:01 +0200 Subject: [PATCH 098/145] fixed style issues --- src/RedirectUriValidators/RedirectUriValidator.php | 4 ++-- tests/RedirectUriValidators/RedirectUriValidatorTest.php | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index f4ccf55ce..e2ce5e3eb 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -24,7 +24,7 @@ class RedirectUriValidator implements RedirectUriValidatorInterface public function __construct($allowedRedirectUri) { if (\is_string($allowedRedirectUri)) { - $this->allowedRedirectUris = [ $allowedRedirectUri ]; + $this->allowedRedirectUris = [$allowedRedirectUri]; } elseif (\is_array($allowedRedirectUri)) { $this->allowedRedirectUris = $allowedRedirectUri; } else { @@ -59,7 +59,7 @@ public function validateRedirectUri($redirectUri) */ private function isLoopbackUri($redirectUri) { - $parsedUrl = parse_url($redirectUri); + $parsedUrl = \parse_url($redirectUri); return $parsedUrl['scheme'] === 'http' && (\in_array($parsedUrl['host'], ['127.0.0.1', '[::1]'], true)); diff --git a/tests/RedirectUriValidators/RedirectUriValidatorTest.php b/tests/RedirectUriValidators/RedirectUriValidatorTest.php index 5af657f25..0c516cda0 100644 --- a/tests/RedirectUriValidators/RedirectUriValidatorTest.php +++ b/tests/RedirectUriValidators/RedirectUriValidatorTest.php @@ -3,7 +3,6 @@ namespace LeagueTests\RedirectUriValidators; use League\OAuth2\Server\RedirectUriValidators\RedirectUriValidator; -use LeagueTests\Stubs\ClientEntity; use PHPUnit\Framework\TestCase; class RedirectUriValidatorTest extends TestCase From 58bd2ba974d5402baed3202f0a545e748dce0cf7 Mon Sep 17 00:00:00 2001 From: Sebastiano Degan Date: Tue, 20 Apr 2021 08:22:29 +0200 Subject: [PATCH 099/145] fixed variable name conflict --- src/RedirectUriValidators/RedirectUriValidator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index e2ce5e3eb..2cb020801 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -88,8 +88,8 @@ private function matchUriExcludingPort($redirectUri) { $parsedUrl = $this->parseUrlAndRemovePort($redirectUri); - foreach ($this->allowedRedirectUris as $redirectUri) { - if ($parsedUrl === $this->parseUrlAndRemovePort($redirectUri)) { + foreach ($this->allowedRedirectUris as $allowedRedirectUri) { + if ($parsedUrl === $this->parseUrlAndRemovePort($allowedRedirectUri)) { return true; } } From 39b862a1aa0ddf863e7b6fb01ff8db5d7e78a09f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 19:38:43 +0000 Subject: [PATCH 100/145] Upgrade to GitHub-native Dependabot --- .github/dependabot.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..e54b58bfe --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: composer + directory: "/" + schedule: + interval: daily + time: "11:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: league/event + versions: + - 3.0.0 From 2d5413e90bc9875ffa30f60f68de8af9250388ce Mon Sep 17 00:00:00 2001 From: ElisDN Date: Mon, 3 May 2021 15:05:05 +0300 Subject: [PATCH 101/145] Ignore PHPUnit cache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e22101f24..a859c02f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /vendor /composer.lock phpunit.xml +.phpunit.result.cache .idea /examples/vendor examples/public.key From 9ccc922ef97573f548c57468e4e07bc13ac29dc0 Mon Sep 17 00:00:00 2001 From: ElisDN Date: Tue, 4 May 2021 17:06:15 +0300 Subject: [PATCH 102/145] Fix request variable name --- tests/AuthorizationValidators/BearerTokenValidatorTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/AuthorizationValidators/BearerTokenValidatorTest.php b/tests/AuthorizationValidators/BearerTokenValidatorTest.php index 705368e4c..17319bda4 100644 --- a/tests/AuthorizationValidators/BearerTokenValidatorTest.php +++ b/tests/AuthorizationValidators/BearerTokenValidatorTest.php @@ -38,9 +38,9 @@ public function testBearerTokenValidatorAcceptsValidToken() $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $validJwt->toString())); - $response = $bearerTokenValidator->validateAuthorization($request); + $validRequest = $bearerTokenValidator->validateAuthorization($request); - $this->assertArrayHasKey('authorization', $response->getHeaders()); + $this->assertArrayHasKey('authorization', $validRequest->getHeaders()); } public function testBearerTokenValidatorRejectsExpiredToken() From fa790881cd0fdfbbe801f2b52b5c99a10906d2e2 Mon Sep 17 00:00:00 2001 From: Eugene Borovov Date: Sun, 16 May 2021 21:37:48 +0700 Subject: [PATCH 103/145] backward compatibility --- src/CryptKey.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CryptKey.php b/src/CryptKey.php index cc46ec48d..17359a16e 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -19,6 +19,10 @@ class CryptKey { + /** @deprecated left for backward compatibility check */ + const RSA_KEY_PATTERN = + '/^(-----BEGIN (RSA )?(PUBLIC|PRIVATE) KEY-----)\R.*(-----END (RSA )?(PUBLIC|PRIVATE) KEY-----)\R?$/s'; + private const FILE_PREFIX = 'file://'; /** From 90b662f4d7c1b8f9a456aeb171519a2601d3e25d Mon Sep 17 00:00:00 2001 From: Eugene Borovov Date: Thu, 27 May 2021 18:08:28 +0700 Subject: [PATCH 104/145] Verify key with openssl --- composer.json | 3 +-- src/CryptKey.php | 25 +++++++++---------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index 1a2642aca..ee3eef51b 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,7 @@ "lcobucci/jwt": "^3.4 || ~4.0.0", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", - "ext-json": "*", - "phpseclib/phpseclib": "~3.0" + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "^8.5.13", diff --git a/src/CryptKey.php b/src/CryptKey.php index 17359a16e..a438df355 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -12,9 +12,6 @@ namespace League\OAuth2\Server; use LogicException; -use phpseclib3\Crypt\EC; -use phpseclib3\Crypt\RSA; -use phpseclib3\Exception\NoKeyLoadedException; use RuntimeException; class CryptKey @@ -124,21 +121,17 @@ private function saveKeyToFile($key) */ private function isValidKey($contents, $passPhrase) { - try { - RSA::load($contents, $passPhrase); - - return true; - } catch (NoKeyLoadedException $e) { - } - - try { - EC::load($contents, $passPhrase); - - return true; - } catch (NoKeyLoadedException $e) { + $pkey = openssl_pkey_get_private($contents, $passPhrase) ?: openssl_pkey_get_public($contents); + if ($pkey === false) { + return false; } + $details = openssl_pkey_get_details($pkey); - return false; + return $details !== false && in_array( + $details['type'] ?? -1, + [OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_EC], + true + ); } /** From 02afad178e4ea634b66bbbe27ed1a0f0d6c59548 Mon Sep 17 00:00:00 2001 From: Eugene Borovov Date: Thu, 27 May 2021 18:10:33 +0700 Subject: [PATCH 105/145] Fix code style --- src/CryptKey.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index a438df355..4aab847b2 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -121,13 +121,13 @@ private function saveKeyToFile($key) */ private function isValidKey($contents, $passPhrase) { - $pkey = openssl_pkey_get_private($contents, $passPhrase) ?: openssl_pkey_get_public($contents); + $pkey = \openssl_pkey_get_private($contents, $passPhrase) ?: \openssl_pkey_get_public($contents); if ($pkey === false) { return false; } - $details = openssl_pkey_get_details($pkey); + $details = \openssl_pkey_get_details($pkey); - return $details !== false && in_array( + return $details !== false && \in_array( $details['type'] ?? -1, [OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_EC], true From b65d486cd7213cee0b5a12e3cca6e4a8546d95ac Mon Sep 17 00:00:00 2001 From: Eugene Borovov Date: Mon, 31 May 2021 11:40:33 +0700 Subject: [PATCH 106/145] Remove isFilePath function --- src/CryptKey.php | 44 ++++++++------------- tests/Utils/CryptKeyTest.php | 77 ++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 28 deletions(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index 4aab847b2..9608dec24 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -39,29 +39,23 @@ class CryptKey */ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = true) { - $this->keyPath = $keyPath; $this->passPhrase = $passPhrase; - if (\is_file($this->keyPath) && !$this->isFilePath()) { - $this->keyPath = self::FILE_PREFIX . $this->keyPath; - } - - if ($this->isFilePath()) { - if (!\file_exists($this->keyPath) || !\is_readable($this->keyPath)) { - throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $this->keyPath)); + if (\is_file($keyPath)) { + if (\strpos($keyPath, self::FILE_PREFIX) !== 0) { + $keyPath = self::FILE_PREFIX . $keyPath; } - $contents = \file_get_contents($this->keyPath); - } else { - $contents = $keyPath; - } - - if ($this->isValidKey($contents, $this->passPhrase ?? '')) { - if (!$this->isFilePath()) { - $this->keyPath = $this->saveKeyToFile($keyPath); + if (!\is_readable($keyPath)) { + throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $keyPath)); } + $isFileKey = true; + $contents = \file_get_contents($keyPath); + $this->keyPath = $keyPath; } else { - throw new LogicException('Unable to read key' . ($this->isFilePath() ? " from file $keyPath" : '')); + $isFileKey = false; + $contents = $keyPath; + $this->keyPath = $this->saveKeyToFile($keyPath); } if ($keyPermissionsCheck === true) { @@ -78,6 +72,10 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = ); } } + + if (!$this->isValidKey($contents, $this->passPhrase ?? '')) { + throw new LogicException('Unable to read key' . ($isFileKey ? " from file $keyPath" : '')); + } } /** @@ -92,7 +90,7 @@ private function saveKeyToFile($key) $tmpDir = \sys_get_temp_dir(); $keyPath = $tmpDir . '/' . \sha1($key) . '.key'; - if (\file_exists($keyPath)) { + if (\is_readable($keyPath)) { return self::FILE_PREFIX . $keyPath; } @@ -134,16 +132,6 @@ private function isValidKey($contents, $passPhrase) ); } - /** - * Checks whether the key is a file. - * - * @return bool - */ - private function isFilePath() - { - return \strpos($this->keyPath, self::FILE_PREFIX) === 0; - } - /** * Retrieve key path. * diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index d5ab0caed..aa493bf3a 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -51,4 +51,81 @@ public function testKeyFileCreation() $key->getKeyPath() ); } + + public function testUnsupportedKeyType() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Unable to read key'); + + try { + // Create the keypair + $res = \openssl_pkey_new([ + 'digest_alg' => 'sha512', + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_DSA, + ]); + // Get private key + \openssl_pkey_export($res, $privkey, 'mystrongpassword'); + $path = 'file://' . \sys_get_temp_dir() . '/' . \sha1($privkey) . '.key'; + + new CryptKey($privkey, 'mystrongpassword'); + } finally { + if (isset($path)) { + @unlink($path); + } + } + } + + public function testECKeyType() + { + try { + // Create the keypair + $res = \openssl_pkey_new([ + 'digest_alg' => 'sha512', + 'curve_name' => 'prime256v1', + 'private_key_type' => OPENSSL_KEYTYPE_EC, + ]); + // Get private key + \openssl_pkey_export($res, $privkey, 'mystrongpassword'); + + $key = new CryptKey($privkey, 'mystrongpassword'); + $path = 'file://' . \sys_get_temp_dir() . '/' . \sha1($privkey) . '.key'; + + $this->assertEquals($path, $key->getKeyPath()); + $this->assertEquals('mystrongpassword', $key->getPassPhrase()); + + } catch (\Throwable $e) { + $this->fail('The EC key was not created'); + } finally { + if (isset($path)) { + @unlink($path); + } + } + } + + public function testRSAKeyType() + { + try { + // Create the keypair + $res = \openssl_pkey_new([ + 'digest_alg' => 'sha512', + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]); + // Get private key + \openssl_pkey_export($res, $privkey, 'mystrongpassword'); + + $key = new CryptKey($privkey, 'mystrongpassword'); + $path = 'file://' . \sys_get_temp_dir() . '/' . \sha1($privkey) . '.key'; + + $this->assertEquals($path, $key->getKeyPath()); + $this->assertEquals('mystrongpassword', $key->getPassPhrase()); + } catch (\Throwable $e) { + $this->fail('The RSA key was not created'); + } finally { + if (isset($path)) { + @unlink($path); + } + } + } } From 92d9bb1873c04b400851558fb679d9e9315802aa Mon Sep 17 00:00:00 2001 From: Eugene Borovov Date: Mon, 31 May 2021 11:48:46 +0700 Subject: [PATCH 107/145] Fix code style --- tests/Utils/CryptKeyTest.php | 45 +++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index aa493bf3a..8514a9a33 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -33,10 +33,7 @@ public function testKeyFileCreation() $key = new CryptKey($keyContent); - $this->assertEquals( - 'file://' . \sys_get_temp_dir() . '/' . \sha1($keyContent) . '.key', - $key->getKeyPath() - ); + $this->assertEquals(self::generateKeyPath($keyContent), $key->getKeyPath()); $keyContent = \file_get_contents(__DIR__ . '/../Stubs/private.key.crlf'); @@ -46,10 +43,7 @@ public function testKeyFileCreation() $key = new CryptKey($keyContent); - $this->assertEquals( - 'file://' . \sys_get_temp_dir() . '/' . \sha1($keyContent) . '.key', - $key->getKeyPath() - ); + $this->assertEquals(self::generateKeyPath($keyContent), $key->getKeyPath()); } public function testUnsupportedKeyType() @@ -65,13 +59,13 @@ public function testUnsupportedKeyType() 'private_key_type' => OPENSSL_KEYTYPE_DSA, ]); // Get private key - \openssl_pkey_export($res, $privkey, 'mystrongpassword'); - $path = 'file://' . \sys_get_temp_dir() . '/' . \sha1($privkey) . '.key'; + \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); + $path = self::generateKeyPath($keyContent); - new CryptKey($privkey, 'mystrongpassword'); + new CryptKey($keyContent, 'mystrongpassword'); } finally { if (isset($path)) { - @unlink($path); + @\unlink($path); } } } @@ -86,19 +80,18 @@ public function testECKeyType() 'private_key_type' => OPENSSL_KEYTYPE_EC, ]); // Get private key - \openssl_pkey_export($res, $privkey, 'mystrongpassword'); + \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); - $key = new CryptKey($privkey, 'mystrongpassword'); - $path = 'file://' . \sys_get_temp_dir() . '/' . \sha1($privkey) . '.key'; + $key = new CryptKey($keyContent, 'mystrongpassword'); + $path = self::generateKeyPath($keyContent); $this->assertEquals($path, $key->getKeyPath()); $this->assertEquals('mystrongpassword', $key->getPassPhrase()); - } catch (\Throwable $e) { $this->fail('The EC key was not created'); } finally { if (isset($path)) { - @unlink($path); + @\unlink($path); } } } @@ -113,10 +106,10 @@ public function testRSAKeyType() 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); // Get private key - \openssl_pkey_export($res, $privkey, 'mystrongpassword'); + \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); - $key = new CryptKey($privkey, 'mystrongpassword'); - $path = 'file://' . \sys_get_temp_dir() . '/' . \sha1($privkey) . '.key'; + $key = new CryptKey($keyContent, 'mystrongpassword'); + $path = self::generateKeyPath($keyContent); $this->assertEquals($path, $key->getKeyPath()); $this->assertEquals('mystrongpassword', $key->getPassPhrase()); @@ -124,8 +117,18 @@ public function testRSAKeyType() $this->fail('The RSA key was not created'); } finally { if (isset($path)) { - @unlink($path); + @\unlink($path); } } } + + /** + * @param string $keyContent + * + * @return string + */ + private static function generateKeyPath($keyContent) + { + return 'file://' . \sys_get_temp_dir() . '/' . \sha1($keyContent) . '.key'; + } } From de1670374de854f6f66e22b2a710b2dc0bdbf272 Mon Sep 17 00:00:00 2001 From: Eugene Borovov Date: Mon, 31 May 2021 11:56:41 +0700 Subject: [PATCH 108/145] Revert save key method --- src/CryptKey.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index 9608dec24..be33ce80a 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -90,7 +90,7 @@ private function saveKeyToFile($key) $tmpDir = \sys_get_temp_dir(); $keyPath = $tmpDir . '/' . \sha1($key) . '.key'; - if (\is_readable($keyPath)) { + if (\file_exists($keyPath)) { return self::FILE_PREFIX . $keyPath; } From c8fb25eb3863526a3687449833ccd3ab75e74115 Mon Sep 17 00:00:00 2001 From: janhopman-nhb Date: Mon, 31 May 2021 15:06:55 +0200 Subject: [PATCH 109/145] Empty commit From 8ea2bd12c87504c26e3895fe972387580948253a Mon Sep 17 00:00:00 2001 From: janhopman-nhb Date: Mon, 31 May 2021 15:16:03 +0200 Subject: [PATCH 110/145] Fix ci style. --- src/Grant/RefreshTokenGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 3c3649303..2dedf15c3 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -79,7 +79,7 @@ public function respondToAccessTokenRequest( $refreshToken = $this->issueRefreshToken($accessToken); if ($refreshToken !== null) { - $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken)); + $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken)); $responseType->setRefreshToken($refreshToken); } } From 5cf9d0737d9575cfd2b1ef6fbc736a7b364e9a23 Mon Sep 17 00:00:00 2001 From: janhopman-nhb Date: Mon, 31 May 2021 15:28:27 +0200 Subject: [PATCH 111/145] Remove method from interface since it is already present on the abstract. --- src/Grant/GrantTypeInterface.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index d761fb0ab..41ebeb5ff 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -141,11 +141,4 @@ public function setPrivateKey(CryptKey $privateKey); * @param string|Key|null $key */ public function setEncryptionKey($key = null); - - /** - * Enables ability to prevent refresh tokens from being revoked. - * - * @param bool $revokeRefreshTokens - */ - public function setRevokeRefreshTokens(bool $revokeRefreshTokens); } From 0d57b7051ad51cce3bac9ba40e0fd6c9e4f6674d Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 31 May 2021 20:34:31 +0100 Subject: [PATCH 112/145] Update changelog to include PR 1215 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa60f1ff3..5cc7f0c4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - The server will now validate redirect uris according to rfc8252 (PR #1203) - Events emitted now include the refresh token and access token payloads (PR #1211) +### Changed +- Keys are now validated using `openssl_pkey_get_private()` and openssl_pkey_get_public()` instead of regex matching (PR #1215) + ### Fixed - The server will now only recognise and handle an authorization header if the value of the header is non-empty. This is to circumvent issues where some common frameworks set this header even if no value is present (PR #1170) - Added type validation for redirect uri, client ID, client secret, scopes, auth code, state, username, and password inputs (PR #1210) From 96b76efc5c70cdccf97f1cb521d9fa594ddbae34 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Fri, 21 May 2021 15:23:22 +0200 Subject: [PATCH 113/145] Respond with helpful and spec complient error on invalid user credentials --- src/Exception/OAuthServerException.php | 2 +- src/Grant/PasswordGrant.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 64adebf14..a0be0a5dd 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -189,7 +189,7 @@ public static function invalidScope($scope, $redirectUri = null) */ public static function invalidCredentials() { - return new static('The user credentials were incorrect.', 6, 'invalid_credentials', 401); + return new static('The user credentials were incorrect.', 6, 'invalid_grant', 400); } /** diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 4e12fa535..fd32d2688 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -106,7 +106,7 @@ protected function validateUser(ServerRequestInterface $request, ClientEntityInt if ($user instanceof UserEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidGrant(); + throw OAuthServerException::invalidCredentials(); } return $user; From 3e31fe97db50d411b68b094f9c12e38699ea4b2a Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Mon, 31 May 2021 21:49:10 +0200 Subject: [PATCH 114/145] Update tests --- tests/Exception/OAuthServerExceptionTest.php | 7 +++++++ tests/Grant/PasswordGrantTest.php | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/Exception/OAuthServerExceptionTest.php b/tests/Exception/OAuthServerExceptionTest.php index 7ece08032..38b86d433 100644 --- a/tests/Exception/OAuthServerExceptionTest.php +++ b/tests/Exception/OAuthServerExceptionTest.php @@ -137,4 +137,11 @@ public function testCanGetRedirectionUri() $this->assertSame('https://example.com/error', $exceptionWithRedirect->getRedirectUri()); } + + public function testInvalidCredentialsIsInvalidGrant() + { + $exception = OAuthServerException::invalidCredentials(); + + $this->assertSame('invalid_grant', $exception->getErrorType()); + } } diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 0be3d4826..b53ab2357 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -211,7 +211,7 @@ public function testRespondToRequestBadCredentials() $responseType = new StubResponseType(); $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); - $this->expectExceptionCode(10); + $this->expectExceptionCode(6); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } From e3d5d6d4ca23ec44e6587b8051204595eaea0541 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 31 May 2021 20:59:58 +0100 Subject: [PATCH 115/145] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc7f0c4b..f1e6dfff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - The server will now only recognise and handle an authorization header if the value of the header is non-empty. This is to circumvent issues where some common frameworks set this header even if no value is present (PR #1170) - Added type validation for redirect uri, client ID, client secret, scopes, auth code, state, username, and password inputs (PR #1210) +- Allow scope "0" to be used. Previously this was removed from a request because it failed an `empty()` check (PR #1092) ## [8.2.4] - released 2020-12-10 ### Fixed From 936e229c701e379f30e26b537c8838a472e4cd54 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 31 May 2021 21:01:33 +0100 Subject: [PATCH 116/145] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e6dfff8..b7738c2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - The server will now only recognise and handle an authorization header if the value of the header is non-empty. This is to circumvent issues where some common frameworks set this header even if no value is present (PR #1170) - Added type validation for redirect uri, client ID, client secret, scopes, auth code, state, username, and password inputs (PR #1210) -- Allow scope "0" to be used. Previously this was removed from a request because it failed an `empty()` check (PR #1092) +- Allow scope "0" to be used. Previously this was removed from a request because it failed an `empty()` check (PR #1181) ## [8.2.4] - released 2020-12-10 ### Fixed From c808d13049b12bdf954049e45d388dfc8462f7ed Mon Sep 17 00:00:00 2001 From: janhopman-nhb Date: Tue, 1 Jun 2021 09:24:35 +0200 Subject: [PATCH 117/145] Set revokeRefreshTokens prop to true by default, added method to change, cleaned up constructor. --- src/AuthorizationServer.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 90de52578..1b6d593a7 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -82,7 +82,7 @@ class AuthorizationServer implements EmitterAwareInterface /** * @var bool */ - private $revokeRefreshTokens; + private $revokeRefreshTokens = true; /** * New server instance. @@ -93,7 +93,6 @@ class AuthorizationServer implements EmitterAwareInterface * @param CryptKey|string $privateKey * @param string|Key $encryptionKey * @param null|ResponseTypeInterface $responseType - * @param bool $revokeRefreshTokens */ public function __construct( ClientRepositoryInterface $clientRepository, @@ -101,8 +100,7 @@ public function __construct( ScopeRepositoryInterface $scopeRepository, $privateKey, $encryptionKey, - ResponseTypeInterface $responseType = null, - bool $revokeRefreshTokens = true + ResponseTypeInterface $responseType = null ) { $this->clientRepository = $clientRepository; $this->accessTokenRepository = $accessTokenRepository; @@ -122,7 +120,6 @@ public function __construct( } $this->responseType = $responseType; - $this->revokeRefreshTokens = $revokeRefreshTokens; } /** @@ -242,4 +239,14 @@ public function setDefaultScope($defaultScope) { $this->defaultScope = $defaultScope; } + + /** + * Sets wether to revoke refresh tokens or not (for all grant types). + * + * @param bool $revokeRefreshTokens + */ + public function setRevokeRefreshTokens(bool $revokeRefreshTokens): void + { + $this->revokeRefreshTokens = $revokeRefreshTokens; + } } From d0cf49242b16ce5620f7f781c32663605f16b33f Mon Sep 17 00:00:00 2001 From: janhopman-nhb Date: Tue, 1 Jun 2021 13:43:35 +0200 Subject: [PATCH 118/145] Add tests. --- tests/Grant/RefreshTokenGrantTest.php | 115 ++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index f60b9a67d..090919c61 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -18,6 +18,7 @@ use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; +use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; class RefreshTokenGrantTest extends TestCase @@ -469,4 +470,118 @@ public function testRespondToRequestRevokedToken() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } + + public function testRevokedRefreshToken() + { + $refreshTokenId = 'foo'; + + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $client->setRedirectUri('http://foo/bar'); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $scopeEntity = new ScopeEntity(); + $scopeEntity->setIdentifier('foo'); + + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf(); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('isRefreshTokenRevoked') + ->will($this->onConsecutiveCalls(false, true)); + $refreshTokenRepositoryMock->expects($this->once())->method('revokeRefreshToken')->with($this->equalTo($refreshTokenId)); + + $oldRefreshToken = $this->cryptStub->doEncrypt( + \json_encode( + [ + 'client_id' => 'foo', + 'refresh_token_id' => $refreshTokenId, + 'access_token_id' => 'abcdef', + 'scopes' => ['foo'], + 'user_id' => 123, + 'expire_time' => \time() + 3600, + ] + ) + ); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + 'scope' => ['foo'], + ]); + + $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $grant->setRevokeRefreshTokens(true); + $grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M')); + + Assert::assertTrue($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId)); + } + + public function testUnrevokedRefreshToken() + { + $refreshTokenId = 'foo'; + + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $client->setRedirectUri('http://foo/bar'); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $scopeEntity = new ScopeEntity(); + $scopeEntity->setIdentifier('foo'); + + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf(); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('isRefreshTokenRevoked')->willReturn(false); + $refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken'); + + $oldRefreshToken = $this->cryptStub->doEncrypt( + \json_encode( + [ + 'client_id' => 'foo', + 'refresh_token_id' => $refreshTokenId, + 'access_token_id' => 'abcdef', + 'scopes' => ['foo'], + 'user_id' => 123, + 'expire_time' => \time() + 3600, + ] + ) + ); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + 'scope' => ['foo'], + ]); + + $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M')); + + Assert::assertFalse($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId)); + } } From d7634f92fcc2a4dc90a611987dc06579957b68d0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 3 Jun 2021 22:48:24 +0100 Subject: [PATCH 119/145] change function name and update changelog --- CHANGELOG.md | 1 + src/AuthorizationServer.php | 4 ++-- src/Grant/AbstractGrant.php | 2 +- tests/Grant/RefreshTokenGrantTest.php | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7738c2f7..d42353c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - The server will now validate redirect uris according to rfc8252 (PR #1203) - Events emitted now include the refresh token and access token payloads (PR #1211) +- Use the `revokeRefreshTokens()` function to decide whether refresh tokens are revoked or not upon use (PR #1189) ### Changed - Keys are now validated using `openssl_pkey_get_private()` and openssl_pkey_get_public()` instead of regex matching (PR #1215) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 1b6d593a7..a719656c6 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -141,7 +141,7 @@ public function enableGrantType(GrantTypeInterface $grantType, DateInterval $acc $grantType->setPrivateKey($this->privateKey); $grantType->setEmitter($this->getEmitter()); $grantType->setEncryptionKey($this->encryptionKey); - $grantType->setRevokeRefreshTokens($this->revokeRefreshTokens); + $grantType->revokeRefreshTokens($this->revokeRefreshTokens); $this->enabledGrantTypes[$grantType->getIdentifier()] = $grantType; $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] = $accessTokenTTL; @@ -245,7 +245,7 @@ public function setDefaultScope($defaultScope) * * @param bool $revokeRefreshTokens */ - public function setRevokeRefreshTokens(bool $revokeRefreshTokens): void + public function revokeRefreshTokens(bool $revokeRefreshTokens): void { $this->revokeRefreshTokens = $revokeRefreshTokens; } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 8192da1a6..1665b980e 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -175,7 +175,7 @@ public function setDefaultScope($scope) /** * @param bool $revokeRefreshTokens */ - public function setRevokeRefreshTokens(bool $revokeRefreshTokens) + public function revokeRefreshTokens(bool $revokeRefreshTokens) { $this->revokeRefreshTokens = $revokeRefreshTokens; } diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 090919c61..8f56fac4c 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -69,7 +69,7 @@ public function testRespondToRequest() $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $grant->setRevokeRefreshTokens(true); + $grant->revokeRefreshTokens(true); $oldRefreshToken = $this->cryptStub->doEncrypt( \json_encode( @@ -183,7 +183,7 @@ public function testRespondToReducedScopes() $grant->setScopeRepository($scopeRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $grant->setRevokeRefreshTokens(true); + $grant->revokeRefreshTokens(true); $oldRefreshToken = $this->cryptStub->doEncrypt( \json_encode( @@ -523,7 +523,7 @@ public function testRevokedRefreshToken() $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $grant->setRevokeRefreshTokens(true); + $grant->revokeRefreshTokens(true); $grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M')); Assert::assertTrue($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId)); From 4ea27e85a8237086b7fa2be89c297d06f99e1535 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 3 Jun 2021 22:55:03 +0100 Subject: [PATCH 120/145] update changelog for version 8.3.0 release --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d42353c13..048479c12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [8.3.0] - released 2021-06-03 ### Added - The server will now validate redirect uris according to rfc8252 (PR #1203) - Events emitted now include the refresh token and access token payloads (PR #1211) @@ -539,7 +541,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.3.0...HEAD +[8.3.0]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...8.3.0 [8.2.4]: https://github.com/thephpleague/oauth2-server/compare/8.2.3...8.2.4 [8.2.3]: https://github.com/thephpleague/oauth2-server/compare/8.2.2...8.2.3 [8.2.2]: https://github.com/thephpleague/oauth2-server/compare/8.2.1...8.2.2 From be65bf1d98ab11c9d806164af05fe5a81f93fa87 Mon Sep 17 00:00:00 2001 From: sephster Date: Fri, 4 Jun 2021 09:23:33 +0100 Subject: [PATCH 121/145] Fix tests --- src/Grant/AbstractGrant.php | 2 +- src/Grant/ImplicitGrant.php | 2 +- tests/Grant/AbstractGrantTest.php | 32 ------------------------------- 3 files changed, 2 insertions(+), 34 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 1665b980e..0a5b514fc 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -256,7 +256,7 @@ protected function getClientCredentials(ServerRequestInterface $request) $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); - if (!\is_string($clientId)) { + if (\is_null($clientId)) { throw OAuthServerException::invalidRequest('client_id'); } diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 21e06ed41..0bd91d5ac 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -120,7 +120,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $this->getServerParameter('PHP_AUTH_USER', $request) ); - if (!\is_string($clientId)) { + if (\is_null($clientId)) { throw OAuthServerException::invalidRequest('client_id'); } diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 9623dc7ca..618545efe 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -89,38 +89,6 @@ public function testHttpBasicNoColon() $this->assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); } - public function testGetClientCredentialsClientIdNotAString() - { - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - - /** @var AbstractGrant $grantMock */ - $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $grantMock->setClientRepository($clientRepositoryMock); - - $abstractGrantReflection = new \ReflectionClass($grantMock); - - $serverRequest = new ServerRequest( - [], - [], - null, - 'POST', - 'php://input', - [], - [], - [], - [ - 'client_id' => ['not', 'a', 'string'], - 'client_secret' => 'client_secret', - ] - ); - $getClientCredentialsMethod = $abstractGrantReflection->getMethod('getClientCredentials'); - $getClientCredentialsMethod->setAccessible(true); - - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); - - $getClientCredentialsMethod->invoke($grantMock, $serverRequest, true, true); - } - public function testGetClientCredentialsClientSecretNotAString() { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); From 1423ae4c44574485885935013f6acc8a240c2de4 Mon Sep 17 00:00:00 2001 From: sephster Date: Fri, 4 Jun 2021 09:28:19 +0100 Subject: [PATCH 122/145] update changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 048479c12..f5b4a971c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [8.3.1] - released 2021-06-04 +### Fixed +- Revert check on clientID. We will no longer require this to be a string (PR #1233) + ## [8.3.0] - released 2021-06-03 ### Added - The server will now validate redirect uris according to rfc8252 (PR #1203) @@ -541,7 +545,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.3.0...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.3.1...HEAD +[8.3.1]: https://github.com/thephpleague/oauth2-server/compare/8.3.0...8.3.1 [8.3.0]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...8.3.0 [8.2.4]: https://github.com/thephpleague/oauth2-server/compare/8.2.3...8.2.4 [8.2.3]: https://github.com/thephpleague/oauth2-server/compare/8.2.2...8.2.3 From eae0272c5f5c7a644af5455bb0ead75317b35db4 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 15 Jun 2021 11:46:31 +0200 Subject: [PATCH 123/145] Allow lcobucci/jwt 4.1 Signed-off-by: Alexander M. Turek --- composer.json | 2 +- src/AuthorizationValidators/BearerTokenValidator.php | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ee3eef51b..6d01aecd7 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": "^7.2 || ^8.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.4 || ~4.0.0", + "lcobucci/jwt": "^3.4 || ^4.0", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "ext-json": "*" diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index bab8919f6..486001e27 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -16,6 +16,7 @@ use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Validation\Constraint\SignedWith; +use Lcobucci\JWT\Validation\Constraint\StrictValidAt; use Lcobucci\JWT\Validation\Constraint\ValidAt; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; use League\OAuth2\Server\CryptKey; @@ -74,7 +75,9 @@ private function initJwtConfiguration() ); $this->jwtConfiguration->setValidationConstraints( - new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), + \class_exists(StrictValidAt::class) + ? new StrictValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))) + : new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), new SignedWith(new Sha256(), LocalFileReference::file($this->publicKey->getKeyPath())) ); } From 04efa914b0cc2270d77459663e8ac9cc120894f8 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 10 Jul 2021 11:41:44 +0100 Subject: [PATCH 124/145] Update changelog for PR #1236 Updated the changelog to add details about PR #1236. This PR allows us to support version 4.1.x or greater of lcobucci/jwt by conditionally supporting the StrictValidAt() validator. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5b4a971c..a785f6d27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- Conditionally support the `StrictValidAt()` method in lcobucci/jwt so we can use version 4.1x of the library (PR #1236) ## [8.3.1] - released 2021-06-04 ### Fixed From dc7fa33779d0aaf8d119be05bb9852012f2298e7 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 10 Jul 2021 11:49:22 +0100 Subject: [PATCH 125/145] Fix typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a785f6d27..a612caedc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Changed -- Conditionally support the `StrictValidAt()` method in lcobucci/jwt so we can use version 4.1x of the library (PR #1236) +- Conditionally support the `StrictValidAt()` method in lcobucci/jwt so we can use version 4.1.x or greater of the library (PR #1236) ## [8.3.1] - released 2021-06-04 ### Fixed From 330deb0bff7d3e9e2447d3813ea7ffca2722bab7 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Thu, 24 Dec 2020 14:33:06 -0500 Subject: [PATCH 126/145] Use key as string without a temporary file --- .../BearerTokenValidator.php | 2 +- src/CryptKey.php | 51 ++++++++----------- src/Entities/Traits/AccessTokenTrait.php | 2 +- tests/Utils/CryptKeyTest.php | 18 ++++--- 4 files changed, 34 insertions(+), 39 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 486001e27..5b844546b 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -78,7 +78,7 @@ private function initJwtConfiguration() \class_exists(StrictValidAt::class) ? new StrictValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))) : new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), - new SignedWith(new Sha256(), LocalFileReference::file($this->publicKey->getKeyPath())) + new SignedWith(new Sha256(), $this->publicKey->getKey()) ); } diff --git a/src/CryptKey.php b/src/CryptKey.php index be33ce80a..a54255933 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -11,6 +11,10 @@ namespace League\OAuth2\Server; +use Lcobucci\JWT\Signer\Key; +use Lcobucci\JWT\Signer\Key\InMemory; +use Lcobucci\JWT\Signer\Key\LocalFileReference; + use LogicException; use RuntimeException; @@ -22,6 +26,11 @@ class CryptKey private const FILE_PREFIX = 'file://'; + /** + * @var Key + */ + protected $key; + /** * @var string */ @@ -52,10 +61,17 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = $isFileKey = true; $contents = \file_get_contents($keyPath); $this->keyPath = $keyPath; + $this->key = LocalFileReference::file($this->keyPath, $this->passPhrase ?? ''); } else { $isFileKey = false; $contents = $keyPath; - $this->keyPath = $this->saveKeyToFile($keyPath); + $this->key = InMemory::plainText($keyPath, $this->passPhrase ?? ''); + $this->keyPath = ''; + // There's no file, so no need for permission check. + $keyPermissionsCheck = false; + } + if (!$this->isValidKey($contents, $this->passPhrase ?? '')) { + throw new LogicException('Unable to read key' . ($isFileKey ? " from file $keyPath" : '')); } if ($keyPermissionsCheck === true) { @@ -72,41 +88,16 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = ); } } - - if (!$this->isValidKey($contents, $this->passPhrase ?? '')) { - throw new LogicException('Unable to read key' . ($isFileKey ? " from file $keyPath" : '')); - } } /** - * @param string $key + * Get key * - * @throws RuntimeException - * - * @return string + * @return Key */ - private function saveKeyToFile($key) + public function getKey(): Key { - $tmpDir = \sys_get_temp_dir(); - $keyPath = $tmpDir . '/' . \sha1($key) . '.key'; - - if (\file_exists($keyPath)) { - return self::FILE_PREFIX . $keyPath; - } - - if (\file_put_contents($keyPath, $key) === false) { - // @codeCoverageIgnoreStart - throw new RuntimeException(\sprintf('Unable to write key file to temporary directory "%s"', $tmpDir)); - // @codeCoverageIgnoreEnd - } - - if (\chmod($keyPath, 0600) === false) { - // @codeCoverageIgnoreStart - throw new RuntimeException(\sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath)); - // @codeCoverageIgnoreEnd - } - - return self::FILE_PREFIX . $keyPath; + return $this->key; } /** diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index dc5263f7e..d3c1b0330 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -46,7 +46,7 @@ public function initJwtConfiguration() { $this->jwtConfiguration = Configuration::forAsymmetricSigner( new Sha256(), - LocalFileReference::file($this->privateKey->getKeyPath(), $this->privateKey->getPassPhrase() ?? ''), + $this->privateKey->getKey(), InMemory::plainText('') ); } diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index 8514a9a33..583de1e8d 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -23,7 +23,7 @@ public function testKeyCreation() $this->assertEquals('secret', $key->getPassPhrase()); } - public function testKeyFileCreation() + public function testKeyString() { $keyContent = \file_get_contents(__DIR__ . '/../Stubs/public.key'); @@ -33,7 +33,10 @@ public function testKeyFileCreation() $key = new CryptKey($keyContent); - $this->assertEquals(self::generateKeyPath($keyContent), $key->getKeyPath()); + $this->assertEquals( + $keyContent, + $key->getKey()->contents() + ); $keyContent = \file_get_contents(__DIR__ . '/../Stubs/private.key.crlf'); @@ -43,7 +46,10 @@ public function testKeyFileCreation() $key = new CryptKey($keyContent); - $this->assertEquals(self::generateKeyPath($keyContent), $key->getKeyPath()); + $this->assertEquals( + $keyContent, + $key->getKey()->contents() + ); } public function testUnsupportedKeyType() @@ -83,9 +89,8 @@ public function testECKeyType() \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); $key = new CryptKey($keyContent, 'mystrongpassword'); - $path = self::generateKeyPath($keyContent); - $this->assertEquals($path, $key->getKeyPath()); + $this->assertEquals('', $key->getKeyPath()); $this->assertEquals('mystrongpassword', $key->getPassPhrase()); } catch (\Throwable $e) { $this->fail('The EC key was not created'); @@ -109,9 +114,8 @@ public function testRSAKeyType() \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); $key = new CryptKey($keyContent, 'mystrongpassword'); - $path = self::generateKeyPath($keyContent); - $this->assertEquals($path, $key->getKeyPath()); + $this->assertEquals('', $key->getKeyPath()); $this->assertEquals('mystrongpassword', $key->getPassPhrase()); } catch (\Throwable $e) { $this->fail('The RSA key was not created'); From 0db9f82c25c00679fb2d1dc0dff6d4d331065e45 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Thu, 24 Dec 2020 14:37:38 -0500 Subject: [PATCH 127/145] Style fixes --- .../BearerTokenValidator.php | 1 - src/CryptKey.php | 25 +++++++++---------- src/Entities/Traits/AccessTokenTrait.php | 1 - 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 5b844546b..ee061d01b 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -13,7 +13,6 @@ use Lcobucci\Clock\SystemClock; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; -use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\StrictValidAt; diff --git a/src/CryptKey.php b/src/CryptKey.php index a54255933..4b6c16383 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -16,7 +16,6 @@ use Lcobucci\JWT\Signer\Key\LocalFileReference; use LogicException; -use RuntimeException; class CryptKey { @@ -50,7 +49,13 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = { $this->passPhrase = $passPhrase; - if (\is_file($keyPath)) { + if (\strpos($keyPath, self::FILE_PREFIX) !== 0 && $this->isValidKey($keyPath, $this->passPhrase ?? '')) { + $contents = $keyPath; + $this->key = InMemory::plainText($keyPath, $this->passPhrase ?? ''); + $this->keyPath = ''; + // There's no file, so no need for permission check. + $keyPermissionsCheck = false; + } else if (\is_file($keyPath)) { if (\strpos($keyPath, self::FILE_PREFIX) !== 0) { $keyPath = self::FILE_PREFIX . $keyPath; } @@ -58,21 +63,15 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = if (!\is_readable($keyPath)) { throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $keyPath)); } - $isFileKey = true; $contents = \file_get_contents($keyPath); $this->keyPath = $keyPath; $this->key = LocalFileReference::file($this->keyPath, $this->passPhrase ?? ''); - } else { - $isFileKey = false; - $contents = $keyPath; - $this->key = InMemory::plainText($keyPath, $this->passPhrase ?? ''); - $this->keyPath = ''; - // There's no file, so no need for permission check. - $keyPermissionsCheck = false; - } - if (!$this->isValidKey($contents, $this->passPhrase ?? '')) { - throw new LogicException('Unable to read key' . ($isFileKey ? " from file $keyPath" : '')); + if (!$this->isValidKey($contents, $this->passPhrase ?? '')) { + throw new LogicException('Unable to read key from file ' . $keyPath); + } } + else + throw new LogicException('Unable to read key from file ' . $keyPath); if ($keyPermissionsCheck === true) { // Verify the permissions of the key diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index d3c1b0330..75d6b6b42 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -12,7 +12,6 @@ use DateTimeImmutable; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; -use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token; use League\OAuth2\Server\CryptKey; From 3c2eda4e896b717c6a2647c66cdd870fc2af8b3c Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Thu, 24 Dec 2020 14:41:12 -0500 Subject: [PATCH 128/145] More style fixes --- src/CryptKey.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index 4b6c16383..218b3e2fb 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -14,7 +14,6 @@ use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Key\LocalFileReference; - use LogicException; class CryptKey From dd23cbd3880231bca21755f76cf4b891c857b98e Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Fri, 25 Jun 2021 13:01:43 -0400 Subject: [PATCH 129/145] Style fixes --- src/CryptKey.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index 218b3e2fb..7007562e6 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -49,11 +49,11 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = $this->passPhrase = $passPhrase; if (\strpos($keyPath, self::FILE_PREFIX) !== 0 && $this->isValidKey($keyPath, $this->passPhrase ?? '')) { - $contents = $keyPath; - $this->key = InMemory::plainText($keyPath, $this->passPhrase ?? ''); - $this->keyPath = ''; - // There's no file, so no need for permission check. - $keyPermissionsCheck = false; + $contents = $keyPath; + $this->key = InMemory::plainText($keyPath, $this->passPhrase ?? ''); + $this->keyPath = ''; + // There's no file, so no need for permission check. + $keyPermissionsCheck = false; } else if (\is_file($keyPath)) { if (\strpos($keyPath, self::FILE_PREFIX) !== 0) { $keyPath = self::FILE_PREFIX . $keyPath; @@ -66,11 +66,12 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = $this->keyPath = $keyPath; $this->key = LocalFileReference::file($this->keyPath, $this->passPhrase ?? ''); if (!$this->isValidKey($contents, $this->passPhrase ?? '')) { - throw new LogicException('Unable to read key from file ' . $keyPath); + throw new LogicException('Unable to read key from file ' . $keyPath); } } - else + else { throw new LogicException('Unable to read key from file ' . $keyPath); + } if ($keyPermissionsCheck === true) { // Verify the permissions of the key From f338c7a4a16dbba4ef0a4e82357b89c79396dcf0 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Fri, 25 Jun 2021 13:02:52 -0400 Subject: [PATCH 130/145] Style fixes 2 --- src/CryptKey.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index 7007562e6..26bf02b4e 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -54,7 +54,7 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = $this->keyPath = ''; // There's no file, so no need for permission check. $keyPermissionsCheck = false; - } else if (\is_file($keyPath)) { + } elseif (\is_file($keyPath)) { if (\strpos($keyPath, self::FILE_PREFIX) !== 0) { $keyPath = self::FILE_PREFIX . $keyPath; } @@ -66,11 +66,11 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = $this->keyPath = $keyPath; $this->key = LocalFileReference::file($this->keyPath, $this->passPhrase ?? ''); if (!$this->isValidKey($contents, $this->passPhrase ?? '')) { - throw new LogicException('Unable to read key from file ' . $keyPath); + throw new LogicException('Unable to read key from file ' . $keyPath); } } else { - throw new LogicException('Unable to read key from file ' . $keyPath); + throw new LogicException('Unable to read key from file ' . $keyPath); } if ($keyPermissionsCheck === true) { From c1f2e0efb919dfabd53c5fc6573abea07c29efa4 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Fri, 25 Jun 2021 13:03:29 -0400 Subject: [PATCH 131/145] Style fixes 3 --- src/CryptKey.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index 26bf02b4e..2d643287a 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -68,8 +68,7 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = if (!$this->isValidKey($contents, $this->passPhrase ?? '')) { throw new LogicException('Unable to read key from file ' . $keyPath); } - } - else { + } else { throw new LogicException('Unable to read key from file ' . $keyPath); } From 426040e8bd994575ab1947a0979c95aa2046753e Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Fri, 25 Jun 2021 23:38:11 -0400 Subject: [PATCH 132/145] Store key contents instead of Key object --- .../BearerTokenValidator.php | 5 +++- src/CryptKey.php | 23 ++++++++----------- src/Entities/Traits/AccessTokenTrait.php | 2 +- tests/Utils/CryptKeyTest.php | 4 ++-- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index ee061d01b..df912d5ab 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -77,7 +77,10 @@ private function initJwtConfiguration() \class_exists(StrictValidAt::class) ? new StrictValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))) : new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), - new SignedWith(new Sha256(), $this->publicKey->getKey()) + new SignedWith( + new Sha256(), + InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? ''), + ) ); } diff --git a/src/CryptKey.php b/src/CryptKey.php index 2d643287a..14cd8e7b4 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -11,9 +11,6 @@ namespace League\OAuth2\Server; -use Lcobucci\JWT\Signer\Key; -use Lcobucci\JWT\Signer\Key\InMemory; -use Lcobucci\JWT\Signer\Key\LocalFileReference; use LogicException; class CryptKey @@ -25,9 +22,9 @@ class CryptKey private const FILE_PREFIX = 'file://'; /** - * @var Key + * @var string Key contents */ - protected $key; + protected $keyContents; /** * @var string @@ -49,8 +46,7 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = $this->passPhrase = $passPhrase; if (\strpos($keyPath, self::FILE_PREFIX) !== 0 && $this->isValidKey($keyPath, $this->passPhrase ?? '')) { - $contents = $keyPath; - $this->key = InMemory::plainText($keyPath, $this->passPhrase ?? ''); + $this->keyContents = $keyPath; $this->keyPath = ''; // There's no file, so no need for permission check. $keyPermissionsCheck = false; @@ -62,10 +58,9 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = if (!\is_readable($keyPath)) { throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $keyPath)); } - $contents = \file_get_contents($keyPath); + $this->keyContents = \file_get_contents($keyPath); $this->keyPath = $keyPath; - $this->key = LocalFileReference::file($this->keyPath, $this->passPhrase ?? ''); - if (!$this->isValidKey($contents, $this->passPhrase ?? '')) { + if (!$this->isValidKey($this->keyContents, $this->passPhrase ?? '')) { throw new LogicException('Unable to read key from file ' . $keyPath); } } else { @@ -89,13 +84,13 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = } /** - * Get key + * Get key contents * - * @return Key + * @return string Key contents */ - public function getKey(): Key + public function getKeyContents(): string { - return $this->key; + return $this->keyContents; } /** diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 75d6b6b42..5caf9533d 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -45,7 +45,7 @@ public function initJwtConfiguration() { $this->jwtConfiguration = Configuration::forAsymmetricSigner( new Sha256(), - $this->privateKey->getKey(), + InMemory::plainText($this->privateKey->getKeyContents(), $this->privateKey->getPassPhrase() ?? ''), InMemory::plainText('') ); } diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index 583de1e8d..b9c53b660 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -35,7 +35,7 @@ public function testKeyString() $this->assertEquals( $keyContent, - $key->getKey()->contents() + $key->getKeyContents() ); $keyContent = \file_get_contents(__DIR__ . '/../Stubs/private.key.crlf'); @@ -48,7 +48,7 @@ public function testKeyString() $this->assertEquals( $keyContent, - $key->getKey()->contents() + $key->getKeyContents() ); } From bec2d333825bfbfa3ddd6725837b2eb5d6cb36ff Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 10 Jul 2021 09:36:00 -0400 Subject: [PATCH 133/145] Remove trailing comma --- src/AuthorizationValidators/BearerTokenValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index df912d5ab..7645d8822 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -79,7 +79,7 @@ private function initJwtConfiguration() : new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), new SignedWith( new Sha256(), - InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? ''), + InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? '') ) ); } From 40587657ddfe0ab5c078d6f26a86d65a4129e514 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 12 Jul 2021 08:45:30 +0100 Subject: [PATCH 134/145] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a612caedc..bd0487831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Changed - Conditionally support the `StrictValidAt()` method in lcobucci/jwt so we can use version 4.1.x or greater of the library (PR #1236) +- When providing invalid credentials, the library now responds with the error message _The user credentials were incorrect_ (PR #1230) +- Keys are always stored in memory now and are not written to a file in the /tmp directory (PR #1180) ## [8.3.1] - released 2021-06-04 ### Fixed From 34a170c5d9c1907074961d2c1374b1fc34dff442 Mon Sep 17 00:00:00 2001 From: Jesse Donat Date: Fri, 16 Jul 2021 16:12:31 -0500 Subject: [PATCH 135/145] Simplifies `Bearer` regex `(?:\s+)?` is just `\s*` but harder --- src/AuthorizationValidators/BearerTokenValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 7645d8822..e41e1a2b7 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -94,7 +94,7 @@ public function validateAuthorization(ServerRequestInterface $request) } $header = $request->getHeader('authorization'); - $jwt = \trim((string) \preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0])); + $jwt = \trim((string) \preg_replace('/^\s*Bearer\s/', '', $header[0])); try { // Attempt to parse the JWT From 715d5fa0ad25979bfc41cf3322322639e14733c5 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 27 Jul 2021 09:15:07 +0100 Subject: [PATCH 136/145] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd0487831..729ab21e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Conditionally support the `StrictValidAt()` method in lcobucci/jwt so we can use version 4.1.x or greater of the library (PR #1236) - When providing invalid credentials, the library now responds with the error message _The user credentials were incorrect_ (PR #1230) - Keys are always stored in memory now and are not written to a file in the /tmp directory (PR #1180) +- The regex for matching the bearer token has been simplified (PR #1238) ## [8.3.1] - released 2021-06-04 ### Fixed From 0809487d33dd8a2c8c8c04e4a599ba4aadba1ae6 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 27 Jul 2021 09:17:08 +0100 Subject: [PATCH 137/145] Update changelog for version 8.3.2 release --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 729ab21e7..870a325a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [8.3.2] - released 2021-07-27 ### Changed - Conditionally support the `StrictValidAt()` method in lcobucci/jwt so we can use version 4.1.x or greater of the library (PR #1236) - When providing invalid credentials, the library now responds with the error message _The user credentials were incorrect_ (PR #1230) @@ -550,7 +552,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.3.1...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.3.2...HEAD +[8.3.2]: https://github.com/thephpleague/oauth2-server/compare/8.3.1...8.3.2 [8.3.1]: https://github.com/thephpleague/oauth2-server/compare/8.3.0...8.3.1 [8.3.0]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...8.3.0 [8.2.4]: https://github.com/thephpleague/oauth2-server/compare/8.2.3...8.2.4 From 80009a25b3c950d56c3c7864319d185d53712bbf Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Wed, 8 Sep 2021 01:49:54 +0200 Subject: [PATCH 138/145] Small syntax fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 870a325a0..099fd33d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Use the `revokeRefreshTokens()` function to decide whether refresh tokens are revoked or not upon use (PR #1189) ### Changed -- Keys are now validated using `openssl_pkey_get_private()` and openssl_pkey_get_public()` instead of regex matching (PR #1215) +- Keys are now validated using `openssl_pkey_get_private()` and `openssl_pkey_get_public()` instead of regex matching (PR #1215) ### Fixed - The server will now only recognise and handle an authorization header if the value of the header is non-empty. This is to circumvent issues where some common frameworks set this header even if no value is present (PR #1170) From 0e4fd349f588471d5523af14fde328a2dc8f9418 Mon Sep 17 00:00:00 2001 From: Tinsh Date: Thu, 16 Sep 2021 10:40:45 +0800 Subject: [PATCH 139/145] docs(AuthorizationServer): fix a typo in a comment --- src/AuthorizationServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index a719656c6..4d6862157 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -241,7 +241,7 @@ public function setDefaultScope($defaultScope) } /** - * Sets wether to revoke refresh tokens or not (for all grant types). + * Sets whether to revoke refresh tokens or not (for all grant types). * * @param bool $revokeRefreshTokens */ From e5c981c5048f0160710c80da054dea2829d0102a Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Thu, 30 Sep 2021 14:28:47 +0200 Subject: [PATCH 140/145] Avoid using broken `LocalFileReference` from lcobucci/jwt --- tests/AuthorizationValidators/BearerTokenValidatorTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/AuthorizationValidators/BearerTokenValidatorTest.php b/tests/AuthorizationValidators/BearerTokenValidatorTest.php index 17319bda4..838d2bbae 100644 --- a/tests/AuthorizationValidators/BearerTokenValidatorTest.php +++ b/tests/AuthorizationValidators/BearerTokenValidatorTest.php @@ -5,7 +5,7 @@ use DateInterval; use DateTimeImmutable; use Laminas\Diactoros\ServerRequest; -use Lcobucci\JWT\Signer\Key\LocalFileReference; +use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator; use League\OAuth2\Server\CryptKey; @@ -34,7 +34,7 @@ public function testBearerTokenValidatorAcceptsValidToken() ->expiresAt((new DateTimeImmutable())->add(new DateInterval('PT1H'))) ->relatedTo('user-id') ->withClaim('scopes', 'scope1 scope2 scope3 scope4') - ->getToken(new Sha256(), LocalFileReference::file(__DIR__ . '/../Stubs/private.key')); + ->getToken(new Sha256(), InMemory::file(__DIR__ . '/../Stubs/private.key')); $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $validJwt->toString())); @@ -62,7 +62,7 @@ public function testBearerTokenValidatorRejectsExpiredToken() ->expiresAt((new DateTimeImmutable())->sub(new DateInterval('PT1H'))) ->relatedTo('user-id') ->withClaim('scopes', 'scope1 scope2 scope3 scope4') - ->getToken(new Sha256(), LocalFileReference::file(__DIR__ . '/../Stubs/private.key')); + ->getToken(new Sha256(), InMemory::file(__DIR__ . '/../Stubs/private.key')); $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $expiredJwt->toString())); From 86d96d2e7879291e5455b6f4aa5e13df4339cfe5 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 11 Oct 2021 21:33:34 +0100 Subject: [PATCH 141/145] Add details to changelog for LocalFileReference() deprecation --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 099fd33d5..6db9c463c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [8.3.3] - released 2021-10-11 +### Security +- Removed the use of `LocalFileReference()` in lcobucci/jwt. Function deprecated as per [GHSA-7322-jrq4-x5hf](https://github.com/lcobucci/jwt/security/advisories/GHSA-7322-jrq4-x5hf) (PR #1249) + ## [8.3.2] - released 2021-07-27 ### Changed - Conditionally support the `StrictValidAt()` method in lcobucci/jwt so we can use version 4.1.x or greater of the library (PR #1236) @@ -552,7 +556,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.3.2...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.3.3...HEAD +[8.3.3]: https://github.com/thephpleague/oauth2-server/compare/8.3.2...8.3.3 [8.3.2]: https://github.com/thephpleague/oauth2-server/compare/8.3.1...8.3.2 [8.3.1]: https://github.com/thephpleague/oauth2-server/compare/8.3.0...8.3.1 [8.3.0]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...8.3.0 From f28ca2055fe3b7288cc656a836e393fa487804f2 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 11 Oct 2021 21:35:23 +0100 Subject: [PATCH 142/145] Update examples to use lcobucci/jwt 4.0.4 as minimum --- examples/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/composer.json b/examples/composer.json index d265472e6..730b45d18 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -4,7 +4,7 @@ }, "require-dev": { "league/event": "^2.2", - "lcobucci/jwt": "^3.4 || ^4.0", + "lcobucci/jwt": "^3.4 || ^4.0.4", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "laminas/laminas-diactoros": "^2.5.0" From 9be7ebb5b422f6cfe3ca6d805edc607ccb05bf09 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 11 Oct 2021 21:39:48 +0100 Subject: [PATCH 143/145] Update version constraint for lcobucci/jwt --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6d01aecd7..a551eb53a 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": "^7.2 || ^8.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.4 || ^4.0", + "lcobucci/jwt": "^3.4 || ^4.0.4", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "ext-json": "*" From f5698a3893eda9a17bcd48636990281e7ca77b2a Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 11 Oct 2021 21:41:49 +0100 Subject: [PATCH 144/145] Update version constraint for lcobucci/jwt --- composer.json | 2 +- examples/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index a551eb53a..7cfbc0cc4 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": "^7.2 || ^8.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.4 || ^4.0.4", + "lcobucci/jwt": "^3.4.6 || ^4.0.4", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "ext-json": "*" diff --git a/examples/composer.json b/examples/composer.json index 730b45d18..087caab72 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -4,7 +4,7 @@ }, "require-dev": { "league/event": "^2.2", - "lcobucci/jwt": "^3.4 || ^4.0.4", + "lcobucci/jwt": "^3.4.6 || ^4.0.4", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", "laminas/laminas-diactoros": "^2.5.0" From 1d035e7094968dba7fd90d97c65da4d54c43bb8c Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 13 Oct 2021 21:00:30 +0200 Subject: [PATCH 145/145] Use thephpleague/oauth2-server-bundle for the Symfony integration --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ecd65944e..5307b840a 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ We use [Github Actions](https://github.com/features/actions), [Scrutinizer](http * [Laravel Passport](https://github.com/laravel/passport) * [OAuth 2 Server for CakePHP 3](https://github.com/uafrica/oauth-server) * [OAuth 2 Server for Mezzio](https://github.com/mezzio/mezzio-authentication-oauth2) -* [Trikoder OAuth 2 Bundle (Symfony)](https://github.com/trikoder/oauth2-bundle) +* [OAuth 2 Server Bundle (Symfony)](https://github.com/thephpleague/oauth2-server-bundle) * [Heimdall for CodeIgniter 4](https://github.com/ezralazuardy/heimdall) ## Changelog