diff --git a/CHANGELOG.md b/CHANGELOG.md index 561b775..7d0ddb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,96 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 2.0.1 - TBD + +### Added + +- Nothing. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 2.0.0 - 2019-12-28 + +### Added + +- Nothing. + +### Changed + +- [#69](https://github.com/zendframework/zend-expressive-authentication-oauth2/pull/69) updates the minimum supported version of league/oauth-server to ^8.0. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 1.3.0 - 2019-12-28 + +### Added + +- [#62](https://github.com/zendframework/zend-expressive-authentication-oauth2/pull/62) adds the ability to configure and add event listeners for the underlying league/oauth2 implementation. See the [event listeners configuration documentation](https://docs.zendframework.com/zend-expressive-authentication-oauth2/intro/#configure-event-listeners) for more information. + +### Changed + +- Nothing. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- Nothing. + +## 1.2.1 - 2019-12-28 + +### Added + +- Nothing. + +### Changed + +- [#55](https://github.com/zendframework/zend-expressive-authentication-oauth2/pull/55) changes how the `OAuth2Adapter` validates when a client ID is present. Previously, if a client ID was present, but not a user ID, it would attempt to pull a user from the user factory using the client ID, which was incorrect. With this release, it no longer does that. + +### Deprecated + +- Nothing. + +### Removed + +- Nothing. + +### Fixed + +- [#71](https://github.com/zendframework/zend-expressive-authentication-oauth2/pull/71) adds a check to `AccessTokenRepository` to verify that a row was returned before checking if a token was revoked, raising an exception if not. + +- [#72](https://github.com/zendframework/zend-expressive-authentication-oauth2/pull/72) updates the database schema in provided examples to reflect actual requirements. + ## 1.2.0 - 2019-09-01 ### Added diff --git a/README.md b/README.md index 4fd5d06..aaa8e09 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # OAuth2 server middleware for Expressive and PSR-7 applications +> ## Repository abandoned 2019-12-31 +> +> This repository has moved to [mezzio/mezzio-authentication-oauth2](https://github.com/mezzio/mezzio-authentication-oauth2). + [![Build Status](https://secure.travis-ci.org/zendframework/zend-expressive-authentication-oauth2.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-expressive-authentication-oauth2) [![Coverage Status](https://coveralls.io/repos/github/zendframework/zend-expressive-authentication-oauth2/badge.svg?branch=master)](https://coveralls.io/github/zendframework/zend-expressive-authentication-oauth2?branch=master) diff --git a/composer.json b/composer.json index c421e58..180b8d4 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ }, "require": { "php": "^7.1", - "league/oauth2-server": "^7.3.0", + "league/oauth2-server": "^8.0.0", "psr/container": "^1.0", "psr/http-message": "^1.0.1", "psr/http-server-middleware": "^1.0", @@ -53,8 +53,8 @@ }, "extra": { "branch-alias": { - "dev-master": "1.2.x-dev", - "dev-develop": "1.3.x-dev" + "dev-master": "2.0.x-dev", + "dev-develop": "2.1.x-dev" }, "zf": { "config-provider": "Zend\\Expressive\\Authentication\\OAuth2\\ConfigProvider" diff --git a/composer.lock b/composer.lock index 2de372e..1915661 100644 --- a/composer.lock +++ b/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": "bda1e7feca55a8efac26338aa133c188", + "content-hash": "607e2fc1e526e325c6d9b176d6250da4", "packages": [ { "name": "defuse/php-encryption", @@ -113,8 +113,8 @@ "authors": [ { "name": "Luís Otávio Cobucci Oblonczyk", - "role": "Developer", - "email": "lcobucci@gmail.com" + "email": "lcobucci@gmail.com", + "role": "Developer" } ], "description": "A simple library to work with JSON Web Token and JSON Web Signature", @@ -176,24 +176,25 @@ }, { "name": "league/oauth2-server", - "version": "7.4.0", + "version": "8.0.0", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-server.git", - "reference": "2eb1cf79e59d807d89c256e7ac5e2bf8bdbd4acf" + "reference": "e1dc4d708c56fcfa205be4bb1862b6d525b4baac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/2eb1cf79e59d807d89c256e7ac5e2bf8bdbd4acf", - "reference": "2eb1cf79e59d807d89c256e7ac5e2bf8bdbd4acf", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/e1dc4d708c56fcfa205be4bb1862b6d525b4baac", + "reference": "e1dc4d708c56fcfa205be4bb1862b6d525b4baac", "shasum": "" }, "require": { - "defuse/php-encryption": "^2.1", + "defuse/php-encryption": "^2.2.1", + "ext-json": "*", "ext-openssl": "*", - "lcobucci/jwt": "^3.2.2", - "league/event": "^2.1", - "php": ">=7.0.0", + "lcobucci/jwt": "^3.3.1", + "league/event": "^2.2", + "php": ">=7.1.0", "psr/http-message": "^1.0.1" }, "replace": { @@ -201,12 +202,11 @@ "lncd/oauth2": "*" }, "require-dev": { - "phpstan/phpstan": "^0.9.2", - "phpstan/phpstan-phpunit": "^0.9.4", - "phpstan/phpstan-strict-rules": "^0.9.0", - "phpunit/phpunit": "^6.3 || ^7.0", + "phpstan/phpstan": "^0.11.8", + "phpstan/phpstan-phpunit": "^0.11.2", + "phpunit/phpunit": "^7.5.13 || ^8.2.3", "roave/security-advisories": "dev-master", - "zendframework/zend-diactoros": "^1.3.2" + "zendframework/zend-diactoros": "^2.1.2" }, "type": "library", "autoload": { @@ -249,7 +249,7 @@ "secure", "server" ], - "time": "2019-05-05T09:22:01+00:00" + "time": "2019-07-13T18:58:26+00:00" }, { "name": "paragonie/random_compat", @@ -598,20 +598,21 @@ ], "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", "homepage": "https://github.com/container-interop/container-interop", + "abandoned": "psr/container", "time": "2017-02-14T19:40:03+00:00" }, { "name": "doctrine/instantiator", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "a2c590166b2133a4633738648b6b064edae0814a" + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", - "reference": "a2c590166b2133a4633738648b6b064edae0814a", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", + "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", "shasum": "" }, "require": { @@ -654,20 +655,20 @@ "constructor", "instantiate" ], - "time": "2019-03-17T17:37:11+00:00" + "time": "2019-10-21T16:45:58+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.9.3", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + "reference": "579bb7356d91f9456ccd505f24ca8b667966a0a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", - "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/579bb7356d91f9456ccd505f24ca8b667966a0a7", + "reference": "579bb7356d91f9456ccd505f24ca8b667966a0a7", "shasum": "" }, "require": { @@ -702,7 +703,7 @@ "object", "object graph" ], - "time": "2019-08-09T12:45:53+00:00" + "time": "2019-12-15T19:12:40+00:00" }, { "name": "phar-io/manifest", @@ -742,18 +743,18 @@ "authors": [ { "name": "Arne Blankerts", - "role": "Developer", - "email": "arne@blankerts.de" + "email": "arne@blankerts.de", + "role": "Developer" }, { "name": "Sebastian Heuer", - "role": "Developer", - "email": "sebastian@phpeople.de" + "email": "sebastian@phpeople.de", + "role": "Developer" }, { "name": "Sebastian Bergmann", - "role": "Developer", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "Developer" } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", @@ -808,35 +809,33 @@ }, { "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", + "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", "shasum": "" }, "require": { - "php": ">=5.5" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "phpunit/phpunit": "~6" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -858,31 +857,32 @@ "reflection", "static analysis" ], - "time": "2017-09-11T18:02:19+00:00" + "time": "2018-08-07T13:53:10+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.1", + "version": "4.3.4", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", - "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", "shasum": "" }, "require": { "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", "webmozart/assert": "^1.0" }, "require-dev": { - "doctrine/instantiator": "~1.0.5", + "doctrine/instantiator": "^1.0.5", "mockery/mockery": "^1.0", + "phpdocumentor/type-resolver": "0.4.*", "phpunit/phpunit": "^6.4" }, "type": "library", @@ -909,41 +909,40 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-04-30T17:48:53+00:00" + "time": "2019-12-28T18:55:12+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "php": "^7.1", + "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-tokenizer": "^7.1", + "mockery/mockery": "~1", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -956,37 +955,38 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14T14:27:02+00:00" + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2019-08-22T18:11:29+00:00" }, { "name": "phpspec/prophecy", - "version": "1.8.1", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", - "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/cbe1df668b3fe136bcc909126a0f529a78d4cbbc", + "reference": "cbe1df668b3fe136bcc909126a0f529a78d4cbbc", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", + "phpspec/phpspec": "^2.5 || ^3.2", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.10.x-dev" } }, "autoload": { @@ -1019,20 +1019,20 @@ "spy", "stub" ], - "time": "2019-06-13T12:50:23+00:00" + "time": "2019-12-22T21:05:45+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "7.0.7", + "version": "7.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7743bbcfff2a907e9ee4a25be13d0f8ec5e73800" + "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7743bbcfff2a907e9ee4a25be13d0f8ec5e73800", - "reference": "7743bbcfff2a907e9ee4a25be13d0f8ec5e73800", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf", + "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf", "shasum": "" }, "require": { @@ -1041,7 +1041,7 @@ "php": "^7.2", "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.1.0", + "phpunit/php-token-stream": "^3.1.1", "sebastian/code-unit-reverse-lookup": "^1.0.1", "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", @@ -1082,7 +1082,7 @@ "testing", "xunit" ], - "time": "2019-07-25T05:31:54+00:00" + "time": "2019-11-20T13:55:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1122,8 +1122,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "role": "lead", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], "description": "FilterIterator implementation that filters files based on a list of suffixes.", @@ -1213,8 +1213,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "role": "lead", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], "description": "Utility class for timing", @@ -1226,16 +1226,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a" + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a", - "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", + "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", "shasum": "" }, "require": { @@ -1271,20 +1271,20 @@ "keywords": [ "tokenizer" ], - "time": "2019-07-25T05:29:42+00:00" + "time": "2019-09-17T06:23:10+00:00" }, { "name": "phpunit/phpunit", - "version": "8.3.4", + "version": "8.5.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e31cce0cf4499c0ccdbbb211a3280d36ab341e36" + "reference": "7870c78da3c5e4883eaef36ae47853ebb3cb86f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e31cce0cf4499c0ccdbbb211a3280d36ab341e36", - "reference": "e31cce0cf4499c0ccdbbb211a3280d36ab341e36", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7870c78da3c5e4883eaef36ae47853ebb3cb86f2", + "reference": "7870c78da3c5e4883eaef36ae47853ebb3cb86f2", "shasum": "" }, "require": { @@ -1307,7 +1307,7 @@ "sebastian/comparator": "^3.0.2", "sebastian/diff": "^3.0.2", "sebastian/environment": "^4.2.2", - "sebastian/exporter": "^3.1.0", + "sebastian/exporter": "^3.1.1", "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", "sebastian/resource-operations": "^2.0.1", @@ -1328,7 +1328,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.3-dev" + "dev-master": "8.5-dev" } }, "autoload": { @@ -1343,8 +1343,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "role": "lead", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], "description": "The PHP Unit Testing framework.", @@ -1354,7 +1354,7 @@ "testing", "xunit" ], - "time": "2019-08-11T06:56:55+00:00" + "time": "2019-12-25T14:49:39+00:00" }, { "name": "psr/http-factory", @@ -1414,12 +1414,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "ea693fa060702164985511acc3ceb5389c9ac761" + "reference": "44a677c8e06241a66409ae6e4820dc166fc09ab2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ea693fa060702164985511acc3ceb5389c9ac761", - "reference": "ea693fa060702164985511acc3ceb5389c9ac761", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/44a677c8e06241a66409ae6e4820dc166fc09ab2", + "reference": "44a677c8e06241a66409ae6e4820dc166fc09ab2", "shasum": "" }, "conflict": { @@ -1440,9 +1440,9 @@ "composer/composer": "<=1-alpha.11", "contao-components/mediaelement": ">=2.14.2,<2.21.1", "contao/core": ">=2,<3.5.39", - "contao/core-bundle": ">=4,<4.4.39|>=4.5,<4.7.5", + "contao/core-bundle": ">=4,<4.4.46|>=4.5,<4.8.6", "contao/listing-bundle": ">=4,<4.4.8", - "contao/newsletter-bundle": ">=4,<4.1", + "datadog/dd-trace": ">=0.30,<0.30.2", "david-garcia/phpwhois": "<=4.3.1", "doctrine/annotations": ">=1,<1.2.7", "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", @@ -1454,8 +1454,8 @@ "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", "dompdf/dompdf": ">=0.6,<0.6.2", - "drupal/core": ">=7,<7.67|>=8,<8.6.16|>=8.7,<8.7.1|>8.7.3,<8.7.5", - "drupal/drupal": ">=7,<7.67|>=8,<8.6.16|>=8.7,<8.7.1|>8.7.3,<8.7.5", + "drupal/core": ">=7,<8.7.11|>=8.8,<8.8.1", + "drupal/drupal": ">=7,<8.7.11|>=8.8,<8.8.1", "erusev/parsedown": "<1.7.2", "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.4", "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.13.1|>=6,<6.7.9.1|>=6.8,<6.13.5.1|>=7,<7.2.4.1|>=7.3,<7.3.2.1", @@ -1485,9 +1485,9 @@ "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", "league/commonmark": "<0.18.3", - "magento/magento1ce": "<1.9.4.1", - "magento/magento1ee": ">=1.9,<1.14.4.1", - "magento/product-community-edition": ">=2,<2.2.8|>=2.3,<2.3.1", + "magento/magento1ce": "<1.9.4.3", + "magento/magento1ee": ">=1,<1.14.4.3", + "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2", "monolog/monolog": ">=1.8,<1.12", "namshi/jose": "<2.2", "onelogin/php-saml": "<2.10.4", @@ -1508,8 +1508,9 @@ "propel/propel": ">=2-alpha.1,<=2-alpha.7", "propel/propel1": ">=1,<=1.7.1", "pusher/pusher-php-server": "<2.2.1", - "robrichards/xmlseclibs": ">=1,<3.0.2", + "robrichards/xmlseclibs": ">=1,<3.0.4", "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", + "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", "sensiolabs/connect": "<4.2.3", "serluck/phpwhois": "<=4.2.6", "shopware/shopware": "<5.3.7", @@ -1522,7 +1523,7 @@ "silverstripe/userforms": "<3", "simple-updates/phpwhois": "<=1", "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", - "simplesamlphp/simplesamlphp": "<1.17.3", + "simplesamlphp/simplesamlphp": "<1.17.8", "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", "slim/slim": "<2.6", "smarty/smarty": "<3.1.33", @@ -1530,18 +1531,20 @@ "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", "stormpath/sdk": ">=0,<9.9.99", + "studio-42/elfinder": "<2.1.48", "swiftmailer/swiftmailer": ">=4,<5.4.5", "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", "sylius/grid-bundle": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", "sylius/sylius": ">=1,<1.1.18|>=1.2,<1.2.17|>=1.3,<1.3.12|>=1.4,<1.4.4", - "symfony/cache": ">=3.1,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/http-foundation": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8", + "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/http-kernel": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/mime": ">=4.3,<4.3.8", "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/polyfill": ">=1,<1.10", "symfony/polyfill-php55": ">=1,<1.10", @@ -1552,11 +1555,12 @@ "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8", "symfony/serializer": ">=2,<2.0.11", - "symfony/symfony": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/symfony": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", "symfony/translation": ">=2,<2.0.17", "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", "tecnickcom/tcpdf": "<6.2.22", @@ -1566,8 +1570,8 @@ "titon/framework": ">=0,<9.9.99", "truckersmp/phpwhois": "<=4.3.1", "twig/twig": "<1.38|>=2,<2.7", - "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.27|>=9,<9.5.8", - "typo3/cms-core": ">=8,<8.7.27|>=9,<9.5.8", + "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", + "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", @@ -1621,7 +1625,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2018-03-07T15:45:44+00:00" + "time": "2019-12-26T14:16:40+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1790,16 +1794,16 @@ }, { "name": "sebastian/environment", - "version": "4.2.2", + "version": "4.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", - "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", + "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", "shasum": "" }, "require": { @@ -1839,20 +1843,20 @@ "environment", "hhvm" ], - "time": "2019-05-05T09:05:15+00:00" + "time": "2019-11-20T08:46:58+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.1", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "06a9a5947f47b3029d76118eb5c22802e5869687" + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/06a9a5947f47b3029d76118eb5c22802e5869687", - "reference": "06a9a5947f47b3029d76118eb5c22802e5869687", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", + "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", "shasum": "" }, "require": { @@ -1906,7 +1910,7 @@ "export", "exporter" ], - "time": "2019-08-11T12:43:14+00:00" + "time": "2019-09-14T09:02:43+00:00" }, { "name": "sebastian/global-state", @@ -2187,8 +2191,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "role": "lead", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], "description": "Collection of value objects that represent the types of the PHP type system", @@ -2318,16 +2322,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.12.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", "shasum": "" }, "require": { @@ -2339,7 +2343,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -2372,7 +2376,7 @@ "polyfill", "portable" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "theseer/tokenizer", @@ -2407,8 +2411,8 @@ "authors": [ { "name": "Arne Blankerts", - "role": "Developer", - "email": "arne@blankerts.de" + "email": "arne@blankerts.de", + "role": "Developer" } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", @@ -2416,31 +2420,29 @@ }, { "name": "webmozart/assert", - "version": "1.5.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", - "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", + "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", "shasum": "" }, "require": { "php": "^5.3.3 || ^7.0", "symfony/polyfill-ctype": "^1.8" }, + "conflict": { + "vimeo/psalm": "<3.6.0" + }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -2462,7 +2464,7 @@ "check", "validate" ], - "time": "2019-08-24T08:43:50+00:00" + "time": "2019-11-24T13:36:37+00:00" }, { "name": "zendframework/zend-coding-standard", @@ -2495,16 +2497,16 @@ }, { "name": "zendframework/zend-diactoros", - "version": "2.1.3", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1" + "reference": "de5847b068362a88684a55b0dbb40d85986cfa52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/279723778c40164bcf984a2df12ff2c6ec5e61c1", - "reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/de5847b068362a88684a55b0dbb40d85986cfa52", + "reference": "de5847b068362a88684a55b0dbb40d85986cfa52", "shasum": "" }, "require": { @@ -2517,6 +2519,7 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { + "ext-curl": "*", "ext-dom": "*", "ext-libxml": "*", "http-interop/http-factory-tests": "^0.5.0", @@ -2557,7 +2560,7 @@ "psr", "psr-7" ], - "time": "2019-07-10T16:13:25+00:00" + "time": "2019-11-13T19:16:13+00:00" }, { "name": "zendframework/zend-servicemanager", diff --git a/data/oauth2.sql b/data/oauth2.sql index 508e667..f971783 100644 --- a/data/oauth2.sql +++ b/data/oauth2.sql @@ -1,66 +1,90 @@ -CREATE TABLE oauth_auth_codes ( - id VARCHAR(100), - user_id INTEGER, - client_id INTEGER, - scopes TEXT NULL, - revoked BOOLEAN, - expires_at TIMESTAMP NULL, - PRIMARY KEY(id) -); +-- +-- Table structure for table `oauth_access_tokens` +-- -CREATE TABLE oauth_access_tokens ( - id VARCHAR(100), - user_id VARCHAR(40) NULL, - client_id VARCHAR(40), - name VARCHAR(255) NULL, - scopes TEXT NULL, - revoked BOOLEAN, - created_at TIMESTAMP NULL, - updated_at TIMESTAMP NULL, - expires_at TIMESTAMP NULL, - PRIMARY KEY(id) +CREATE TABLE `oauth_access_tokens` ( + `id` varchar(100) PRIMARY KEY NOT NULL, + `user_id` int(10) DEFAULT NULL, + `client_id` int(10) NOT NULL, + `name` varchar(255) DEFAULT NULL, + `scopes` text, + `revoked` tinyint(1) NOT NULL DEFAULT '0', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime DEFAULT NULL, + `expires_at` datetime NOT NULL ); -CREATE INDEX idx1_oauth_access_tokens ON oauth_access_tokens(user_id); - -CREATE TABLE oauth_refresh_tokens ( - id VARCHAR(100), - access_token_id VARCHAR(100), - revoked BOOLEAN, - expires_at TIMESTAMP NULL, - PRIMARY KEY(id) + +CREATE INDEX `IDX_CA42527CA76ED39519EB6921BDA26CCD` ON oauth_access_tokens (`user_id`,`client_id`); +CREATE INDEX `IDX_CA42527CA76ED395` ON oauth_access_tokens (`user_id`); +CREATE INDEX `IDX_CA42527C19EB6921` ON oauth_access_tokens (`client_id`); + +-- +-- Table structure for table `oauth_auth_codes` +-- + +CREATE TABLE `oauth_auth_codes` ( + `id` varchar(100) PRIMARY KEY NOT NULL, + `user_id` int(10) DEFAULT NULL, + `client_id` int(10) NOT NULL, + `scopes` text, + `revoked` tinyint(1) NOT NULL DEFAULT '0', + `expires_at` datetime DEFAULT NULL ); -CREATE INDEX idx1_oauth_refresh_tokens ON oauth_refresh_tokens(access_token_id); - -CREATE TABLE oauth_clients ( - name VARCHAR(40) NOT NULL, - user_id INTEGER NULL, - secret VARCHAR(100) NULL, - redirect VARCHAR(255), - personal_access_client BOOLEAN, - password_client BOOLEAN, - revoked BOOLEAN, - created_at TIMESTAMP NULL, - updated_at TIMESTAMP NULL, - PRIMARY KEY (name) + +CREATE INDEX `IDX_BB493F83A76ED395` ON oauth_auth_codes (`user_id`); +CREATE INDEX `IDX_BB493F8319EB6921` ON oauth_auth_codes (`client_id`); + +-- +-- Table structure for table `oauth_clients` +-- + +CREATE TABLE `oauth_clients` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `user_id` int(10) DEFAULT NULL, + `name` varchar(100) NOT NULL, + `secret` varchar(100) DEFAULT NULL, + `redirect` varchar(255) DEFAULT NULL, + `personal_access_client` tinyint(1) DEFAULT NULL, + `password_client` tinyint(1) DEFAULT NULL, + `revoked` tinyint(1) DEFAULT NULL, + `created_at` datetime DEFAULT CURRENT_TIMESTAMP, + `updated_at` datetime DEFAULT NULL ); -CREATE INDEX idx1_oauth_clients ON oauth_clients(user_id); -CREATE TABLE oauth_personal_access_clients ( - client_id INTEGER, - created_at TIMESTAMP NULL, - updated_at TIMESTAMP NULL +CREATE INDEX `IDX_13CE81015E237E06A76ED395BDA26CCD` ON oauth_clients (`name`,`user_id`); +CREATE INDEX `IDX_13CE8101A76ED395` ON oauth_clients (`user_id`); + +-- +-- Table structure for table `oauth_refresh_tokens` +-- + +CREATE TABLE `oauth_refresh_tokens` ( + `id` varchar(100) PRIMARY KEY NOT NULL, + `access_token_id` varchar(100) NOT NULL, + `revoked` tinyint(1) NOT NULL DEFAULT '0', + `expires_at` datetime NOT NULL ); -CREATE INDEX idx1_oauth_personal_access_clients ON oauth_personal_access_clients(client_id); - -CREATE TABLE oauth_users ( - username VARCHAR(40) NOT NULL, - password VARCHAR(100) NOT NULL, - first_name VARCHAR(80), - last_name VARCHAR(80), - PRIMARY KEY (username) + +CREATE INDEX `IDX_5AB6872CCB2688BDA26CCD` ON oauth_refresh_tokens (`access_token_id`); + +-- +-- Table structure for table `oauth_scopes` +-- + +CREATE TABLE `oauth_scopes` ( + `id` varchar(100) PRIMARY KEY NOT NULL ); -CREATE TABLE oauth_scopes ( - id VARCHAR(30) NOT NULL, - PRIMARY KEY (id) +-- +-- Table structure for table `oauth_users` +-- + +CREATE TABLE `oauth_users` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `username` varchar(320) UNIQUE NOT NULL, + `password` varchar(100) NOT NULL, + `first_name` varchar(80) DEFAULT NULL, + `last_name` varchar(80) DEFAULT NULL ); + +CREATE INDEX `UNIQ_93804FF8F85E0677` ON oauth_users (`username`); diff --git a/docs/book/v1/intro.md b/docs/book/v1/intro.md index 8df0477..16dbf3b 100644 --- a/docs/book/v1/intro.md +++ b/docs/book/v1/intro.md @@ -81,6 +81,20 @@ The `private_key` and `public_key` values contains the paths to the previous generated pair of keys. The `encryption_key` contains the encryption key value as a string, as stored in the `data/oauth/encryption.key` file. +By default both key files are checked for correct permissions (chmod 400, 440, +600, 640 or 660 is expected, and 600 or 660 is recommended). In case the +environment/operating system (e.g. Windows) does not support such a permissions, +the check can be disabled: + +```php + // ... + 'private_key' => [ + 'key_or_path' => __DIR__ . '/../data/oauth/private.key', + 'key_permissions_check' => false, + ], + // ... +``` + The `access_token_expire` value is the time-to-live (TTL) value of the access token. The time period is represented using the [DateInterval](http://php.net/manual/en/class.dateinterval.php) format in PHP. The default value is `P1D` (1 day). @@ -112,6 +126,50 @@ grants are configured to be available. If you would like to disable any of the supplied grants, change the value for the grant to `null`. Additionally, you can extend this array to add your own custom grants. +### Configure Event Listeners + +- **Since 1.3.0** + +_Optional_ The `event_listeners` and `event_listener_providers` arrays may be used to enable event listeners for events published by `league\oauth2-server`. See the [Authorization Server Domain Events documentation](https://oauth2.thephpleague.com/authorization-server/events/). The possible event names can be found [in `League\OAuth2\Server\RequestEvent`](https://github.com/thephpleague/oauth2-server/blob/0b0b43d43342c0909b3b32fb7a09d502c368d2ec/src/RequestEvent.php#L17-L22). + +#### Event Listeners + +The `event_listeners` key must contain an array of arrays. Each array element must contain at least 2 elements and may include a 3rd element. These roughly correspond to the arguments passed to [`League\Event\ListenerAcceptorInterface::addListener()`](https://github.com/thephpleague/event/blob/d2cc124cf9a3fab2bb4ff963307f60361ce4d119/src/ListenerAcceptorInterface.php#L43). The first element must be a string -- either the [wildcard (`*`)](https://event.thephpleague.com/2.0/listeners/wildcard/) or a [single event name](https://event.thephpleague.com/2.0/events/named/). The second element must be either a callable, a concrete instance of `League\Event\ListenerInterface`, or a string pointing to your listener service instance in the container. The third element is optional, and must be an integer if provided. + +See the [documentation for callable listeners](https://event.thephpleague.com/2.0/listeners/callables/). + +#### Event Listener Providers + +The `event_listener_providers` key must contain an array. Each array element must contain either a concrete instance of `League\Event\ListenerProviderInterface` or a string pointing to your container service instance of a listener provider. + +See the [documentation for listener providers](https://event.thephpleague.com/2.0/listeners/providers/). + +Example config: + +```php +return [ + 'event_listeners' => [ + // using a container service + [ + \League\OAuth2\Server\RequestEvent::CLIENT_AUTHENTICATION_FAILED, + \My\Event\Listener\Service::class, + ], + // using a callable + [ + \League\OAuth2\Server\RequestEvent::ACCESS_TOKEN_ISSUED, + function (\League\OAuth2\Server\RequestEvent $event) { + // do something + }, + ], + ], + 'event_listener_providers' => [ + \My\Event\ListenerProvider\Service::class, + ], +]; +``` + +## OAuth2 Database + You need to provide an OAuth2 database yourself, or generate a [SQLite](https://www.sqlite.org) database with the following command (using `sqlite3` for GNU/Linux): @@ -138,7 +196,7 @@ For security reason, the client `secret` and the user `password` are stored using the `bcrypt` algorithm as used by the [password_hash](http://php.net/manual/en/function.password-hash.php) function. -## Configure OAuth2 routes +## Configure OAuth2 Routes As the final step, in order to use the OAuth2 server you need to configure the routes for the **token endpoint** and **authorization**. diff --git a/src/AuthorizationServerFactory.php b/src/AuthorizationServerFactory.php index 1ecbc76..b4ad06f 100644 --- a/src/AuthorizationServerFactory.php +++ b/src/AuthorizationServerFactory.php @@ -11,20 +11,30 @@ namespace Zend\Expressive\Authentication\OAuth2; use DateInterval; +use League\Event\ListenerProviderInterface; + use League\OAuth2\Server\AuthorizationServer; -use League\OAuth2\Server\Grant; -use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; -use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; -use League\OAuth2\Server\Repositories\UserRepositoryInterface; use Psr\Container\ContainerInterface; -use Zend\Expressive\Authentication\OAuth2\Exception\InvalidConfigException; +/** + * Factory for OAuth AuthorizationServer + * + * Initializes a new AuthorizationServer with required params from config. + * Then configured grant types are enabled with configured access token + * expiry. Then any optionally configured event listeners are attached to the + * AuthorizationServer. + */ class AuthorizationServerFactory { use ConfigTrait; use CryptKeyTrait; use RepositoryTrait; + /** + * @param ContainerInterface $container + * + * @return AuthorizationServer + */ public function __invoke(ContainerInterface $container) : AuthorizationServer { $clientRepository = $this->getClientRepository($container); @@ -46,7 +56,7 @@ public function __invoke(ContainerInterface $container) : AuthorizationServer $accessTokenInterval = new DateInterval($this->getAccessTokenExpire($container)); foreach ($grants as $grant) { - // Config may set this grant to null. Continue on if grant has been disabled + // Config may set this grant to null. Continue on if grant has been disabled if (empty($grant)) { continue; } @@ -57,6 +67,76 @@ public function __invoke(ContainerInterface $container) : AuthorizationServer ); } + // add listeners if configured + $this->addListeners($authServer, $container); + + // add listener providers if configured + $this->addListenerProviders($authServer, $container); + return $authServer; } + + /** + * Optionally add event listeners + * + * @param AuthorizationServer $authServer + * @param ContainerInterface $container + */ + private function addListeners( + AuthorizationServer $authServer, + ContainerInterface $container + ): void { + $listeners = $this->getListenersConfig($container); + + foreach ($listeners as $idx => $listenerConfig) { + $event = $listenerConfig[0]; + $listener = $listenerConfig[1]; + $priority = $listenerConfig[2] ?? null; + if (is_string($listener)) { + if (! $container->has($listener)) { + throw new Exception\InvalidConfigException(sprintf( + 'The second element of event_listeners config at ' . + 'index "%s" is a string and therefore expected to ' . + 'be available as a service key in the container. ' . + 'A service named "%s" was not found.', + $idx, + $listener + )); + } + $listener = $container->get($listener); + } + $authServer->getEmitter() + ->addListener($event, $listener, $priority); + } + } + + /** + * Optionally add event listener providers + * + * @param AuthorizationServer $authServer + * @param ContainerInterface $container + */ + private function addListenerProviders( + AuthorizationServer $authServer, + ContainerInterface $container + ): void { + $providers = $this->getListenerProvidersConfig($container); + + foreach ($providers as $idx => $provider) { + if (is_string($provider)) { + if (! $container->has($provider)) { + throw new Exception\InvalidConfigException(sprintf( + 'The event_listener_providers config at ' . + 'index "%s" is a string and therefore expected to ' . + 'be available as a service key in the container. ' . + 'A service named "%s" was not found.', + $idx, + $provider + )); + } + $provider = $container->get($provider); + } + $authServer->getEmitter()->useListenerProvider($provider); + } + } } diff --git a/src/ConfigTrait.php b/src/ConfigTrait.php index 163e9bf..1b75670 100644 --- a/src/ConfigTrait.php +++ b/src/ConfigTrait.php @@ -103,4 +103,46 @@ protected function getGrantsConfig(ContainerInterface $container) : array return $config['grants']; } + + /** + * @param ContainerInterface $container + * + * @return array + */ + protected function getListenersConfig(ContainerInterface $container) : array + { + $config = $container->get('config')['authentication'] ?? []; + + if (empty($config['event_listeners'])) { + return []; + } + if (! is_array($config['event_listeners'])) { + throw new InvalidConfigException( + 'The event_listeners config must be an array value' + ); + } + + return $config['event_listeners']; + } + + /** + * @param ContainerInterface $container + * + * @return array + */ + protected function getListenerProvidersConfig(ContainerInterface $container) : array + { + $config = $container->get('config')['authentication'] ?? []; + + if (empty($config['event_listener_providers'])) { + return []; + } + if (! is_array($config['event_listener_providers'])) { + throw new InvalidConfigException( + 'The event_listener_providers config must be an array value' + ); + } + + return $config['event_listener_providers']; + } } diff --git a/src/OAuth2Adapter.php b/src/OAuth2Adapter.php index 999bcd6..c24659c 100644 --- a/src/OAuth2Adapter.php +++ b/src/OAuth2Adapter.php @@ -29,6 +29,11 @@ class OAuth2Adapter implements AuthenticationInterface */ protected $responseFactory; + /** + * @var callable + */ + protected $userFactory; + public function __construct( ResourceServer $resourceServer, callable $responseFactory, @@ -56,9 +61,9 @@ public function authenticate(ServerRequestInterface $request) : ?UserInterface $result = $this->resourceServer->validateAuthenticatedRequest($request); $userId = $result->getAttribute('oauth_user_id', null); $clientId = $result->getAttribute('oauth_client_id', null); - if (isset($userId) || isset($clientId)) { + if (isset($userId)) { return ($this->userFactory)( - $userId ?? $clientId, + $userId, [], [ 'oauth_user_id' => $userId, diff --git a/src/Repository/Pdo/AccessTokenRepository.php b/src/Repository/Pdo/AccessTokenRepository.php index cac3249..03aa739 100644 --- a/src/Repository/Pdo/AccessTokenRepository.php +++ b/src/Repository/Pdo/AccessTokenRepository.php @@ -12,6 +12,7 @@ use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use Zend\Expressive\Authentication\OAuth2\Entity\AccessTokenEntity; @@ -114,6 +115,9 @@ public function isAccessTokenRevoked($tokenId) return false; } $row = $sth->fetch(); + if (! is_array($row)) { + throw OAuthServerException::invalidRefreshToken(); + } return array_key_exists('revoked', $row) ? (bool) $row['revoked'] : false; } diff --git a/src/Repository/Pdo/ClientRepository.php b/src/Repository/Pdo/ClientRepository.php index a2f2107..83597a0 100644 --- a/src/Repository/Pdo/ClientRepository.php +++ b/src/Repository/Pdo/ClientRepository.php @@ -1,7 +1,7 @@ pdo->prepare( - 'SELECT * FROM oauth_clients WHERE name = :clientIdentifier' + public function getClientEntity($clientIdentifier) : ?ClientEntityInterface + { + $clientData = $this->getClientData($clientIdentifier); + + if (empty($clientData)) { + return null; + } + + return new ClientEntity( + $clientIdentifier, + $clientData['name'] ?? '', + $clientData['redirect'] ?? '' ); - $sth->bindParam(':clientIdentifier', $clientIdentifier); + } - if (false === $sth->execute()) { - return; + /** + * {@inheritDoc} + */ + public function validateClient($clientIdentifier, $clientSecret, $grantType) : bool + { + $clientData = $this->getClientData($clientIdentifier); + + if (empty($clientData)) { + return false; } - $row = $sth->fetch(); - if (empty($row) || ! $this->isGranted($row, $grantType)) { - return; + + if (! $this->isGranted($clientData, $grantType)) { + return false; } - if ($mustValidateSecret - && (empty($row['secret']) || ! password_verify((string) $clientSecret, $row['secret'])) - ) { - return; + if (empty($clientData['secret']) || ! password_verify((string) $clientSecret, $clientData['secret'])) { + return false; } - return new ClientEntity($clientIdentifier, $row['name'], $row['redirect']); + return true; } /** * Check the grantType for the client value, stored in $row * - * @param array $row + * @param array $row * @param string $grantType + * * @return bool */ protected function isGranted(array $row, string $grantType = null) : bool @@ -68,4 +79,24 @@ protected function isGranted(array $row, string $grantType = null) : bool return true; } } + + private function getClientData(string $clientIdentifier) : ?array + { + $statement = $this->pdo->prepare( + 'SELECT * FROM oauth_clients WHERE name = :clientIdentifier' + ); + $statement->bindParam(':clientIdentifier', $clientIdentifier); + + if ($statement->execute() === false) { + return null; + } + + $row = $statement->fetch(); + + if (empty($row)) { + return null; + } + + return $row; + } } diff --git a/test/AuthorizationServerFactoryTest.php b/test/AuthorizationServerFactoryTest.php index ef1df13..05d9898 100644 --- a/test/AuthorizationServerFactoryTest.php +++ b/test/AuthorizationServerFactoryTest.php @@ -10,22 +10,32 @@ namespace ZendTest\Expressive\Authentication\OAuth2; +use League\Event\ListenerProviderInterface; + use League\OAuth2\Server\AuthorizationServer; +use League\OAuth2\Server\Grant\PasswordGrant; use League\OAuth2\Server\Grant\ClientCredentialsGrant; use League\OAuth2\Server\Grant\GrantTypeInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use PHPUnit\Framework\TestCase; +use Prophecy\Prophecy\ObjectProphecy; + use Psr\Container\ContainerInterface; use Zend\Expressive\Authentication\OAuth2\AuthorizationServerFactory; +use League\OAuth2\Server\RequestEvent; +use League\Event\ListenerInterface; use function array_merge; use function array_slice; use function in_array; +use Zend\Expressive\Authentication\OAuth2\Exception\InvalidConfigException; + class AuthorizationServerFactoryTest extends TestCase { + public function testInvoke() { $mockContainer = $this->prophesize(ContainerInterface::class); @@ -41,10 +51,8 @@ public function testInvoke() 'encryption_key' => 'iALlwJ1sH77dmFCJFo+pMdM6Af4bF/hCca1EDDx7MwE=', 'access_token_expire' => 'P1D', 'grants' => [ - ClientCredentialsGrant::class - => ClientCredentialsGrant::class, - PasswordGrant::class - => PasswordGrant::class, + ClientCredentialsGrant::class => ClientCredentialsGrant::class, + PasswordGrant::class => PasswordGrant::class, ], ] ]; @@ -66,4 +74,183 @@ public function testInvoke() $this->assertInstanceOf(AuthorizationServer::class, $result); } + + /** + * @return ObjectProphecy + */ + private function getContainerMock(): ObjectProphecy + { + $mockContainer = $this->prophesize(ContainerInterface::class); + $mockClientRepo = $this->prophesize(ClientRepositoryInterface::class); + $mockAccessTokenRepo = $this->prophesize(AccessTokenRepositoryInterface::class); + $mockScopeRepo = $this->prophesize(ScopeRepositoryInterface::class); + $mockClientGrant = $this->prophesize(GrantTypeInterface::class); + $mockPasswordGrant = $this->prophesize(GrantTypeInterface::class); + + $mockContainer->has(ClientRepositoryInterface::class)->willReturn(true); + $mockContainer->has(AccessTokenRepositoryInterface::class)->willReturn(true); + $mockContainer->has(ScopeRepositoryInterface::class)->willReturn(true); + + $mockContainer->get(ClientRepositoryInterface::class)->willReturn($mockClientRepo->reveal()); + $mockContainer->get(AccessTokenRepositoryInterface::class)->willReturn($mockAccessTokenRepo->reveal()); + $mockContainer->get(ScopeRepositoryInterface::class)->willReturn($mockScopeRepo->reveal()); + $mockContainer->get(ClientCredentialsGrant::class)->willReturn($mockClientGrant->reveal()); + $mockContainer->get(PasswordGrant::class)->willReturn($mockPasswordGrant->reveal()); + + return $mockContainer; + } + + public function testInvokeWithNullGrant() + { + $mockContainer = $this->getContainerMock(); + + $config = [ + 'authentication' => [ + 'private_key' => __DIR__ . '/TestAsset/private.key', + 'encryption_key' => 'iALlwJ1sH77dmFCJFo+pMdM6Af4bF/hCca1EDDx7MwE=', + 'access_token_expire' => 'P1D', + 'grants' => [ + ClientCredentialsGrant::class => null, + PasswordGrant::class => PasswordGrant::class, + ], + ], + ]; + + $mockContainer->get('config')->willReturn($config); + + $factory = new AuthorizationServerFactory(); + + $result = $factory($mockContainer->reveal()); + + $this->assertInstanceOf(AuthorizationServer::class, $result); + } + + public function testInvokeWithListenerConfig() + { + $mockContainer = $this->getContainerMock(); + $mockListener = $this->prophesize(ListenerInterface::class); + $mockContainer->has(ListenerInterface::class)->willReturn(true); + $mockContainer->get(ListenerInterface::class)->willReturn($mockListener->reveal()); + + $config = [ + 'authentication' => [ + 'private_key' => __DIR__ . '/TestAsset/private.key', + 'encryption_key' => 'iALlwJ1sH77dmFCJFo+pMdM6Af4bF/hCca1EDDx7MwE=', + 'access_token_expire' => 'P1D', + 'grants' => [ + ClientCredentialsGrant::class => ClientCredentialsGrant::class, + ], + 'event_listeners' => [ + [ + RequestEvent::CLIENT_AUTHENTICATION_FAILED, + function (RequestEvent $event) { + // do something + }, + ], + [ + RequestEvent::CLIENT_AUTHENTICATION_FAILED, + ListenerInterface::class, + ], + ], + ], + ]; + + $mockContainer->get('config')->willReturn($config); + + $factory = new AuthorizationServerFactory(); + + $result = $factory($mockContainer->reveal()); + + $this->assertInstanceOf(AuthorizationServer::class, $result); + } + + public function testInvokeWithListenerConfigMissingServiceThrowsException() + { + $mockContainer = $this->getContainerMock(); + $mockListener = $this->prophesize(ListenerInterface::class); + $mockContainer->has(ListenerInterface::class)->willReturn(false); + + $config = [ + 'authentication' => [ + 'private_key' => __DIR__ . '/TestAsset/private.key', + 'encryption_key' => 'iALlwJ1sH77dmFCJFo+pMdM6Af4bF/hCca1EDDx7MwE=', + 'access_token_expire' => 'P1D', + 'grants' => [ + ClientCredentialsGrant::class => ClientCredentialsGrant::class, + ], + 'event_listeners' => [ + [ + RequestEvent::CLIENT_AUTHENTICATION_FAILED, + ListenerInterface::class, + ], + ], + ], + ]; + + $mockContainer->get('config')->willReturn($config); + + $factory = new AuthorizationServerFactory(); + + $this->expectException(InvalidConfigException::class); + + $result = $factory($mockContainer->reveal()); + } + + public function testInvokeWithListenerProviderConfig() + { + $mockContainer = $this->getContainerMock(); + $mockProvider = $this->prophesize(ListenerProviderInterface::class); + $mockContainer->has(ListenerProviderInterface::class)->willReturn(true); + $mockContainer->get(ListenerProviderInterface::class)->willReturn($mockProvider->reveal()); + + $config = [ + 'authentication' => [ + 'private_key' => __DIR__ . '/TestAsset/private.key', + 'encryption_key' => 'iALlwJ1sH77dmFCJFo+pMdM6Af4bF/hCca1EDDx7MwE=', + 'access_token_expire' => 'P1D', + 'grants' => [ + ClientCredentialsGrant::class => ClientCredentialsGrant::class, + ], + 'event_listener_providers' => [ + ListenerProviderInterface::class + ], + ], + ]; + + $mockContainer->get('config')->willReturn($config); + + $factory = new AuthorizationServerFactory(); + + $result = $factory($mockContainer->reveal()); + + $this->assertInstanceOf(AuthorizationServer::class, $result); + } + + public function testInvokeWithListenerProviderConfigMissingServiceThrowsException() + { + $mockContainer = $this->getContainerMock(); + $mockProvider = $this->prophesize(ListenerProviderInterface::class); + $mockContainer->has(ListenerProviderInterface::class)->willReturn(false); + + $config = [ + 'authentication' => [ + 'private_key' => __DIR__ . '/TestAsset/private.key', + 'encryption_key' => 'iALlwJ1sH77dmFCJFo+pMdM6Af4bF/hCca1EDDx7MwE=', + 'access_token_expire' => 'P1D', + 'grants' => [ + ClientCredentialsGrant::class => ClientCredentialsGrant::class, + ], + 'event_listener_providers' => [ + ListenerProviderInterface::class, + ], + ], + ]; + + $mockContainer->get('config')->willReturn($config); + + $factory = new AuthorizationServerFactory(); + + $this->expectException(InvalidConfigException::class); + $factory($mockContainer->reveal()); + } } diff --git a/test/ConfigTraitTest.php b/test/ConfigTraitTest.php index 52c71aa..2808660 100644 --- a/test/ConfigTraitTest.php +++ b/test/ConfigTraitTest.php @@ -152,4 +152,82 @@ public function testGetGrantsConfig() $result = $this->trait->proxy('getGrantsConfig', $this->container->reveal()); $this->assertEquals($this->config['authentication']['grants'], $result); } + + public function testGetListenersConfigNoConfig() + { + $this->container + ->get('config') + ->willReturn([]); + $result = $this->trait + ->proxy('getListenersConfig', $this->container->reveal()); + $this->assertInternalType('array', $result); + } + + /** + * @expectedException Zend\Expressive\Authentication\OAuth2\Exception\InvalidConfigException + */ + public function testGetListenersConfigNoArrayValue() + { + $this->container + ->get('config') + ->willReturn([ + 'authentication' => [ + 'event_listeners' => 'xxx', + ], + ]); + + $this->trait->proxy('getListenersConfig', $this->container->reveal()); + } + + public function testGetListenersConfig() + { + $this->container->get('config') + ->willReturn([ + 'authentication' => [ + 'event_listeners' => $expected = [['xxx']], + ], + ]); + $result = $this->trait + ->proxy('getListenersConfig', $this->container->reveal()); + $this->assertEquals($expected, $result); + } + + public function testGetListenerProvidersConfigNoConfig() + { + $this->container + ->get('config') + ->willReturn([]); + $result = $this->trait + ->proxy('getListenerProvidersConfig', $this->container->reveal()); + $this->assertInternalType('array', $result); + } + + /** + * @expectedException Zend\Expressive\Authentication\OAuth2\Exception\InvalidConfigException + */ + public function testGetListenerProvidersConfigNoArrayValue() + { + $this->container + ->get('config') + ->willReturn([ + 'authentication' => [ + 'event_listener_providers' => 'xxx', + ], + ]); + + $this->trait->proxy('getListenerProvidersConfig', $this->container->reveal()); + } + + public function testGetListenerProvidersConfig() + { + $this->container->get('config') + ->willReturn([ + 'authentication' => [ + 'event_listener_providers' => $expected = ['xxx'], + ], + ]); + $result = $this->trait + ->proxy('getListenerProvidersConfig', $this->container->reveal()); + $this->assertEquals($expected, $result); + } } diff --git a/test/OAuth2AdapterTest.php b/test/OAuth2AdapterTest.php index 611e92d..5bced61 100644 --- a/test/OAuth2AdapterTest.php +++ b/test/OAuth2AdapterTest.php @@ -14,6 +14,7 @@ use League\OAuth2\Server\ResourceServer; use PHPUnit\Framework\TestCase; use Prophecy\Argument; +use Prophecy\Prophecy\ObjectProphecy; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Zend\Expressive\Authentication\AuthenticationInterface; @@ -32,6 +33,9 @@ class OAuth2AdapterTest extends TestCase /** @var callable */ private $responseFactory; + /** @var callable */ + private $userFactory; + protected function setUp() : void { $this->resourceServer = $this->prophesize(ResourceServer::class); @@ -122,7 +126,7 @@ public function testAuthenticateReturnsAUserIfTheResourceServerProducesAUserId() $this->assertSame([], $user->getRoles()); } - public function testAuthenticateReturnsAClientIfTheResourceServerProducesAClientId() + public function testAuthenticateReturnsNullIfTheResourceServerProducesAClientIdOnly() { $request = $this->prophesize(ServerRequestInterface::class); $request->getAttribute('oauth_user_id', null)->willReturn(null); @@ -141,10 +145,7 @@ public function testAuthenticateReturnsAClientIfTheResourceServerProducesAClient ); $user = $adapter->authenticate($request->reveal()); - - $this->assertInstanceOf(UserInterface::class, $user); - $this->assertSame('some-identifier', $user->getIdentity()); - $this->assertSame([], $user->getRoles()); + $this->assertNull($user); } public function testUnauthorizedResponseProducesAResponseWithAWwwAuthenticateHeader() diff --git a/test/Pdo/OAuth2PdoMiddlewareTest.php b/test/Pdo/OAuth2PdoMiddlewareTest.php index 5bfed5a..338766d 100644 --- a/test/Pdo/OAuth2PdoMiddlewareTest.php +++ b/test/Pdo/OAuth2PdoMiddlewareTest.php @@ -1,7 +1,7 @@ 'test', 'state' => $state ]; + + $codeVerifier = new S256Verifier(); + + $params['code_challenge_method'] = $codeVerifier->getMethod(); + $params['code_verifier'] = self::CODE_VERIFIER; + $params['code_challenge'] = strtr( + rtrim(base64_encode(hash('sha256', self::CODE_VERIFIER, true)), '='), + '+/', + '-_' + ); + $request = $this->buildServerRequest( 'GET', '/auth_code?' . http_build_query($params), @@ -324,8 +338,10 @@ public function testProcessFromAuthorizationCode(string $code) 'client_id' => 'client_test2', 'client_secret' => 'test', 'redirect_uri' => '/redirect', - 'code' => $code + 'code' => $code, + 'code_verifier' => self::CODE_VERIFIER, ]; + $request = $this->buildServerRequest( 'POST', '/access_token', diff --git a/test/Repository/Pdo/AccessTokenRepositoryTest.php b/test/Repository/Pdo/AccessTokenRepositoryTest.php index 4bb48cc..85b61b9 100644 --- a/test/Repository/Pdo/AccessTokenRepositoryTest.php +++ b/test/Repository/Pdo/AccessTokenRepositoryTest.php @@ -15,6 +15,7 @@ use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\Traits\AccessTokenTrait; +use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use PDOStatement; use PHPUnit\Framework\TestCase; @@ -133,6 +134,21 @@ public function testIsAccessTokenRevokedReturnsTrueWhenRowRevokedFlagIsTrue() $this->assertTrue($this->repo->isAccessTokenRevoked('token_id')); } + public function testIsAcessTokenRevokedRaisesExceptionWhenTokenIdDontExists() + { + $statement = $this->prophesize(PDOStatement::class); + $statement->bindParam(':tokenId', 'token_id')->shouldBeCalled(); + $statement->execute()->willReturn(true)->shouldBeCalled(); + $statement->fetch()->willReturn(false)->shouldBeCalled(); + + $this->pdo + ->prepare(Argument::containingString('SELECT revoked FROM oauth_access_tokens')) + ->will([$statement, 'reveal']); + + $this->expectException(OAuthServerException::class); + $this->repo->isAccessTokenRevoked('token_id'); + } + public function testRevokeAccessToken() { $statement = $this->prophesize(PDOStatement::class); diff --git a/test/Repository/Pdo/ClientRepositoryTest.php b/test/Repository/Pdo/ClientRepositoryTest.php index df7e137..6ae6636 100644 --- a/test/Repository/Pdo/ClientRepositoryTest.php +++ b/test/Repository/Pdo/ClientRepositoryTest.php @@ -1,7 +1,7 @@ will([$statement, 'reveal']); $this->assertNull( - $this->repo ->getClientEntity( - 'client_id', - 'grant_type' - ) + $this->repo ->getClientEntity('client_id') ); } @@ -59,10 +56,45 @@ public function testGetClientEntityReturnsNullIfNoRowReturned() $client = $this->prophesize(ClientEntityInterface::class); $this->assertNull( - $this->repo ->getClientEntity( - 'client_id', - 'grant_type' - ) + $this->repo ->getClientEntity('client_id') + ); + } + + public function testGetClientEntityReturnsCorrectEntity() + { + $name = 'foo'; + $redirect = 'bar'; + + $statement = $this->prophesize(PDOStatement::class); + $statement->bindParam(':clientIdentifier', 'client_id')->shouldBeCalled(); + $statement->execute()->will(function () use ($statement, $name, $redirect) { + $statement->fetch()->willReturn([ + 'name' => $name, + 'redirect' => $redirect, + ]); + return null; + }); + + $this->pdo + ->prepare(Argument::containingString('SELECT * FROM oauth_clients')) + ->will([$statement, 'reveal']); + + $this->prophesize(ClientEntityInterface::class); + + /** @var ClientEntityInterface $client */ + $client = $this->repo->getClientEntity('client_id'); + + $this->assertInstanceOf( + ClientEntityInterface::class, + $client + ); + $this->assertEquals( + $name, + $client->getName() + ); + $this->assertEquals( + [$redirect], + $client->getRedirectUri() ); } @@ -82,10 +114,34 @@ public function invalidGrants() ]; } + public function testValidateClientReturnsFalseIfNoRowReturned() + { + $statement = $this->prophesize(PDOStatement::class); + $statement->bindParam(':clientIdentifier', 'client_id')->shouldBeCalled(); + $statement->execute()->will(function () use ($statement) { + $statement->fetch()->willReturn([]); + return null; + }); + + $this->pdo + ->prepare(Argument::containingString('SELECT * FROM oauth_clients')) + ->will([$statement, 'reveal']); + + $client = $this->prophesize(ClientEntityInterface::class); + + $this->assertFalse( + $this->repo->validateClient( + 'client_id', + '', + 'password' + ) + ); + } + /** * @dataProvider invalidGrants */ - public function testGetClientEntityReturnsNullIfRowIndicatesNotGranted(string $grantType, array $rowReturned) + public function testValidateClientReturnsFalseIfRowIndicatesNotGranted(string $grantType, array $rowReturned) { $statement = $this->prophesize(PDOStatement::class); $statement->bindParam(':clientIdentifier', 'client_id')->shouldBeCalled(); @@ -100,22 +156,23 @@ public function testGetClientEntityReturnsNullIfRowIndicatesNotGranted(string $g $client = $this->prophesize(ClientEntityInterface::class); - $this->assertNull( - $this->repo ->getClientEntity( + $this->assertFalse( + $this->repo ->validateClient( 'client_id', + '', $grantType ) ); } - public function testGetClientReturnsNullForNonMatchingClientSecret() + public function testValidateClientReturnsFalseForNonMatchingClientSecret() { $statement = $this->prophesize(PDOStatement::class); $statement->bindParam(':clientIdentifier', 'client_id')->shouldBeCalled(); $statement->execute()->will(function () use ($statement) { $statement->fetch()->willReturn([ 'password_client' => true, - 'secret' => 'unknown password', + 'secret' => 'bar', ]); return null; }); @@ -126,17 +183,16 @@ public function testGetClientReturnsNullForNonMatchingClientSecret() $client = $this->prophesize(ClientEntityInterface::class); - $this->assertNull( - $this->repo ->getClientEntity( + $this->assertFalse( + $this->repo ->validateClient( 'client_id', - 'password_client', - 'password', - true + 'foo', + 'password' ) ); } - public function testGetClientReturnsNullForEmptyClientSecret() + public function testValidateClientReturnsFalseForEmptyClientSecret() { $statement = $this->prophesize(PDOStatement::class); $statement->bindParam(':clientIdentifier', 'client_id')->shouldBeCalled(); @@ -154,12 +210,11 @@ public function testGetClientReturnsNullForEmptyClientSecret() $client = $this->prophesize(ClientEntityInterface::class); - $this->assertNull( - $this->repo ->getClientEntity( + $this->assertFalse( + $this->repo ->validateClient( 'client_id', - 'password_client', - 'password', - true + 'foo', + 'password' ) ); }