├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml.dist ├── src ├── AccessToken.php ├── GrantType │ ├── AuthorizationCode.php │ ├── ClientCredentials.php │ ├── GrantTypeBase.php │ ├── GrantTypeInterface.php │ ├── JwtBearer.php │ ├── PasswordCredentials.php │ ├── RefreshToken.php │ └── RefreshTokenGrantTypeInterface.php └── Middleware │ └── OAuthMiddleware.php └── tests ├── AccessTokenTest.php ├── GrantType ├── AuthorizationCodeTest.php ├── JwtBearerTest.php ├── PasswordCredentialsTest.php └── RefreshTokenTest.php ├── Middleware └── OAuthMiddlewareTest.php ├── MockOAuth2Server.php ├── MockOAuthMiddleware.php ├── TestBase.php └── private.key /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /vendor/ 3 | /phpunit.xml 4 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build_failure_conditions: 2 | - 'issues.label("coding-style").new.exists' 3 | - 'issues.severity(>= MAJOR).new.exists' 4 | 5 | tools: 6 | external_code_coverage: false 7 | php_code_sniffer: 8 | config: { standard: 'PSR2' } 9 | 10 | build: 11 | environment: 12 | php: 13 | version: 5.6.16 14 | ini: 15 | display_errors: true 16 | date.timezone: Europe/London 17 | memory_limit: 2G 18 | tests: 19 | override: 20 | - 21 | command: 'vendor/bin/phpunit --coverage-clover=coverage.clover' 22 | coverage: 23 | file: 'coverage.clover' 24 | format: 'php-clover' 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | php: 6 | - 5.6 7 | - 7.0 8 | - 7.1 9 | - hhvm 10 | 11 | cache: 12 | directories: 13 | - $HOME/.composer/cache 14 | 15 | before_install: 16 | - rm composer.lock 17 | 18 | install: 19 | - composer install --prefer-dist --no-interaction 20 | 21 | before_script: 22 | - mkdir -p build/logs 23 | 24 | script: 25 | - ./vendor/bin/phpunit --verbose --coverage-clover build/logs/clover.xml 26 | 27 | after_script: 28 | - wget https://scrutinizer-ci.com/ocular.phar -t 3 29 | - php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Bojan Zivanovic 4 | Copyright (c) 2016 Daniel Macedo, Sainsbury's Supermarkets Ltd. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](http://www.sainsburys.co.uk/homepage/images/sainsburys.png) 2 | 3 | Sainsbury's guzzle-oauth2-plugin 4 | ================================ 5 | 6 | Provides an OAuth2 plugin (middleware) for [Guzzle](http://guzzlephp.org/). 7 | 8 | [![Build Status](https://travis-ci.org/Sainsburys/guzzle-oauth2-plugin.svg?branch=master)](https://travis-ci.org/Sainsburys/guzzle-oauth2-plugin) 9 | [![Code Coverage](https://scrutinizer-ci.com/g/Sainsburys/guzzle-oauth2-plugin/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Sainsburys/guzzle-oauth2-plugin/?branch=master) 10 | 11 | Version 3.x (on the `master` branch) is intended for Guzzle 6: 12 | ```json 13 | "sainsburys/guzzle-oauth2-plugin": "^3.0" 14 | ``` 15 | 16 | Version 2.x (on the `release/2.0` branch) is intended for Guzzle 5: 17 | ```json 18 | "sainsburys/guzzle-oauth2-plugin": "^2.0" 19 | ``` 20 | 21 | Version 1.x (on the `release/1.0` branch) is intended for Guzzle 3 [Unmaintained]: 22 | ```json 23 | "sainsburys/guzzle-oauth2-plugin": "^1.0" 24 | ``` 25 | 26 | ## Features 27 | 28 | - Acquires access tokens via one of the supported grant types (code, client credentials, 29 | user credentials, refresh token). Or you can set an access token yourself. 30 | - Supports refresh tokens (stores them and uses them to get new access tokens). 31 | - Handles token expiration (acquires new tokens and retries failed requests). 32 | 33 | ## Running the tests 34 | 35 | First make sure you have all the dependencies in place by running `composer install --prefer-dist`, then simply run `.vendor/bin/phpunit`. 36 | 37 | ## Example 38 | ```php 39 | use GuzzleHttp\Client; 40 | use GuzzleHttp\HandlerStack; 41 | use Sainsburys\Guzzle\Oauth2\GrantType\RefreshToken; 42 | use Sainsburys\Guzzle\Oauth2\GrantType\PasswordCredentials; 43 | use Sainsburys\Guzzle\Oauth2\Middleware\OAuthMiddleware; 44 | 45 | $baseUri = 'https://example.com'; 46 | $config = [ 47 | PasswordCredentials::CONFIG_USERNAME => 'test@example.com', 48 | PasswordCredentials::CONFIG_PASSWORD => 'test password', 49 | PasswordCredentials::CONFIG_CLIENT_ID => 'test-client', 50 | PasswordCredentials::CONFIG_TOKEN_URL => '/oauth/token', 51 | 'scope' => 'administration', 52 | ]; 53 | 54 | $oauthClient = new Client(['base_uri' => $baseUri]); 55 | $grantType = new PasswordCredentials($oauthClient, $config); 56 | $refreshToken = new RefreshToken($oauthClient, $config); 57 | $middleware = new OAuthMiddleware($oauthClient, $grantType, $refreshToken); 58 | 59 | $handlerStack = HandlerStack::create(); 60 | $handlerStack->push($middleware->onBefore()); 61 | $handlerStack->push($middleware->onFailure(5)); 62 | 63 | $client = new Client(['handler'=> $handlerStack, 'base_uri' => $baseUri, 'auth' => 'oauth2']); 64 | 65 | $response = $client->request('GET', '/api/user/me'); 66 | 67 | // Use $middleware->getAccessToken(); and $middleware->getRefreshToken() to get tokens 68 | // that can be persisted for subsequent requests. 69 | ``` 70 | 71 | ## Example using Symfony DI config 72 | 73 | ```xml 74 | 77 | 78 | 79 | https://example.com 80 | 81 | test@example.com 82 | test password 83 | test-client 84 | /oauth/token 85 | 86 | 87 | 88 | 89 | 90 | 91 | %acme.base_uri% 92 | 93 | 94 | 95 | 96 | 97 | %acme.oauth.config% 98 | 99 | 100 | 101 | 102 | %acme.oauth.config% 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 5 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | %acme.base_uri% 134 | oauth2 135 | 136 | 137 | 138 | 139 | ``` 140 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sainsburys/guzzle-oauth2-plugin", 3 | "description": "An OAuth2 middleware for Guzzle", 4 | "license": "MIT", 5 | "require": { 6 | "php": ">=5.6", 7 | "guzzlehttp/guzzle": "^6.0|^7.0", 8 | "firebase/php-jwt": "^3.0|^4.0|^5.0|^6.0" 9 | }, 10 | "autoload": { 11 | "psr-4": { 12 | "Sainsburys\\Guzzle\\Oauth2\\": "src" 13 | } 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": "^5.7" 17 | }, 18 | "autoload-dev": { 19 | "psr-4": { 20 | "Sainsburys\\Guzzle\\Oauth2\\Tests\\": "tests" 21 | } 22 | }, 23 | "authors": [ 24 | { 25 | "name": "Bojan Zivanovic" 26 | }, 27 | { 28 | "name": "Damien Tournoud" 29 | }, 30 | { 31 | "name": "Patrick Dawkins" 32 | }, 33 | { 34 | "name": "Daniel Macedo" 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "1933544611d0ff2be9bc7047d8c1285d", 8 | "packages": [ 9 | { 10 | "name": "firebase/php-jwt", 11 | "version": "v6.3.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/firebase/php-jwt.git", 15 | "reference": "018dfc4e1da92ad8a1b90adc4893f476a3b41cb8" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/firebase/php-jwt/zipball/018dfc4e1da92ad8a1b90adc4893f476a3b41cb8", 20 | "reference": "018dfc4e1da92ad8a1b90adc4893f476a3b41cb8", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^7.1||^8.0" 25 | }, 26 | "require-dev": { 27 | "guzzlehttp/guzzle": "^6.5||^7.4", 28 | "phpspec/prophecy-phpunit": "^1.1", 29 | "phpunit/phpunit": "^7.5||^9.5", 30 | "psr/cache": "^1.0||^2.0", 31 | "psr/http-client": "^1.0", 32 | "psr/http-factory": "^1.0" 33 | }, 34 | "suggest": { 35 | "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" 36 | }, 37 | "type": "library", 38 | "autoload": { 39 | "psr-4": { 40 | "Firebase\\JWT\\": "src" 41 | } 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "BSD-3-Clause" 46 | ], 47 | "authors": [ 48 | { 49 | "name": "Neuman Vong", 50 | "email": "neuman+pear@twilio.com", 51 | "role": "Developer" 52 | }, 53 | { 54 | "name": "Anant Narayanan", 55 | "email": "anant@php.net", 56 | "role": "Developer" 57 | } 58 | ], 59 | "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", 60 | "homepage": "https://github.com/firebase/php-jwt", 61 | "keywords": [ 62 | "jwt", 63 | "php" 64 | ], 65 | "support": { 66 | "issues": "https://github.com/firebase/php-jwt/issues", 67 | "source": "https://github.com/firebase/php-jwt/tree/v6.3.0" 68 | }, 69 | "time": "2022-07-15T16:48:45+00:00" 70 | }, 71 | { 72 | "name": "guzzlehttp/guzzle", 73 | "version": "7.5.0", 74 | "source": { 75 | "type": "git", 76 | "url": "https://github.com/guzzle/guzzle.git", 77 | "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" 78 | }, 79 | "dist": { 80 | "type": "zip", 81 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", 82 | "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", 83 | "shasum": "" 84 | }, 85 | "require": { 86 | "ext-json": "*", 87 | "guzzlehttp/promises": "^1.5", 88 | "guzzlehttp/psr7": "^1.9 || ^2.4", 89 | "php": "^7.2.5 || ^8.0", 90 | "psr/http-client": "^1.0", 91 | "symfony/deprecation-contracts": "^2.2 || ^3.0" 92 | }, 93 | "provide": { 94 | "psr/http-client-implementation": "1.0" 95 | }, 96 | "require-dev": { 97 | "bamarni/composer-bin-plugin": "^1.8.1", 98 | "ext-curl": "*", 99 | "php-http/client-integration-tests": "^3.0", 100 | "phpunit/phpunit": "^8.5.29 || ^9.5.23", 101 | "psr/log": "^1.1 || ^2.0 || ^3.0" 102 | }, 103 | "suggest": { 104 | "ext-curl": "Required for CURL handler support", 105 | "ext-intl": "Required for Internationalized Domain Name (IDN) support", 106 | "psr/log": "Required for using the Log middleware" 107 | }, 108 | "type": "library", 109 | "extra": { 110 | "bamarni-bin": { 111 | "bin-links": true, 112 | "forward-command": false 113 | }, 114 | "branch-alias": { 115 | "dev-master": "7.5-dev" 116 | } 117 | }, 118 | "autoload": { 119 | "files": [ 120 | "src/functions_include.php" 121 | ], 122 | "psr-4": { 123 | "GuzzleHttp\\": "src/" 124 | } 125 | }, 126 | "notification-url": "https://packagist.org/downloads/", 127 | "license": [ 128 | "MIT" 129 | ], 130 | "authors": [ 131 | { 132 | "name": "Graham Campbell", 133 | "email": "hello@gjcampbell.co.uk", 134 | "homepage": "https://github.com/GrahamCampbell" 135 | }, 136 | { 137 | "name": "Michael Dowling", 138 | "email": "mtdowling@gmail.com", 139 | "homepage": "https://github.com/mtdowling" 140 | }, 141 | { 142 | "name": "Jeremy Lindblom", 143 | "email": "jeremeamia@gmail.com", 144 | "homepage": "https://github.com/jeremeamia" 145 | }, 146 | { 147 | "name": "George Mponos", 148 | "email": "gmponos@gmail.com", 149 | "homepage": "https://github.com/gmponos" 150 | }, 151 | { 152 | "name": "Tobias Nyholm", 153 | "email": "tobias.nyholm@gmail.com", 154 | "homepage": "https://github.com/Nyholm" 155 | }, 156 | { 157 | "name": "Márk Sági-Kazár", 158 | "email": "mark.sagikazar@gmail.com", 159 | "homepage": "https://github.com/sagikazarmark" 160 | }, 161 | { 162 | "name": "Tobias Schultze", 163 | "email": "webmaster@tubo-world.de", 164 | "homepage": "https://github.com/Tobion" 165 | } 166 | ], 167 | "description": "Guzzle is a PHP HTTP client library", 168 | "keywords": [ 169 | "client", 170 | "curl", 171 | "framework", 172 | "http", 173 | "http client", 174 | "psr-18", 175 | "psr-7", 176 | "rest", 177 | "web service" 178 | ], 179 | "support": { 180 | "issues": "https://github.com/guzzle/guzzle/issues", 181 | "source": "https://github.com/guzzle/guzzle/tree/7.5.0" 182 | }, 183 | "funding": [ 184 | { 185 | "url": "https://github.com/GrahamCampbell", 186 | "type": "github" 187 | }, 188 | { 189 | "url": "https://github.com/Nyholm", 190 | "type": "github" 191 | }, 192 | { 193 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", 194 | "type": "tidelift" 195 | } 196 | ], 197 | "time": "2022-08-28T15:39:27+00:00" 198 | }, 199 | { 200 | "name": "guzzlehttp/promises", 201 | "version": "1.5.2", 202 | "source": { 203 | "type": "git", 204 | "url": "https://github.com/guzzle/promises.git", 205 | "reference": "b94b2807d85443f9719887892882d0329d1e2598" 206 | }, 207 | "dist": { 208 | "type": "zip", 209 | "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", 210 | "reference": "b94b2807d85443f9719887892882d0329d1e2598", 211 | "shasum": "" 212 | }, 213 | "require": { 214 | "php": ">=5.5" 215 | }, 216 | "require-dev": { 217 | "symfony/phpunit-bridge": "^4.4 || ^5.1" 218 | }, 219 | "type": "library", 220 | "extra": { 221 | "branch-alias": { 222 | "dev-master": "1.5-dev" 223 | } 224 | }, 225 | "autoload": { 226 | "files": [ 227 | "src/functions_include.php" 228 | ], 229 | "psr-4": { 230 | "GuzzleHttp\\Promise\\": "src/" 231 | } 232 | }, 233 | "notification-url": "https://packagist.org/downloads/", 234 | "license": [ 235 | "MIT" 236 | ], 237 | "authors": [ 238 | { 239 | "name": "Graham Campbell", 240 | "email": "hello@gjcampbell.co.uk", 241 | "homepage": "https://github.com/GrahamCampbell" 242 | }, 243 | { 244 | "name": "Michael Dowling", 245 | "email": "mtdowling@gmail.com", 246 | "homepage": "https://github.com/mtdowling" 247 | }, 248 | { 249 | "name": "Tobias Nyholm", 250 | "email": "tobias.nyholm@gmail.com", 251 | "homepage": "https://github.com/Nyholm" 252 | }, 253 | { 254 | "name": "Tobias Schultze", 255 | "email": "webmaster@tubo-world.de", 256 | "homepage": "https://github.com/Tobion" 257 | } 258 | ], 259 | "description": "Guzzle promises library", 260 | "keywords": [ 261 | "promise" 262 | ], 263 | "support": { 264 | "issues": "https://github.com/guzzle/promises/issues", 265 | "source": "https://github.com/guzzle/promises/tree/1.5.2" 266 | }, 267 | "funding": [ 268 | { 269 | "url": "https://github.com/GrahamCampbell", 270 | "type": "github" 271 | }, 272 | { 273 | "url": "https://github.com/Nyholm", 274 | "type": "github" 275 | }, 276 | { 277 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", 278 | "type": "tidelift" 279 | } 280 | ], 281 | "time": "2022-08-28T14:55:35+00:00" 282 | }, 283 | { 284 | "name": "guzzlehttp/psr7", 285 | "version": "2.4.1", 286 | "source": { 287 | "type": "git", 288 | "url": "https://github.com/guzzle/psr7.git", 289 | "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379" 290 | }, 291 | "dist": { 292 | "type": "zip", 293 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379", 294 | "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379", 295 | "shasum": "" 296 | }, 297 | "require": { 298 | "php": "^7.2.5 || ^8.0", 299 | "psr/http-factory": "^1.0", 300 | "psr/http-message": "^1.0", 301 | "ralouphie/getallheaders": "^3.0" 302 | }, 303 | "provide": { 304 | "psr/http-factory-implementation": "1.0", 305 | "psr/http-message-implementation": "1.0" 306 | }, 307 | "require-dev": { 308 | "bamarni/composer-bin-plugin": "^1.8.1", 309 | "http-interop/http-factory-tests": "^0.9", 310 | "phpunit/phpunit": "^8.5.29 || ^9.5.23" 311 | }, 312 | "suggest": { 313 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" 314 | }, 315 | "type": "library", 316 | "extra": { 317 | "bamarni-bin": { 318 | "bin-links": true, 319 | "forward-command": false 320 | }, 321 | "branch-alias": { 322 | "dev-master": "2.4-dev" 323 | } 324 | }, 325 | "autoload": { 326 | "psr-4": { 327 | "GuzzleHttp\\Psr7\\": "src/" 328 | } 329 | }, 330 | "notification-url": "https://packagist.org/downloads/", 331 | "license": [ 332 | "MIT" 333 | ], 334 | "authors": [ 335 | { 336 | "name": "Graham Campbell", 337 | "email": "hello@gjcampbell.co.uk", 338 | "homepage": "https://github.com/GrahamCampbell" 339 | }, 340 | { 341 | "name": "Michael Dowling", 342 | "email": "mtdowling@gmail.com", 343 | "homepage": "https://github.com/mtdowling" 344 | }, 345 | { 346 | "name": "George Mponos", 347 | "email": "gmponos@gmail.com", 348 | "homepage": "https://github.com/gmponos" 349 | }, 350 | { 351 | "name": "Tobias Nyholm", 352 | "email": "tobias.nyholm@gmail.com", 353 | "homepage": "https://github.com/Nyholm" 354 | }, 355 | { 356 | "name": "Márk Sági-Kazár", 357 | "email": "mark.sagikazar@gmail.com", 358 | "homepage": "https://github.com/sagikazarmark" 359 | }, 360 | { 361 | "name": "Tobias Schultze", 362 | "email": "webmaster@tubo-world.de", 363 | "homepage": "https://github.com/Tobion" 364 | }, 365 | { 366 | "name": "Márk Sági-Kazár", 367 | "email": "mark.sagikazar@gmail.com", 368 | "homepage": "https://sagikazarmark.hu" 369 | } 370 | ], 371 | "description": "PSR-7 message implementation that also provides common utility methods", 372 | "keywords": [ 373 | "http", 374 | "message", 375 | "psr-7", 376 | "request", 377 | "response", 378 | "stream", 379 | "uri", 380 | "url" 381 | ], 382 | "support": { 383 | "issues": "https://github.com/guzzle/psr7/issues", 384 | "source": "https://github.com/guzzle/psr7/tree/2.4.1" 385 | }, 386 | "funding": [ 387 | { 388 | "url": "https://github.com/GrahamCampbell", 389 | "type": "github" 390 | }, 391 | { 392 | "url": "https://github.com/Nyholm", 393 | "type": "github" 394 | }, 395 | { 396 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", 397 | "type": "tidelift" 398 | } 399 | ], 400 | "time": "2022-08-28T14:45:39+00:00" 401 | }, 402 | { 403 | "name": "psr/http-client", 404 | "version": "1.0.1", 405 | "source": { 406 | "type": "git", 407 | "url": "https://github.com/php-fig/http-client.git", 408 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" 409 | }, 410 | "dist": { 411 | "type": "zip", 412 | "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", 413 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", 414 | "shasum": "" 415 | }, 416 | "require": { 417 | "php": "^7.0 || ^8.0", 418 | "psr/http-message": "^1.0" 419 | }, 420 | "type": "library", 421 | "extra": { 422 | "branch-alias": { 423 | "dev-master": "1.0.x-dev" 424 | } 425 | }, 426 | "autoload": { 427 | "psr-4": { 428 | "Psr\\Http\\Client\\": "src/" 429 | } 430 | }, 431 | "notification-url": "https://packagist.org/downloads/", 432 | "license": [ 433 | "MIT" 434 | ], 435 | "authors": [ 436 | { 437 | "name": "PHP-FIG", 438 | "homepage": "http://www.php-fig.org/" 439 | } 440 | ], 441 | "description": "Common interface for HTTP clients", 442 | "homepage": "https://github.com/php-fig/http-client", 443 | "keywords": [ 444 | "http", 445 | "http-client", 446 | "psr", 447 | "psr-18" 448 | ], 449 | "support": { 450 | "source": "https://github.com/php-fig/http-client/tree/master" 451 | }, 452 | "time": "2020-06-29T06:28:15+00:00" 453 | }, 454 | { 455 | "name": "psr/http-factory", 456 | "version": "1.0.1", 457 | "source": { 458 | "type": "git", 459 | "url": "https://github.com/php-fig/http-factory.git", 460 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" 461 | }, 462 | "dist": { 463 | "type": "zip", 464 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", 465 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", 466 | "shasum": "" 467 | }, 468 | "require": { 469 | "php": ">=7.0.0", 470 | "psr/http-message": "^1.0" 471 | }, 472 | "type": "library", 473 | "extra": { 474 | "branch-alias": { 475 | "dev-master": "1.0.x-dev" 476 | } 477 | }, 478 | "autoload": { 479 | "psr-4": { 480 | "Psr\\Http\\Message\\": "src/" 481 | } 482 | }, 483 | "notification-url": "https://packagist.org/downloads/", 484 | "license": [ 485 | "MIT" 486 | ], 487 | "authors": [ 488 | { 489 | "name": "PHP-FIG", 490 | "homepage": "http://www.php-fig.org/" 491 | } 492 | ], 493 | "description": "Common interfaces for PSR-7 HTTP message factories", 494 | "keywords": [ 495 | "factory", 496 | "http", 497 | "message", 498 | "psr", 499 | "psr-17", 500 | "psr-7", 501 | "request", 502 | "response" 503 | ], 504 | "support": { 505 | "source": "https://github.com/php-fig/http-factory/tree/master" 506 | }, 507 | "time": "2019-04-30T12:38:16+00:00" 508 | }, 509 | { 510 | "name": "psr/http-message", 511 | "version": "1.0.1", 512 | "source": { 513 | "type": "git", 514 | "url": "https://github.com/php-fig/http-message.git", 515 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 516 | }, 517 | "dist": { 518 | "type": "zip", 519 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 520 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 521 | "shasum": "" 522 | }, 523 | "require": { 524 | "php": ">=5.3.0" 525 | }, 526 | "type": "library", 527 | "extra": { 528 | "branch-alias": { 529 | "dev-master": "1.0.x-dev" 530 | } 531 | }, 532 | "autoload": { 533 | "psr-4": { 534 | "Psr\\Http\\Message\\": "src/" 535 | } 536 | }, 537 | "notification-url": "https://packagist.org/downloads/", 538 | "license": [ 539 | "MIT" 540 | ], 541 | "authors": [ 542 | { 543 | "name": "PHP-FIG", 544 | "homepage": "http://www.php-fig.org/" 545 | } 546 | ], 547 | "description": "Common interface for HTTP messages", 548 | "homepage": "https://github.com/php-fig/http-message", 549 | "keywords": [ 550 | "http", 551 | "http-message", 552 | "psr", 553 | "psr-7", 554 | "request", 555 | "response" 556 | ], 557 | "support": { 558 | "source": "https://github.com/php-fig/http-message/tree/master" 559 | }, 560 | "time": "2016-08-06T14:39:51+00:00" 561 | }, 562 | { 563 | "name": "ralouphie/getallheaders", 564 | "version": "3.0.3", 565 | "source": { 566 | "type": "git", 567 | "url": "https://github.com/ralouphie/getallheaders.git", 568 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 569 | }, 570 | "dist": { 571 | "type": "zip", 572 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 573 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 574 | "shasum": "" 575 | }, 576 | "require": { 577 | "php": ">=5.6" 578 | }, 579 | "require-dev": { 580 | "php-coveralls/php-coveralls": "^2.1", 581 | "phpunit/phpunit": "^5 || ^6.5" 582 | }, 583 | "type": "library", 584 | "autoload": { 585 | "files": [ 586 | "src/getallheaders.php" 587 | ] 588 | }, 589 | "notification-url": "https://packagist.org/downloads/", 590 | "license": [ 591 | "MIT" 592 | ], 593 | "authors": [ 594 | { 595 | "name": "Ralph Khattar", 596 | "email": "ralph.khattar@gmail.com" 597 | } 598 | ], 599 | "description": "A polyfill for getallheaders.", 600 | "support": { 601 | "issues": "https://github.com/ralouphie/getallheaders/issues", 602 | "source": "https://github.com/ralouphie/getallheaders/tree/develop" 603 | }, 604 | "time": "2019-03-08T08:55:37+00:00" 605 | }, 606 | { 607 | "name": "symfony/deprecation-contracts", 608 | "version": "v2.5.2", 609 | "source": { 610 | "type": "git", 611 | "url": "https://github.com/symfony/deprecation-contracts.git", 612 | "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" 613 | }, 614 | "dist": { 615 | "type": "zip", 616 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", 617 | "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", 618 | "shasum": "" 619 | }, 620 | "require": { 621 | "php": ">=7.1" 622 | }, 623 | "type": "library", 624 | "extra": { 625 | "branch-alias": { 626 | "dev-main": "2.5-dev" 627 | }, 628 | "thanks": { 629 | "name": "symfony/contracts", 630 | "url": "https://github.com/symfony/contracts" 631 | } 632 | }, 633 | "autoload": { 634 | "files": [ 635 | "function.php" 636 | ] 637 | }, 638 | "notification-url": "https://packagist.org/downloads/", 639 | "license": [ 640 | "MIT" 641 | ], 642 | "authors": [ 643 | { 644 | "name": "Nicolas Grekas", 645 | "email": "p@tchwork.com" 646 | }, 647 | { 648 | "name": "Symfony Community", 649 | "homepage": "https://symfony.com/contributors" 650 | } 651 | ], 652 | "description": "A generic function and convention to trigger deprecation notices", 653 | "homepage": "https://symfony.com", 654 | "support": { 655 | "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" 656 | }, 657 | "funding": [ 658 | { 659 | "url": "https://symfony.com/sponsor", 660 | "type": "custom" 661 | }, 662 | { 663 | "url": "https://github.com/fabpot", 664 | "type": "github" 665 | }, 666 | { 667 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 668 | "type": "tidelift" 669 | } 670 | ], 671 | "time": "2022-01-02T09:53:40+00:00" 672 | } 673 | ], 674 | "packages-dev": [ 675 | { 676 | "name": "doctrine/instantiator", 677 | "version": "1.4.1", 678 | "source": { 679 | "type": "git", 680 | "url": "https://github.com/doctrine/instantiator.git", 681 | "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" 682 | }, 683 | "dist": { 684 | "type": "zip", 685 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", 686 | "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", 687 | "shasum": "" 688 | }, 689 | "require": { 690 | "php": "^7.1 || ^8.0" 691 | }, 692 | "require-dev": { 693 | "doctrine/coding-standard": "^9", 694 | "ext-pdo": "*", 695 | "ext-phar": "*", 696 | "phpbench/phpbench": "^0.16 || ^1", 697 | "phpstan/phpstan": "^1.4", 698 | "phpstan/phpstan-phpunit": "^1", 699 | "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", 700 | "vimeo/psalm": "^4.22" 701 | }, 702 | "type": "library", 703 | "autoload": { 704 | "psr-4": { 705 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 706 | } 707 | }, 708 | "notification-url": "https://packagist.org/downloads/", 709 | "license": [ 710 | "MIT" 711 | ], 712 | "authors": [ 713 | { 714 | "name": "Marco Pivetta", 715 | "email": "ocramius@gmail.com", 716 | "homepage": "https://ocramius.github.io/" 717 | } 718 | ], 719 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 720 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 721 | "keywords": [ 722 | "constructor", 723 | "instantiate" 724 | ], 725 | "support": { 726 | "issues": "https://github.com/doctrine/instantiator/issues", 727 | "source": "https://github.com/doctrine/instantiator/tree/1.4.1" 728 | }, 729 | "funding": [ 730 | { 731 | "url": "https://www.doctrine-project.org/sponsorship.html", 732 | "type": "custom" 733 | }, 734 | { 735 | "url": "https://www.patreon.com/phpdoctrine", 736 | "type": "patreon" 737 | }, 738 | { 739 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 740 | "type": "tidelift" 741 | } 742 | ], 743 | "time": "2022-03-03T08:28:38+00:00" 744 | }, 745 | { 746 | "name": "myclabs/deep-copy", 747 | "version": "1.11.0", 748 | "source": { 749 | "type": "git", 750 | "url": "https://github.com/myclabs/DeepCopy.git", 751 | "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" 752 | }, 753 | "dist": { 754 | "type": "zip", 755 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", 756 | "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", 757 | "shasum": "" 758 | }, 759 | "require": { 760 | "php": "^7.1 || ^8.0" 761 | }, 762 | "conflict": { 763 | "doctrine/collections": "<1.6.8", 764 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 765 | }, 766 | "require-dev": { 767 | "doctrine/collections": "^1.6.8", 768 | "doctrine/common": "^2.13.3 || ^3.2.2", 769 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 770 | }, 771 | "type": "library", 772 | "autoload": { 773 | "files": [ 774 | "src/DeepCopy/deep_copy.php" 775 | ], 776 | "psr-4": { 777 | "DeepCopy\\": "src/DeepCopy/" 778 | } 779 | }, 780 | "notification-url": "https://packagist.org/downloads/", 781 | "license": [ 782 | "MIT" 783 | ], 784 | "description": "Create deep copies (clones) of your objects", 785 | "keywords": [ 786 | "clone", 787 | "copy", 788 | "duplicate", 789 | "object", 790 | "object graph" 791 | ], 792 | "support": { 793 | "issues": "https://github.com/myclabs/DeepCopy/issues", 794 | "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" 795 | }, 796 | "funding": [ 797 | { 798 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 799 | "type": "tidelift" 800 | } 801 | ], 802 | "time": "2022-03-03T13:19:32+00:00" 803 | }, 804 | { 805 | "name": "phpdocumentor/reflection-common", 806 | "version": "2.2.0", 807 | "source": { 808 | "type": "git", 809 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 810 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" 811 | }, 812 | "dist": { 813 | "type": "zip", 814 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", 815 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", 816 | "shasum": "" 817 | }, 818 | "require": { 819 | "php": "^7.2 || ^8.0" 820 | }, 821 | "type": "library", 822 | "extra": { 823 | "branch-alias": { 824 | "dev-2.x": "2.x-dev" 825 | } 826 | }, 827 | "autoload": { 828 | "psr-4": { 829 | "phpDocumentor\\Reflection\\": "src/" 830 | } 831 | }, 832 | "notification-url": "https://packagist.org/downloads/", 833 | "license": [ 834 | "MIT" 835 | ], 836 | "authors": [ 837 | { 838 | "name": "Jaap van Otterdijk", 839 | "email": "opensource@ijaap.nl" 840 | } 841 | ], 842 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 843 | "homepage": "http://www.phpdoc.org", 844 | "keywords": [ 845 | "FQSEN", 846 | "phpDocumentor", 847 | "phpdoc", 848 | "reflection", 849 | "static analysis" 850 | ], 851 | "support": { 852 | "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", 853 | "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" 854 | }, 855 | "time": "2020-06-27T09:03:43+00:00" 856 | }, 857 | { 858 | "name": "phpdocumentor/reflection-docblock", 859 | "version": "5.3.0", 860 | "source": { 861 | "type": "git", 862 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 863 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" 864 | }, 865 | "dist": { 866 | "type": "zip", 867 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", 868 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", 869 | "shasum": "" 870 | }, 871 | "require": { 872 | "ext-filter": "*", 873 | "php": "^7.2 || ^8.0", 874 | "phpdocumentor/reflection-common": "^2.2", 875 | "phpdocumentor/type-resolver": "^1.3", 876 | "webmozart/assert": "^1.9.1" 877 | }, 878 | "require-dev": { 879 | "mockery/mockery": "~1.3.2", 880 | "psalm/phar": "^4.8" 881 | }, 882 | "type": "library", 883 | "extra": { 884 | "branch-alias": { 885 | "dev-master": "5.x-dev" 886 | } 887 | }, 888 | "autoload": { 889 | "psr-4": { 890 | "phpDocumentor\\Reflection\\": "src" 891 | } 892 | }, 893 | "notification-url": "https://packagist.org/downloads/", 894 | "license": [ 895 | "MIT" 896 | ], 897 | "authors": [ 898 | { 899 | "name": "Mike van Riel", 900 | "email": "me@mikevanriel.com" 901 | }, 902 | { 903 | "name": "Jaap van Otterdijk", 904 | "email": "account@ijaap.nl" 905 | } 906 | ], 907 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 908 | "support": { 909 | "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", 910 | "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" 911 | }, 912 | "time": "2021-10-19T17:43:47+00:00" 913 | }, 914 | { 915 | "name": "phpdocumentor/type-resolver", 916 | "version": "1.6.1", 917 | "source": { 918 | "type": "git", 919 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 920 | "reference": "77a32518733312af16a44300404e945338981de3" 921 | }, 922 | "dist": { 923 | "type": "zip", 924 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", 925 | "reference": "77a32518733312af16a44300404e945338981de3", 926 | "shasum": "" 927 | }, 928 | "require": { 929 | "php": "^7.2 || ^8.0", 930 | "phpdocumentor/reflection-common": "^2.0" 931 | }, 932 | "require-dev": { 933 | "ext-tokenizer": "*", 934 | "psalm/phar": "^4.8" 935 | }, 936 | "type": "library", 937 | "extra": { 938 | "branch-alias": { 939 | "dev-1.x": "1.x-dev" 940 | } 941 | }, 942 | "autoload": { 943 | "psr-4": { 944 | "phpDocumentor\\Reflection\\": "src" 945 | } 946 | }, 947 | "notification-url": "https://packagist.org/downloads/", 948 | "license": [ 949 | "MIT" 950 | ], 951 | "authors": [ 952 | { 953 | "name": "Mike van Riel", 954 | "email": "me@mikevanriel.com" 955 | } 956 | ], 957 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 958 | "support": { 959 | "issues": "https://github.com/phpDocumentor/TypeResolver/issues", 960 | "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" 961 | }, 962 | "time": "2022-03-15T21:29:03+00:00" 963 | }, 964 | { 965 | "name": "phpspec/prophecy", 966 | "version": "v1.10.3", 967 | "source": { 968 | "type": "git", 969 | "url": "https://github.com/phpspec/prophecy.git", 970 | "reference": "451c3cd1418cf640de218914901e51b064abb093" 971 | }, 972 | "dist": { 973 | "type": "zip", 974 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", 975 | "reference": "451c3cd1418cf640de218914901e51b064abb093", 976 | "shasum": "" 977 | }, 978 | "require": { 979 | "doctrine/instantiator": "^1.0.2", 980 | "php": "^5.3|^7.0", 981 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", 982 | "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", 983 | "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" 984 | }, 985 | "require-dev": { 986 | "phpspec/phpspec": "^2.5 || ^3.2", 987 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" 988 | }, 989 | "type": "library", 990 | "extra": { 991 | "branch-alias": { 992 | "dev-master": "1.10.x-dev" 993 | } 994 | }, 995 | "autoload": { 996 | "psr-4": { 997 | "Prophecy\\": "src/Prophecy" 998 | } 999 | }, 1000 | "notification-url": "https://packagist.org/downloads/", 1001 | "license": [ 1002 | "MIT" 1003 | ], 1004 | "authors": [ 1005 | { 1006 | "name": "Konstantin Kudryashov", 1007 | "email": "ever.zet@gmail.com", 1008 | "homepage": "http://everzet.com" 1009 | }, 1010 | { 1011 | "name": "Marcello Duarte", 1012 | "email": "marcello.duarte@gmail.com" 1013 | } 1014 | ], 1015 | "description": "Highly opinionated mocking framework for PHP 5.3+", 1016 | "homepage": "https://github.com/phpspec/prophecy", 1017 | "keywords": [ 1018 | "Double", 1019 | "Dummy", 1020 | "fake", 1021 | "mock", 1022 | "spy", 1023 | "stub" 1024 | ], 1025 | "support": { 1026 | "issues": "https://github.com/phpspec/prophecy/issues", 1027 | "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" 1028 | }, 1029 | "time": "2020-03-05T15:02:03+00:00" 1030 | }, 1031 | { 1032 | "name": "phpunit/php-code-coverage", 1033 | "version": "4.0.8", 1034 | "source": { 1035 | "type": "git", 1036 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 1037 | "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" 1038 | }, 1039 | "dist": { 1040 | "type": "zip", 1041 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", 1042 | "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", 1043 | "shasum": "" 1044 | }, 1045 | "require": { 1046 | "ext-dom": "*", 1047 | "ext-xmlwriter": "*", 1048 | "php": "^5.6 || ^7.0", 1049 | "phpunit/php-file-iterator": "^1.3", 1050 | "phpunit/php-text-template": "^1.2", 1051 | "phpunit/php-token-stream": "^1.4.2 || ^2.0", 1052 | "sebastian/code-unit-reverse-lookup": "^1.0", 1053 | "sebastian/environment": "^1.3.2 || ^2.0", 1054 | "sebastian/version": "^1.0 || ^2.0" 1055 | }, 1056 | "require-dev": { 1057 | "ext-xdebug": "^2.1.4", 1058 | "phpunit/phpunit": "^5.7" 1059 | }, 1060 | "suggest": { 1061 | "ext-xdebug": "^2.5.1" 1062 | }, 1063 | "type": "library", 1064 | "extra": { 1065 | "branch-alias": { 1066 | "dev-master": "4.0.x-dev" 1067 | } 1068 | }, 1069 | "autoload": { 1070 | "classmap": [ 1071 | "src/" 1072 | ] 1073 | }, 1074 | "notification-url": "https://packagist.org/downloads/", 1075 | "license": [ 1076 | "BSD-3-Clause" 1077 | ], 1078 | "authors": [ 1079 | { 1080 | "name": "Sebastian Bergmann", 1081 | "email": "sb@sebastian-bergmann.de", 1082 | "role": "lead" 1083 | } 1084 | ], 1085 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 1086 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 1087 | "keywords": [ 1088 | "coverage", 1089 | "testing", 1090 | "xunit" 1091 | ], 1092 | "support": { 1093 | "irc": "irc://irc.freenode.net/phpunit", 1094 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 1095 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/4.0" 1096 | }, 1097 | "time": "2017-04-02T07:44:40+00:00" 1098 | }, 1099 | { 1100 | "name": "phpunit/php-file-iterator", 1101 | "version": "1.4.5", 1102 | "source": { 1103 | "type": "git", 1104 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 1105 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" 1106 | }, 1107 | "dist": { 1108 | "type": "zip", 1109 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", 1110 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", 1111 | "shasum": "" 1112 | }, 1113 | "require": { 1114 | "php": ">=5.3.3" 1115 | }, 1116 | "type": "library", 1117 | "extra": { 1118 | "branch-alias": { 1119 | "dev-master": "1.4.x-dev" 1120 | } 1121 | }, 1122 | "autoload": { 1123 | "classmap": [ 1124 | "src/" 1125 | ] 1126 | }, 1127 | "notification-url": "https://packagist.org/downloads/", 1128 | "license": [ 1129 | "BSD-3-Clause" 1130 | ], 1131 | "authors": [ 1132 | { 1133 | "name": "Sebastian Bergmann", 1134 | "email": "sb@sebastian-bergmann.de", 1135 | "role": "lead" 1136 | } 1137 | ], 1138 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 1139 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 1140 | "keywords": [ 1141 | "filesystem", 1142 | "iterator" 1143 | ], 1144 | "support": { 1145 | "irc": "irc://irc.freenode.net/phpunit", 1146 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 1147 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5" 1148 | }, 1149 | "time": "2017-11-27T13:52:08+00:00" 1150 | }, 1151 | { 1152 | "name": "phpunit/php-text-template", 1153 | "version": "1.2.1", 1154 | "source": { 1155 | "type": "git", 1156 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 1157 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 1158 | }, 1159 | "dist": { 1160 | "type": "zip", 1161 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 1162 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 1163 | "shasum": "" 1164 | }, 1165 | "require": { 1166 | "php": ">=5.3.3" 1167 | }, 1168 | "type": "library", 1169 | "autoload": { 1170 | "classmap": [ 1171 | "src/" 1172 | ] 1173 | }, 1174 | "notification-url": "https://packagist.org/downloads/", 1175 | "license": [ 1176 | "BSD-3-Clause" 1177 | ], 1178 | "authors": [ 1179 | { 1180 | "name": "Sebastian Bergmann", 1181 | "email": "sebastian@phpunit.de", 1182 | "role": "lead" 1183 | } 1184 | ], 1185 | "description": "Simple template engine.", 1186 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 1187 | "keywords": [ 1188 | "template" 1189 | ], 1190 | "support": { 1191 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 1192 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" 1193 | }, 1194 | "time": "2015-06-21T13:50:34+00:00" 1195 | }, 1196 | { 1197 | "name": "phpunit/php-timer", 1198 | "version": "1.0.9", 1199 | "source": { 1200 | "type": "git", 1201 | "url": "https://github.com/sebastianbergmann/php-timer.git", 1202 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" 1203 | }, 1204 | "dist": { 1205 | "type": "zip", 1206 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 1207 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 1208 | "shasum": "" 1209 | }, 1210 | "require": { 1211 | "php": "^5.3.3 || ^7.0" 1212 | }, 1213 | "require-dev": { 1214 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 1215 | }, 1216 | "type": "library", 1217 | "extra": { 1218 | "branch-alias": { 1219 | "dev-master": "1.0-dev" 1220 | } 1221 | }, 1222 | "autoload": { 1223 | "classmap": [ 1224 | "src/" 1225 | ] 1226 | }, 1227 | "notification-url": "https://packagist.org/downloads/", 1228 | "license": [ 1229 | "BSD-3-Clause" 1230 | ], 1231 | "authors": [ 1232 | { 1233 | "name": "Sebastian Bergmann", 1234 | "email": "sb@sebastian-bergmann.de", 1235 | "role": "lead" 1236 | } 1237 | ], 1238 | "description": "Utility class for timing", 1239 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 1240 | "keywords": [ 1241 | "timer" 1242 | ], 1243 | "support": { 1244 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 1245 | "source": "https://github.com/sebastianbergmann/php-timer/tree/master" 1246 | }, 1247 | "time": "2017-02-26T11:10:40+00:00" 1248 | }, 1249 | { 1250 | "name": "phpunit/php-token-stream", 1251 | "version": "2.0.2", 1252 | "source": { 1253 | "type": "git", 1254 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 1255 | "reference": "791198a2c6254db10131eecfe8c06670700904db" 1256 | }, 1257 | "dist": { 1258 | "type": "zip", 1259 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", 1260 | "reference": "791198a2c6254db10131eecfe8c06670700904db", 1261 | "shasum": "" 1262 | }, 1263 | "require": { 1264 | "ext-tokenizer": "*", 1265 | "php": "^7.0" 1266 | }, 1267 | "require-dev": { 1268 | "phpunit/phpunit": "^6.2.4" 1269 | }, 1270 | "type": "library", 1271 | "extra": { 1272 | "branch-alias": { 1273 | "dev-master": "2.0-dev" 1274 | } 1275 | }, 1276 | "autoload": { 1277 | "classmap": [ 1278 | "src/" 1279 | ] 1280 | }, 1281 | "notification-url": "https://packagist.org/downloads/", 1282 | "license": [ 1283 | "BSD-3-Clause" 1284 | ], 1285 | "authors": [ 1286 | { 1287 | "name": "Sebastian Bergmann", 1288 | "email": "sebastian@phpunit.de" 1289 | } 1290 | ], 1291 | "description": "Wrapper around PHP's tokenizer extension.", 1292 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 1293 | "keywords": [ 1294 | "tokenizer" 1295 | ], 1296 | "support": { 1297 | "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", 1298 | "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" 1299 | }, 1300 | "abandoned": true, 1301 | "time": "2017-11-27T05:48:46+00:00" 1302 | }, 1303 | { 1304 | "name": "phpunit/phpunit", 1305 | "version": "5.7.27", 1306 | "source": { 1307 | "type": "git", 1308 | "url": "https://github.com/sebastianbergmann/phpunit.git", 1309 | "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" 1310 | }, 1311 | "dist": { 1312 | "type": "zip", 1313 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", 1314 | "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", 1315 | "shasum": "" 1316 | }, 1317 | "require": { 1318 | "ext-dom": "*", 1319 | "ext-json": "*", 1320 | "ext-libxml": "*", 1321 | "ext-mbstring": "*", 1322 | "ext-xml": "*", 1323 | "myclabs/deep-copy": "~1.3", 1324 | "php": "^5.6 || ^7.0", 1325 | "phpspec/prophecy": "^1.6.2", 1326 | "phpunit/php-code-coverage": "^4.0.4", 1327 | "phpunit/php-file-iterator": "~1.4", 1328 | "phpunit/php-text-template": "~1.2", 1329 | "phpunit/php-timer": "^1.0.6", 1330 | "phpunit/phpunit-mock-objects": "^3.2", 1331 | "sebastian/comparator": "^1.2.4", 1332 | "sebastian/diff": "^1.4.3", 1333 | "sebastian/environment": "^1.3.4 || ^2.0", 1334 | "sebastian/exporter": "~2.0", 1335 | "sebastian/global-state": "^1.1", 1336 | "sebastian/object-enumerator": "~2.0", 1337 | "sebastian/resource-operations": "~1.0", 1338 | "sebastian/version": "^1.0.6|^2.0.1", 1339 | "symfony/yaml": "~2.1|~3.0|~4.0" 1340 | }, 1341 | "conflict": { 1342 | "phpdocumentor/reflection-docblock": "3.0.2" 1343 | }, 1344 | "require-dev": { 1345 | "ext-pdo": "*" 1346 | }, 1347 | "suggest": { 1348 | "ext-xdebug": "*", 1349 | "phpunit/php-invoker": "~1.1" 1350 | }, 1351 | "bin": [ 1352 | "phpunit" 1353 | ], 1354 | "type": "library", 1355 | "extra": { 1356 | "branch-alias": { 1357 | "dev-master": "5.7.x-dev" 1358 | } 1359 | }, 1360 | "autoload": { 1361 | "classmap": [ 1362 | "src/" 1363 | ] 1364 | }, 1365 | "notification-url": "https://packagist.org/downloads/", 1366 | "license": [ 1367 | "BSD-3-Clause" 1368 | ], 1369 | "authors": [ 1370 | { 1371 | "name": "Sebastian Bergmann", 1372 | "email": "sebastian@phpunit.de", 1373 | "role": "lead" 1374 | } 1375 | ], 1376 | "description": "The PHP Unit Testing framework.", 1377 | "homepage": "https://phpunit.de/", 1378 | "keywords": [ 1379 | "phpunit", 1380 | "testing", 1381 | "xunit" 1382 | ], 1383 | "support": { 1384 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 1385 | "source": "https://github.com/sebastianbergmann/phpunit/tree/5.7.27" 1386 | }, 1387 | "time": "2018-02-01T05:50:59+00:00" 1388 | }, 1389 | { 1390 | "name": "phpunit/phpunit-mock-objects", 1391 | "version": "3.4.4", 1392 | "source": { 1393 | "type": "git", 1394 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 1395 | "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" 1396 | }, 1397 | "dist": { 1398 | "type": "zip", 1399 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", 1400 | "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", 1401 | "shasum": "" 1402 | }, 1403 | "require": { 1404 | "doctrine/instantiator": "^1.0.2", 1405 | "php": "^5.6 || ^7.0", 1406 | "phpunit/php-text-template": "^1.2", 1407 | "sebastian/exporter": "^1.2 || ^2.0" 1408 | }, 1409 | "conflict": { 1410 | "phpunit/phpunit": "<5.4.0" 1411 | }, 1412 | "require-dev": { 1413 | "phpunit/phpunit": "^5.4" 1414 | }, 1415 | "suggest": { 1416 | "ext-soap": "*" 1417 | }, 1418 | "type": "library", 1419 | "extra": { 1420 | "branch-alias": { 1421 | "dev-master": "3.2.x-dev" 1422 | } 1423 | }, 1424 | "autoload": { 1425 | "classmap": [ 1426 | "src/" 1427 | ] 1428 | }, 1429 | "notification-url": "https://packagist.org/downloads/", 1430 | "license": [ 1431 | "BSD-3-Clause" 1432 | ], 1433 | "authors": [ 1434 | { 1435 | "name": "Sebastian Bergmann", 1436 | "email": "sb@sebastian-bergmann.de", 1437 | "role": "lead" 1438 | } 1439 | ], 1440 | "description": "Mock Object library for PHPUnit", 1441 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 1442 | "keywords": [ 1443 | "mock", 1444 | "xunit" 1445 | ], 1446 | "support": { 1447 | "irc": "irc://irc.freenode.net/phpunit", 1448 | "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues", 1449 | "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/3.4" 1450 | }, 1451 | "abandoned": true, 1452 | "time": "2017-06-30T09:13:00+00:00" 1453 | }, 1454 | { 1455 | "name": "sebastian/code-unit-reverse-lookup", 1456 | "version": "1.0.2", 1457 | "source": { 1458 | "type": "git", 1459 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1460 | "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" 1461 | }, 1462 | "dist": { 1463 | "type": "zip", 1464 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", 1465 | "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", 1466 | "shasum": "" 1467 | }, 1468 | "require": { 1469 | "php": ">=5.6" 1470 | }, 1471 | "require-dev": { 1472 | "phpunit/phpunit": "^8.5" 1473 | }, 1474 | "type": "library", 1475 | "extra": { 1476 | "branch-alias": { 1477 | "dev-master": "1.0.x-dev" 1478 | } 1479 | }, 1480 | "autoload": { 1481 | "classmap": [ 1482 | "src/" 1483 | ] 1484 | }, 1485 | "notification-url": "https://packagist.org/downloads/", 1486 | "license": [ 1487 | "BSD-3-Clause" 1488 | ], 1489 | "authors": [ 1490 | { 1491 | "name": "Sebastian Bergmann", 1492 | "email": "sebastian@phpunit.de" 1493 | } 1494 | ], 1495 | "description": "Looks up which function or method a line of code belongs to", 1496 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1497 | "support": { 1498 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 1499 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" 1500 | }, 1501 | "funding": [ 1502 | { 1503 | "url": "https://github.com/sebastianbergmann", 1504 | "type": "github" 1505 | } 1506 | ], 1507 | "time": "2020-11-30T08:15:22+00:00" 1508 | }, 1509 | { 1510 | "name": "sebastian/comparator", 1511 | "version": "1.2.4", 1512 | "source": { 1513 | "type": "git", 1514 | "url": "https://github.com/sebastianbergmann/comparator.git", 1515 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" 1516 | }, 1517 | "dist": { 1518 | "type": "zip", 1519 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 1520 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", 1521 | "shasum": "" 1522 | }, 1523 | "require": { 1524 | "php": ">=5.3.3", 1525 | "sebastian/diff": "~1.2", 1526 | "sebastian/exporter": "~1.2 || ~2.0" 1527 | }, 1528 | "require-dev": { 1529 | "phpunit/phpunit": "~4.4" 1530 | }, 1531 | "type": "library", 1532 | "extra": { 1533 | "branch-alias": { 1534 | "dev-master": "1.2.x-dev" 1535 | } 1536 | }, 1537 | "autoload": { 1538 | "classmap": [ 1539 | "src/" 1540 | ] 1541 | }, 1542 | "notification-url": "https://packagist.org/downloads/", 1543 | "license": [ 1544 | "BSD-3-Clause" 1545 | ], 1546 | "authors": [ 1547 | { 1548 | "name": "Jeff Welch", 1549 | "email": "whatthejeff@gmail.com" 1550 | }, 1551 | { 1552 | "name": "Volker Dusch", 1553 | "email": "github@wallbash.com" 1554 | }, 1555 | { 1556 | "name": "Bernhard Schussek", 1557 | "email": "bschussek@2bepublished.at" 1558 | }, 1559 | { 1560 | "name": "Sebastian Bergmann", 1561 | "email": "sebastian@phpunit.de" 1562 | } 1563 | ], 1564 | "description": "Provides the functionality to compare PHP values for equality", 1565 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 1566 | "keywords": [ 1567 | "comparator", 1568 | "compare", 1569 | "equality" 1570 | ], 1571 | "support": { 1572 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1573 | "source": "https://github.com/sebastianbergmann/comparator/tree/1.2" 1574 | }, 1575 | "time": "2017-01-29T09:50:25+00:00" 1576 | }, 1577 | { 1578 | "name": "sebastian/diff", 1579 | "version": "1.4.3", 1580 | "source": { 1581 | "type": "git", 1582 | "url": "https://github.com/sebastianbergmann/diff.git", 1583 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" 1584 | }, 1585 | "dist": { 1586 | "type": "zip", 1587 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", 1588 | "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", 1589 | "shasum": "" 1590 | }, 1591 | "require": { 1592 | "php": "^5.3.3 || ^7.0" 1593 | }, 1594 | "require-dev": { 1595 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 1596 | }, 1597 | "type": "library", 1598 | "extra": { 1599 | "branch-alias": { 1600 | "dev-master": "1.4-dev" 1601 | } 1602 | }, 1603 | "autoload": { 1604 | "classmap": [ 1605 | "src/" 1606 | ] 1607 | }, 1608 | "notification-url": "https://packagist.org/downloads/", 1609 | "license": [ 1610 | "BSD-3-Clause" 1611 | ], 1612 | "authors": [ 1613 | { 1614 | "name": "Kore Nordmann", 1615 | "email": "mail@kore-nordmann.de" 1616 | }, 1617 | { 1618 | "name": "Sebastian Bergmann", 1619 | "email": "sebastian@phpunit.de" 1620 | } 1621 | ], 1622 | "description": "Diff implementation", 1623 | "homepage": "https://github.com/sebastianbergmann/diff", 1624 | "keywords": [ 1625 | "diff" 1626 | ], 1627 | "support": { 1628 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1629 | "source": "https://github.com/sebastianbergmann/diff/tree/1.4" 1630 | }, 1631 | "time": "2017-05-22T07:24:03+00:00" 1632 | }, 1633 | { 1634 | "name": "sebastian/environment", 1635 | "version": "2.0.0", 1636 | "source": { 1637 | "type": "git", 1638 | "url": "https://github.com/sebastianbergmann/environment.git", 1639 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" 1640 | }, 1641 | "dist": { 1642 | "type": "zip", 1643 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", 1644 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", 1645 | "shasum": "" 1646 | }, 1647 | "require": { 1648 | "php": "^5.6 || ^7.0" 1649 | }, 1650 | "require-dev": { 1651 | "phpunit/phpunit": "^5.0" 1652 | }, 1653 | "type": "library", 1654 | "extra": { 1655 | "branch-alias": { 1656 | "dev-master": "2.0.x-dev" 1657 | } 1658 | }, 1659 | "autoload": { 1660 | "classmap": [ 1661 | "src/" 1662 | ] 1663 | }, 1664 | "notification-url": "https://packagist.org/downloads/", 1665 | "license": [ 1666 | "BSD-3-Clause" 1667 | ], 1668 | "authors": [ 1669 | { 1670 | "name": "Sebastian Bergmann", 1671 | "email": "sebastian@phpunit.de" 1672 | } 1673 | ], 1674 | "description": "Provides functionality to handle HHVM/PHP environments", 1675 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1676 | "keywords": [ 1677 | "Xdebug", 1678 | "environment", 1679 | "hhvm" 1680 | ], 1681 | "support": { 1682 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1683 | "source": "https://github.com/sebastianbergmann/environment/tree/master" 1684 | }, 1685 | "time": "2016-11-26T07:53:53+00:00" 1686 | }, 1687 | { 1688 | "name": "sebastian/exporter", 1689 | "version": "2.0.0", 1690 | "source": { 1691 | "type": "git", 1692 | "url": "https://github.com/sebastianbergmann/exporter.git", 1693 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" 1694 | }, 1695 | "dist": { 1696 | "type": "zip", 1697 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", 1698 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", 1699 | "shasum": "" 1700 | }, 1701 | "require": { 1702 | "php": ">=5.3.3", 1703 | "sebastian/recursion-context": "~2.0" 1704 | }, 1705 | "require-dev": { 1706 | "ext-mbstring": "*", 1707 | "phpunit/phpunit": "~4.4" 1708 | }, 1709 | "type": "library", 1710 | "extra": { 1711 | "branch-alias": { 1712 | "dev-master": "2.0.x-dev" 1713 | } 1714 | }, 1715 | "autoload": { 1716 | "classmap": [ 1717 | "src/" 1718 | ] 1719 | }, 1720 | "notification-url": "https://packagist.org/downloads/", 1721 | "license": [ 1722 | "BSD-3-Clause" 1723 | ], 1724 | "authors": [ 1725 | { 1726 | "name": "Jeff Welch", 1727 | "email": "whatthejeff@gmail.com" 1728 | }, 1729 | { 1730 | "name": "Volker Dusch", 1731 | "email": "github@wallbash.com" 1732 | }, 1733 | { 1734 | "name": "Bernhard Schussek", 1735 | "email": "bschussek@2bepublished.at" 1736 | }, 1737 | { 1738 | "name": "Sebastian Bergmann", 1739 | "email": "sebastian@phpunit.de" 1740 | }, 1741 | { 1742 | "name": "Adam Harvey", 1743 | "email": "aharvey@php.net" 1744 | } 1745 | ], 1746 | "description": "Provides the functionality to export PHP variables for visualization", 1747 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1748 | "keywords": [ 1749 | "export", 1750 | "exporter" 1751 | ], 1752 | "support": { 1753 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1754 | "source": "https://github.com/sebastianbergmann/exporter/tree/master" 1755 | }, 1756 | "time": "2016-11-19T08:54:04+00:00" 1757 | }, 1758 | { 1759 | "name": "sebastian/global-state", 1760 | "version": "1.1.1", 1761 | "source": { 1762 | "type": "git", 1763 | "url": "https://github.com/sebastianbergmann/global-state.git", 1764 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" 1765 | }, 1766 | "dist": { 1767 | "type": "zip", 1768 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", 1769 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", 1770 | "shasum": "" 1771 | }, 1772 | "require": { 1773 | "php": ">=5.3.3" 1774 | }, 1775 | "require-dev": { 1776 | "phpunit/phpunit": "~4.2" 1777 | }, 1778 | "suggest": { 1779 | "ext-uopz": "*" 1780 | }, 1781 | "type": "library", 1782 | "extra": { 1783 | "branch-alias": { 1784 | "dev-master": "1.0-dev" 1785 | } 1786 | }, 1787 | "autoload": { 1788 | "classmap": [ 1789 | "src/" 1790 | ] 1791 | }, 1792 | "notification-url": "https://packagist.org/downloads/", 1793 | "license": [ 1794 | "BSD-3-Clause" 1795 | ], 1796 | "authors": [ 1797 | { 1798 | "name": "Sebastian Bergmann", 1799 | "email": "sebastian@phpunit.de" 1800 | } 1801 | ], 1802 | "description": "Snapshotting of global state", 1803 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1804 | "keywords": [ 1805 | "global state" 1806 | ], 1807 | "support": { 1808 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1809 | "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1" 1810 | }, 1811 | "time": "2015-10-12T03:26:01+00:00" 1812 | }, 1813 | { 1814 | "name": "sebastian/object-enumerator", 1815 | "version": "2.0.1", 1816 | "source": { 1817 | "type": "git", 1818 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1819 | "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" 1820 | }, 1821 | "dist": { 1822 | "type": "zip", 1823 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", 1824 | "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", 1825 | "shasum": "" 1826 | }, 1827 | "require": { 1828 | "php": ">=5.6", 1829 | "sebastian/recursion-context": "~2.0" 1830 | }, 1831 | "require-dev": { 1832 | "phpunit/phpunit": "~5" 1833 | }, 1834 | "type": "library", 1835 | "extra": { 1836 | "branch-alias": { 1837 | "dev-master": "2.0.x-dev" 1838 | } 1839 | }, 1840 | "autoload": { 1841 | "classmap": [ 1842 | "src/" 1843 | ] 1844 | }, 1845 | "notification-url": "https://packagist.org/downloads/", 1846 | "license": [ 1847 | "BSD-3-Clause" 1848 | ], 1849 | "authors": [ 1850 | { 1851 | "name": "Sebastian Bergmann", 1852 | "email": "sebastian@phpunit.de" 1853 | } 1854 | ], 1855 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1856 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1857 | "support": { 1858 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1859 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/master" 1860 | }, 1861 | "time": "2017-02-18T15:18:39+00:00" 1862 | }, 1863 | { 1864 | "name": "sebastian/recursion-context", 1865 | "version": "2.0.0", 1866 | "source": { 1867 | "type": "git", 1868 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1869 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" 1870 | }, 1871 | "dist": { 1872 | "type": "zip", 1873 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", 1874 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", 1875 | "shasum": "" 1876 | }, 1877 | "require": { 1878 | "php": ">=5.3.3" 1879 | }, 1880 | "require-dev": { 1881 | "phpunit/phpunit": "~4.4" 1882 | }, 1883 | "type": "library", 1884 | "extra": { 1885 | "branch-alias": { 1886 | "dev-master": "2.0.x-dev" 1887 | } 1888 | }, 1889 | "autoload": { 1890 | "classmap": [ 1891 | "src/" 1892 | ] 1893 | }, 1894 | "notification-url": "https://packagist.org/downloads/", 1895 | "license": [ 1896 | "BSD-3-Clause" 1897 | ], 1898 | "authors": [ 1899 | { 1900 | "name": "Jeff Welch", 1901 | "email": "whatthejeff@gmail.com" 1902 | }, 1903 | { 1904 | "name": "Sebastian Bergmann", 1905 | "email": "sebastian@phpunit.de" 1906 | }, 1907 | { 1908 | "name": "Adam Harvey", 1909 | "email": "aharvey@php.net" 1910 | } 1911 | ], 1912 | "description": "Provides functionality to recursively process PHP variables", 1913 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1914 | "support": { 1915 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1916 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/master" 1917 | }, 1918 | "time": "2016-11-19T07:33:16+00:00" 1919 | }, 1920 | { 1921 | "name": "sebastian/resource-operations", 1922 | "version": "1.0.0", 1923 | "source": { 1924 | "type": "git", 1925 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1926 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" 1927 | }, 1928 | "dist": { 1929 | "type": "zip", 1930 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 1931 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 1932 | "shasum": "" 1933 | }, 1934 | "require": { 1935 | "php": ">=5.6.0" 1936 | }, 1937 | "type": "library", 1938 | "extra": { 1939 | "branch-alias": { 1940 | "dev-master": "1.0.x-dev" 1941 | } 1942 | }, 1943 | "autoload": { 1944 | "classmap": [ 1945 | "src/" 1946 | ] 1947 | }, 1948 | "notification-url": "https://packagist.org/downloads/", 1949 | "license": [ 1950 | "BSD-3-Clause" 1951 | ], 1952 | "authors": [ 1953 | { 1954 | "name": "Sebastian Bergmann", 1955 | "email": "sebastian@phpunit.de" 1956 | } 1957 | ], 1958 | "description": "Provides a list of PHP built-in functions that operate on resources", 1959 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1960 | "support": { 1961 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 1962 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/master" 1963 | }, 1964 | "time": "2015-07-28T20:34:47+00:00" 1965 | }, 1966 | { 1967 | "name": "sebastian/version", 1968 | "version": "2.0.1", 1969 | "source": { 1970 | "type": "git", 1971 | "url": "https://github.com/sebastianbergmann/version.git", 1972 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1973 | }, 1974 | "dist": { 1975 | "type": "zip", 1976 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1977 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1978 | "shasum": "" 1979 | }, 1980 | "require": { 1981 | "php": ">=5.6" 1982 | }, 1983 | "type": "library", 1984 | "extra": { 1985 | "branch-alias": { 1986 | "dev-master": "2.0.x-dev" 1987 | } 1988 | }, 1989 | "autoload": { 1990 | "classmap": [ 1991 | "src/" 1992 | ] 1993 | }, 1994 | "notification-url": "https://packagist.org/downloads/", 1995 | "license": [ 1996 | "BSD-3-Clause" 1997 | ], 1998 | "authors": [ 1999 | { 2000 | "name": "Sebastian Bergmann", 2001 | "email": "sebastian@phpunit.de", 2002 | "role": "lead" 2003 | } 2004 | ], 2005 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 2006 | "homepage": "https://github.com/sebastianbergmann/version", 2007 | "support": { 2008 | "issues": "https://github.com/sebastianbergmann/version/issues", 2009 | "source": "https://github.com/sebastianbergmann/version/tree/master" 2010 | }, 2011 | "time": "2016-10-03T07:35:21+00:00" 2012 | }, 2013 | { 2014 | "name": "symfony/polyfill-ctype", 2015 | "version": "v1.26.0", 2016 | "source": { 2017 | "type": "git", 2018 | "url": "https://github.com/symfony/polyfill-ctype.git", 2019 | "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" 2020 | }, 2021 | "dist": { 2022 | "type": "zip", 2023 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", 2024 | "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", 2025 | "shasum": "" 2026 | }, 2027 | "require": { 2028 | "php": ">=7.1" 2029 | }, 2030 | "provide": { 2031 | "ext-ctype": "*" 2032 | }, 2033 | "suggest": { 2034 | "ext-ctype": "For best performance" 2035 | }, 2036 | "type": "library", 2037 | "extra": { 2038 | "branch-alias": { 2039 | "dev-main": "1.26-dev" 2040 | }, 2041 | "thanks": { 2042 | "name": "symfony/polyfill", 2043 | "url": "https://github.com/symfony/polyfill" 2044 | } 2045 | }, 2046 | "autoload": { 2047 | "files": [ 2048 | "bootstrap.php" 2049 | ], 2050 | "psr-4": { 2051 | "Symfony\\Polyfill\\Ctype\\": "" 2052 | } 2053 | }, 2054 | "notification-url": "https://packagist.org/downloads/", 2055 | "license": [ 2056 | "MIT" 2057 | ], 2058 | "authors": [ 2059 | { 2060 | "name": "Gert de Pagter", 2061 | "email": "BackEndTea@gmail.com" 2062 | }, 2063 | { 2064 | "name": "Symfony Community", 2065 | "homepage": "https://symfony.com/contributors" 2066 | } 2067 | ], 2068 | "description": "Symfony polyfill for ctype functions", 2069 | "homepage": "https://symfony.com", 2070 | "keywords": [ 2071 | "compatibility", 2072 | "ctype", 2073 | "polyfill", 2074 | "portable" 2075 | ], 2076 | "support": { 2077 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" 2078 | }, 2079 | "funding": [ 2080 | { 2081 | "url": "https://symfony.com/sponsor", 2082 | "type": "custom" 2083 | }, 2084 | { 2085 | "url": "https://github.com/fabpot", 2086 | "type": "github" 2087 | }, 2088 | { 2089 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2090 | "type": "tidelift" 2091 | } 2092 | ], 2093 | "time": "2022-05-24T11:49:31+00:00" 2094 | }, 2095 | { 2096 | "name": "symfony/polyfill-mbstring", 2097 | "version": "v1.26.0", 2098 | "source": { 2099 | "type": "git", 2100 | "url": "https://github.com/symfony/polyfill-mbstring.git", 2101 | "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" 2102 | }, 2103 | "dist": { 2104 | "type": "zip", 2105 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", 2106 | "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", 2107 | "shasum": "" 2108 | }, 2109 | "require": { 2110 | "php": ">=7.1" 2111 | }, 2112 | "provide": { 2113 | "ext-mbstring": "*" 2114 | }, 2115 | "suggest": { 2116 | "ext-mbstring": "For best performance" 2117 | }, 2118 | "type": "library", 2119 | "extra": { 2120 | "branch-alias": { 2121 | "dev-main": "1.26-dev" 2122 | }, 2123 | "thanks": { 2124 | "name": "symfony/polyfill", 2125 | "url": "https://github.com/symfony/polyfill" 2126 | } 2127 | }, 2128 | "autoload": { 2129 | "files": [ 2130 | "bootstrap.php" 2131 | ], 2132 | "psr-4": { 2133 | "Symfony\\Polyfill\\Mbstring\\": "" 2134 | } 2135 | }, 2136 | "notification-url": "https://packagist.org/downloads/", 2137 | "license": [ 2138 | "MIT" 2139 | ], 2140 | "authors": [ 2141 | { 2142 | "name": "Nicolas Grekas", 2143 | "email": "p@tchwork.com" 2144 | }, 2145 | { 2146 | "name": "Symfony Community", 2147 | "homepage": "https://symfony.com/contributors" 2148 | } 2149 | ], 2150 | "description": "Symfony polyfill for the Mbstring extension", 2151 | "homepage": "https://symfony.com", 2152 | "keywords": [ 2153 | "compatibility", 2154 | "mbstring", 2155 | "polyfill", 2156 | "portable", 2157 | "shim" 2158 | ], 2159 | "support": { 2160 | "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" 2161 | }, 2162 | "funding": [ 2163 | { 2164 | "url": "https://symfony.com/sponsor", 2165 | "type": "custom" 2166 | }, 2167 | { 2168 | "url": "https://github.com/fabpot", 2169 | "type": "github" 2170 | }, 2171 | { 2172 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2173 | "type": "tidelift" 2174 | } 2175 | ], 2176 | "time": "2022-05-24T11:49:31+00:00" 2177 | }, 2178 | { 2179 | "name": "symfony/yaml", 2180 | "version": "v4.4.45", 2181 | "source": { 2182 | "type": "git", 2183 | "url": "https://github.com/symfony/yaml.git", 2184 | "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d" 2185 | }, 2186 | "dist": { 2187 | "type": "zip", 2188 | "url": "https://api.github.com/repos/symfony/yaml/zipball/aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", 2189 | "reference": "aeccc4dc52a9e634f1d1eebeb21eacfdcff1053d", 2190 | "shasum": "" 2191 | }, 2192 | "require": { 2193 | "php": ">=7.1.3", 2194 | "symfony/polyfill-ctype": "~1.8" 2195 | }, 2196 | "conflict": { 2197 | "symfony/console": "<3.4" 2198 | }, 2199 | "require-dev": { 2200 | "symfony/console": "^3.4|^4.0|^5.0" 2201 | }, 2202 | "suggest": { 2203 | "symfony/console": "For validating YAML files using the lint command" 2204 | }, 2205 | "type": "library", 2206 | "autoload": { 2207 | "psr-4": { 2208 | "Symfony\\Component\\Yaml\\": "" 2209 | }, 2210 | "exclude-from-classmap": [ 2211 | "/Tests/" 2212 | ] 2213 | }, 2214 | "notification-url": "https://packagist.org/downloads/", 2215 | "license": [ 2216 | "MIT" 2217 | ], 2218 | "authors": [ 2219 | { 2220 | "name": "Fabien Potencier", 2221 | "email": "fabien@symfony.com" 2222 | }, 2223 | { 2224 | "name": "Symfony Community", 2225 | "homepage": "https://symfony.com/contributors" 2226 | } 2227 | ], 2228 | "description": "Loads and dumps YAML files", 2229 | "homepage": "https://symfony.com", 2230 | "support": { 2231 | "source": "https://github.com/symfony/yaml/tree/v4.4.45" 2232 | }, 2233 | "funding": [ 2234 | { 2235 | "url": "https://symfony.com/sponsor", 2236 | "type": "custom" 2237 | }, 2238 | { 2239 | "url": "https://github.com/fabpot", 2240 | "type": "github" 2241 | }, 2242 | { 2243 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2244 | "type": "tidelift" 2245 | } 2246 | ], 2247 | "time": "2022-08-02T15:47:23+00:00" 2248 | }, 2249 | { 2250 | "name": "webmozart/assert", 2251 | "version": "1.11.0", 2252 | "source": { 2253 | "type": "git", 2254 | "url": "https://github.com/webmozarts/assert.git", 2255 | "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" 2256 | }, 2257 | "dist": { 2258 | "type": "zip", 2259 | "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", 2260 | "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", 2261 | "shasum": "" 2262 | }, 2263 | "require": { 2264 | "ext-ctype": "*", 2265 | "php": "^7.2 || ^8.0" 2266 | }, 2267 | "conflict": { 2268 | "phpstan/phpstan": "<0.12.20", 2269 | "vimeo/psalm": "<4.6.1 || 4.6.2" 2270 | }, 2271 | "require-dev": { 2272 | "phpunit/phpunit": "^8.5.13" 2273 | }, 2274 | "type": "library", 2275 | "extra": { 2276 | "branch-alias": { 2277 | "dev-master": "1.10-dev" 2278 | } 2279 | }, 2280 | "autoload": { 2281 | "psr-4": { 2282 | "Webmozart\\Assert\\": "src/" 2283 | } 2284 | }, 2285 | "notification-url": "https://packagist.org/downloads/", 2286 | "license": [ 2287 | "MIT" 2288 | ], 2289 | "authors": [ 2290 | { 2291 | "name": "Bernhard Schussek", 2292 | "email": "bschussek@gmail.com" 2293 | } 2294 | ], 2295 | "description": "Assertions to validate method input/output with nice error messages.", 2296 | "keywords": [ 2297 | "assert", 2298 | "check", 2299 | "validate" 2300 | ], 2301 | "support": { 2302 | "issues": "https://github.com/webmozarts/assert/issues", 2303 | "source": "https://github.com/webmozarts/assert/tree/1.11.0" 2304 | }, 2305 | "time": "2022-06-03T18:03:27+00:00" 2306 | } 2307 | ], 2308 | "aliases": [], 2309 | "minimum-stability": "stable", 2310 | "stability-flags": [], 2311 | "prefer-stable": false, 2312 | "prefer-lowest": false, 2313 | "platform": { 2314 | "php": ">=5.6" 2315 | }, 2316 | "platform-dev": [], 2317 | "plugin-api-version": "2.2.0" 2318 | } 2319 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | ./tests/ 14 | 15 | 16 | 17 | 18 | 19 | ./src/ 20 | 21 | 22 | 23 | ./vendor 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/AccessToken.php: -------------------------------------------------------------------------------- 1 | token = $token; 32 | $this->type = $type; 33 | $this->data = $data; 34 | if (isset($data['expires'])) { 35 | $this->expires = new \DateTime(); 36 | $this->expires->setTimestamp($data['expires']); 37 | } elseif (isset($data['expires_in'])) { 38 | $this->expires = new \DateTime(); 39 | $this->expires->add(new \DateInterval(sprintf('PT%sS', $data['expires_in']))); 40 | } 41 | if (isset($data['refresh_token'])) { 42 | $this->refreshToken = new self($data['refresh_token'], 'refresh_token'); 43 | } 44 | } 45 | 46 | /** 47 | * @return bool 48 | */ 49 | public function isExpired() 50 | { 51 | return $this->expires !== null && $this->expires->getTimestamp() < time(); 52 | } 53 | 54 | /** 55 | * @return \DateTime|null 56 | */ 57 | public function getExpires() 58 | { 59 | return $this->expires; 60 | } 61 | 62 | /** 63 | * @return array 64 | */ 65 | public function getData() 66 | { 67 | return $this->data; 68 | } 69 | 70 | /** 71 | * @return string 72 | */ 73 | public function getScope() 74 | { 75 | return isset($this->data['scope']) ? $this->data['scope'] : ''; 76 | } 77 | 78 | /** 79 | * @return string 80 | */ 81 | public function getToken() 82 | { 83 | return $this->token; 84 | } 85 | 86 | /** 87 | * @return AccessToken|null 88 | */ 89 | public function getRefreshToken() 90 | { 91 | return $this->refreshToken; 92 | } 93 | 94 | /** 95 | * @return string 96 | */ 97 | public function getType() 98 | { 99 | return $this->type; 100 | } 101 | 102 | /** 103 | * @param AccessToken $refreshToken 104 | * 105 | * @return self 106 | */ 107 | public function setRefreshToken(AccessToken $refreshToken) 108 | { 109 | if ($refreshToken->getType() != 'refresh_token') { 110 | throw new InvalidArgumentException(sprintf('Expected AccessToken to be "refresh_token" type, got "%s"', $refreshToken->getType())); 111 | } 112 | 113 | $this->refreshToken = $refreshToken; 114 | 115 | return $this; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/GrantType/AuthorizationCode.php: -------------------------------------------------------------------------------- 1 | '']); 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | protected function getRequired() 29 | { 30 | return array_merge(parent::getRequired(), [self::CONFIG_CODE => '']); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/GrantType/ClientCredentials.php: -------------------------------------------------------------------------------- 1 | client = $client; 44 | $this->config = array_merge($this->getDefaults(), $config); 45 | 46 | foreach ($this->getRequired() as $key => $requiredAttribute) { 47 | if (!isset($this->config[$key]) || empty($this->config[$key])) { 48 | throw new InvalidArgumentException(sprintf(self::MISSING_ARGUMENT, $key)); 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * Get default configuration items. 55 | * 56 | * @return array 57 | */ 58 | protected function getDefaults() 59 | { 60 | return [ 61 | 'scope' => '', 62 | self::CONFIG_TOKEN_URL => '/oauth2/token', 63 | self::CONFIG_AUTH_LOCATION => 'headers', 64 | ]; 65 | } 66 | 67 | /** 68 | * Get required configuration items. 69 | * 70 | * @return string[] 71 | */ 72 | protected function getRequired() 73 | { 74 | return [ 75 | self::CONFIG_CLIENT_ID => '', 76 | ]; 77 | } 78 | 79 | /** 80 | * Get additional options, if any. 81 | * 82 | * @return array|null 83 | */ 84 | protected function getAdditionalOptions() 85 | { 86 | return null; 87 | } 88 | 89 | /** 90 | * @return array 91 | */ 92 | public function getConfig() 93 | { 94 | return $this->config; 95 | } 96 | 97 | /** 98 | * @param string $name 99 | * 100 | * @return mixed|null 101 | */ 102 | public function getConfigByName($name) 103 | { 104 | if (array_key_exists($name, $this->config)) { 105 | return $this->config[$name]; 106 | } 107 | 108 | return null; 109 | } 110 | 111 | /** 112 | * {@inheritdoc} 113 | */ 114 | public function getToken() 115 | { 116 | $body = $this->config; 117 | $body[self::GRANT_TYPE] = $this->grantType; 118 | unset($body[self::CONFIG_TOKEN_URL], $body[self::CONFIG_AUTH_LOCATION]); 119 | 120 | $requestOptions = []; 121 | 122 | if ($this->config[self::CONFIG_AUTH_LOCATION] !== RequestOptions::BODY) { 123 | $requestOptions[RequestOptions::AUTH] = [ 124 | $this->config[self::CONFIG_CLIENT_ID], 125 | isset($this->config[self::CONFIG_CLIENT_SECRET]) ? $this->config[self::CONFIG_CLIENT_SECRET] : '', 126 | ]; 127 | unset($body[self::CONFIG_CLIENT_ID], $body[self::CONFIG_CLIENT_SECRET]); 128 | } 129 | 130 | $requestOptions[RequestOptions::FORM_PARAMS] = $body; 131 | 132 | if ($additionalOptions = $this->getAdditionalOptions()) { 133 | $requestOptions = array_merge_recursive($requestOptions, $additionalOptions); 134 | } 135 | 136 | $response = $this->client->post($this->config[self::CONFIG_TOKEN_URL], $requestOptions); 137 | $data = json_decode($response->getBody()->__toString(), true); 138 | 139 | return new AccessToken($data['access_token'], $data['token_type'], $data); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/GrantType/GrantTypeInterface.php: -------------------------------------------------------------------------------- 1 | config[self::CONFIG_PRIVATE_KEY] instanceof SplFileObject)) { 32 | throw new InvalidArgumentException('private_key needs to be instance of SplFileObject'); 33 | } 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | protected function getRequired() 40 | { 41 | return array_merge(parent::getRequired(), [self::CONFIG_PRIVATE_KEY => null]); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected function getAdditionalOptions() 48 | { 49 | return [ 50 | RequestOptions::FORM_PARAMS => [ 51 | 'assertion' => $this->computeJwt(), 52 | ], 53 | ]; 54 | } 55 | 56 | /** 57 | * Compute JWT, signing with provided private key. 58 | * 59 | * @return string 60 | */ 61 | protected function computeJwt() 62 | { 63 | $baseUri = $this->client->getConfig('base_uri'); 64 | 65 | $payload = [ 66 | 'iss' => $this->config[self::CONFIG_CLIENT_ID], 67 | 'aud' => sprintf('%s/%s', rtrim(($baseUri instanceof UriInterface ? $baseUri->__toString() : ''), '/'), ltrim($this->config[self::CONFIG_TOKEN_URL], '/')), 68 | 'exp' => time() + 60 * 60, 69 | 'iat' => time(), 70 | ]; 71 | 72 | return JWT::encode($payload, $this->readPrivateKey($this->config[self::CONFIG_PRIVATE_KEY]), 'RS256'); 73 | } 74 | 75 | /** 76 | * Read private key. 77 | * 78 | * @param SplFileObject $privateKey 79 | * 80 | * @return string 81 | */ 82 | protected function readPrivateKey(SplFileObject $privateKey) 83 | { 84 | $key = ''; 85 | while (!$privateKey->eof()) { 86 | $key .= $privateKey->fgets(); 87 | } 88 | 89 | return $key; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/GrantType/PasswordCredentials.php: -------------------------------------------------------------------------------- 1 | '', self::CONFIG_PASSWORD => '']); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/GrantType/RefreshToken.php: -------------------------------------------------------------------------------- 1 | '']; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function setRefreshToken($refreshToken) 31 | { 32 | $this->config[self::CONFIG_REFRESH_TOKEN] = $refreshToken; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function hasRefreshToken() 39 | { 40 | return !empty($this->config[self::CONFIG_REFRESH_TOKEN]); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getToken() 47 | { 48 | if (!$this->hasRefreshToken()) { 49 | throw new \RuntimeException('Refresh token not available'); 50 | } 51 | 52 | return parent::getToken(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/GrantType/RefreshTokenGrantTypeInterface.php: -------------------------------------------------------------------------------- 1 | client = $client; 49 | $this->grantType = $grantType; 50 | $this->refreshTokenGrantType = $refreshTokenGrantType; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function onBefore() 57 | { 58 | return function (callable $handler) { 59 | return function (RequestInterface $request, array $options) use ($handler) { 60 | if ( 61 | isset($options['auth']) && 62 | 'oauth2' == $options['auth'] && 63 | $this->grantType instanceof GrantTypeInterface && 64 | $this->grantType->getConfigByName(GrantTypeBase::CONFIG_TOKEN_URL) != $request->getUri()->getPath() 65 | ) { 66 | $token = $this->getAccessToken(); 67 | if ($token !== null) { 68 | return $handler($request->withAddedHeader('Authorization', 'Bearer '.$token->getToken()), $options); 69 | } 70 | } 71 | 72 | return $handler($request, $options); 73 | }; 74 | }; 75 | } 76 | 77 | public function onFailure($limit) 78 | { 79 | $calls = 0; 80 | 81 | return function (callable $handler) use (&$calls, $limit) { 82 | return function (RequestInterface $request, array $options) use ($handler, &$calls, $limit) { 83 | /* @var PromiseInterface */ 84 | $promise = $handler($request, $options); 85 | 86 | return $promise->then( 87 | function (ResponseInterface $response) use ($request, $options, &$calls, $limit) { 88 | if ( 89 | $response->getStatusCode() == 401 && 90 | isset($options['auth']) && 91 | 'oauth2' == $options['auth'] && 92 | $this->grantType instanceof GrantTypeInterface && 93 | $this->grantType->getConfigByName(GrantTypeBase::CONFIG_TOKEN_URL) != $request->getUri()->getPath() 94 | ) { 95 | ++$calls; 96 | if ($calls > $limit) { 97 | return $response; 98 | } 99 | 100 | if ($token = $this->getAccessToken()) { 101 | $response = $this->client->send($request->withHeader('Authorization', 'Bearer '.$token->getToken()), $options); 102 | } 103 | } 104 | 105 | return $response; 106 | } 107 | ); 108 | }; 109 | }; 110 | } 111 | 112 | /** 113 | * Get a new access token. 114 | * 115 | * @return AccessToken|null 116 | */ 117 | protected function acquireAccessToken() 118 | { 119 | if ($this->refreshTokenGrantType) { 120 | // Get an access token using the stored refresh token. 121 | if ($this->accessToken instanceof AccessToken && $this->accessToken->getRefreshToken() instanceof AccessToken && $this->accessToken->isExpired()) { 122 | $this->refreshTokenGrantType->setRefreshToken($this->accessToken->getRefreshToken()->getToken()); 123 | $this->accessToken = $this->refreshTokenGrantType->getToken(); 124 | } 125 | } 126 | 127 | if ((!$this->accessToken || $this->accessToken->isExpired()) && $this->grantType) { 128 | // Get a new access token. 129 | $this->accessToken = $this->grantType->getToken(); 130 | } 131 | 132 | return $this->accessToken; 133 | } 134 | 135 | /** 136 | * Get the access token. 137 | * 138 | * @return AccessToken|null Oauth2 access token 139 | */ 140 | public function getAccessToken() 141 | { 142 | if (!($this->accessToken instanceof AccessToken) || $this->accessToken->isExpired()) { 143 | $this->acquireAccessToken(); 144 | } 145 | 146 | return $this->accessToken; 147 | } 148 | 149 | /** 150 | * Get the refresh token. 151 | * 152 | * @return AccessToken|null 153 | */ 154 | public function getRefreshToken() 155 | { 156 | if ($this->accessToken instanceof AccessToken) { 157 | return $this->accessToken->getRefreshToken(); 158 | } 159 | 160 | return null; 161 | } 162 | 163 | /** 164 | * Set the access token. 165 | * 166 | * @param AccessToken|string $accessToken 167 | * @param string $type 168 | * @param int $expires 169 | * 170 | * @return self 171 | */ 172 | public function setAccessToken($accessToken, $type = null, $expires = null) 173 | { 174 | if (is_string($accessToken)) { 175 | $this->accessToken = new AccessToken($accessToken, $type, ['expires' => $expires]); 176 | } elseif ($accessToken instanceof AccessToken) { 177 | $this->accessToken = $accessToken; 178 | } else { 179 | throw new \InvalidArgumentException('Invalid access token'); 180 | } 181 | 182 | if ( 183 | $this->accessToken->getRefreshToken() instanceof AccessToken && 184 | $this->refreshTokenGrantType instanceof RefreshTokenGrantTypeInterface 185 | ) { 186 | $this->refreshTokenGrantType->setRefreshToken($this->accessToken->getRefreshToken()->getToken()); 187 | } 188 | 189 | return $this; 190 | } 191 | 192 | /** 193 | * Set the refresh token. 194 | * 195 | * @param AccessToken|string $refreshToken The refresh token 196 | * 197 | * @return self 198 | */ 199 | public function setRefreshToken($refreshToken) 200 | { 201 | if (!($this->accessToken instanceof AccessToken)) { 202 | throw new \InvalidArgumentException('Unable to update the refresh token. You have never set first the access token.'); 203 | } 204 | 205 | if (is_string($refreshToken)) { 206 | $refreshToken = new AccessToken($refreshToken, 'refresh_token'); 207 | } elseif (!$refreshToken instanceof AccessToken) { 208 | throw new \InvalidArgumentException('Invalid refresh token'); 209 | } 210 | 211 | $this->accessToken->setRefreshToken($refreshToken); 212 | 213 | if ($this->refreshTokenGrantType instanceof RefreshTokenGrantTypeInterface) { 214 | $this->refreshTokenGrantType->setRefreshToken($refreshToken->getToken()); 215 | } 216 | 217 | return $this; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /tests/AccessTokenTest.php: -------------------------------------------------------------------------------- 1 | 'testToken', 13 | 'token_type' => 'bearer', 14 | 'expires_in' => 300, 15 | 'scope' => 'profile administration', 16 | 'refresh_token' => 'testRefreshToken', 17 | ]; 18 | $token = new AccessToken($data['access_token'], $data['token_type'], $data); 19 | $this->assertEquals($data['access_token'], $token->getToken()); 20 | $this->assertEquals($data['token_type'], $token->getType()); 21 | $this->assertEquals($data['scope'], $token->getScope()); 22 | $this->assertGreaterThan(time(), $token->getExpires()->getTimestamp()); 23 | $this->assertFalse($token->isExpired()); 24 | $this->assertEquals($data, $token->getData()); 25 | $this->assertEquals('refresh_token', $token->getRefreshToken()->getType()); 26 | $this->assertEquals($data['refresh_token'], $token->getRefreshToken()->getToken()); 27 | } 28 | 29 | public function testAccessTokenSetExpiresDirect() 30 | { 31 | $token = new AccessToken('testToken', 'bearer', ['expires' => 500]); 32 | $this->assertTrue($token->isExpired()); 33 | 34 | $token = new AccessToken('testToken', 'bearer', ['expires' => time() + 500]); 35 | $this->assertFalse($token->isExpired()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/GrantType/AuthorizationCodeTest.php: -------------------------------------------------------------------------------- 1 | expectException(\InvalidArgumentException::class); 13 | $this->expectExceptionMessage('The config is missing the following key: "client_id"'); 14 | 15 | new AuthorizationCode($this->createClient()); 16 | } 17 | 18 | public function testMissingConfigException() 19 | { 20 | $this->expectException(\InvalidArgumentException::class); 21 | $this->expectExceptionMessage('The config is missing the following key: "code"'); 22 | 23 | new AuthorizationCode($this->createClient(), [ 24 | 'client_id' => 'testClient', 25 | ]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/GrantType/JwtBearerTest.php: -------------------------------------------------------------------------------- 1 | expectException(\InvalidArgumentException::class); 14 | $this->expectExceptionMessage('The config is missing the following key: "client_id"'); 15 | 16 | new JwtBearer($this->createClient()); 17 | } 18 | 19 | public function testMissingConfigException() 20 | { 21 | $this->expectException(\InvalidArgumentException::class); 22 | $this->expectExceptionMessage('The config is missing the following key: "private_key"'); 23 | 24 | new JwtBearer($this->createClient(), [ 25 | 'client_id' => 'testClient', 26 | ]); 27 | } 28 | 29 | public function testPrivateKeyNotSplFileObject() 30 | { 31 | $this->expectException(\InvalidArgumentException::class); 32 | $this->expectExceptionMessage('private_key needs to be instance of SplFileObject'); 33 | 34 | new JwtBearer($this->createClient(), [ 35 | 'client_id' => 'testClient', 36 | 'private_key' => 'INVALID' 37 | ]); 38 | } 39 | 40 | public function testValidRequestGetsToken() 41 | { 42 | $grantType = new JwtBearer($this->createClient(), [ 43 | 'client_id' => 'testClient', 44 | 'private_key' => new SplFileObject(__DIR__ . '/../private.key') 45 | ]); 46 | $token = $grantType->getToken(); 47 | $this->assertNotEmpty($token->getToken()); 48 | $this->assertTrue($token->getExpires()->getTimestamp() > time()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/GrantType/PasswordCredentialsTest.php: -------------------------------------------------------------------------------- 1 | expectException(\InvalidArgumentException::class); 13 | $this->expectExceptionMessage('The config is missing the following key: "client_id"'); 14 | 15 | new PasswordCredentials($this->createClient()); 16 | } 17 | 18 | public function testMissingUsernameConfigException() 19 | { 20 | $this->expectException(\InvalidArgumentException::class); 21 | $this->expectExceptionMessage('The config is missing the following key: "username"'); 22 | 23 | new PasswordCredentials($this->createClient(), [ 24 | 'client_id' => 'testClient', 25 | ]); 26 | } 27 | 28 | public function testMissingPasswordConfigException() 29 | { 30 | $this->expectException(\InvalidArgumentException::class); 31 | $this->expectExceptionMessage('The config is missing the following key: "password"'); 32 | 33 | new PasswordCredentials($this->createClient(), [ 34 | 'client_id' => 'testClient', 35 | 'username' => 'validUsername', 36 | ]); 37 | } 38 | 39 | public function testValidPasswordGetsToken() 40 | { 41 | $grantType = new PasswordCredentials($this->createClient(), [ 42 | 'client_id' => 'testClient', 43 | 'username' => 'validUsername', 44 | 'password' => 'validPassword', 45 | ]); 46 | 47 | $token = $grantType->getToken(); 48 | $this->assertNotEmpty($token->getToken()); 49 | $this->assertTrue($token->getExpires()->getTimestamp() > time()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/GrantType/RefreshTokenTest.php: -------------------------------------------------------------------------------- 1 | expectException(\RuntimeException::class); 13 | 14 | $grant = new RefreshToken($this->createClient(), [ 15 | 'client_id' => 'test', 16 | ]); 17 | $grant->getToken(); 18 | } 19 | 20 | public function testMissingParentConfigException() 21 | { 22 | $this->expectException(\InvalidArgumentException::class); 23 | $this->expectExceptionMessage('The config is missing the following key: "client_id"'); 24 | 25 | new RefreshToken($this->createClient()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Middleware/OAuthMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | createClient([ 20 | RequestOptions::AUTH => 'oauth2', 21 | ], 22 | [ 23 | MockOAuth2Server::KEY_EXPECTED_QUERY_COUNT => 2, 24 | ]); 25 | 26 | $middleware = new OAuthMiddleware($client, new ClientCredentials($client, [ 27 | 'client_id' => 'test', 28 | 'client_secret' => 'testSecret', 29 | ])); 30 | 31 | $handlerStack = $this->getHandlerStack(); 32 | $handlerStack->push($middleware->onBefore()); 33 | $handlerStack->push($middleware->onFailure(5)); 34 | 35 | 36 | /** @var ResponseInterface */ 37 | $response = $client->get('/api/collection'); 38 | $this->assertEquals(200, $response->getStatusCode()); 39 | } 40 | 41 | public function testMiddlewareOnBeforeUsesRefreshToken() 42 | { 43 | $credentials = [ 44 | 'client_id' => 'test', 45 | 'client_secret' => 'testSecret', 46 | ]; 47 | 48 | $client = $this->createClient( 49 | [ 50 | RequestOptions::AUTH => 'oauth2', 51 | ], 52 | [ 53 | MockOAuth2Server::KEY_EXPECTED_QUERY_COUNT => 3, 54 | ] 55 | ); 56 | 57 | $accessTokenGrantType = new ClientCredentials( 58 | $client, 59 | $credentials 60 | ); 61 | $refreshToken = new RefreshToken($client, $credentials); 62 | 63 | $middleware = new OAuthMiddleware( 64 | $client, 65 | $accessTokenGrantType, 66 | $refreshToken 67 | ); 68 | $handlerStack = $this->getHandlerStack(); 69 | $handlerStack->push($middleware->onBefore()); 70 | $handlerStack->push($middleware->onFailure(5)); 71 | 72 | // Initially, the access token should be expired. Before the first API 73 | // call, the middleware will use the refresh token to get a new access 74 | // token. 75 | $accessToken = new AccessToken('tokenOld', 'client_credentials', [ 76 | 'refresh_token' => 'refreshTokenOld', 77 | 'expires' => 0 78 | ]); 79 | $middleware->setAccessToken($accessToken); 80 | 81 | $response = $client->get('/api/collection'); 82 | 83 | // Now, the access token should be valid. 84 | $this->assertEquals('token', $middleware->getAccessToken()->getToken()); 85 | $this->assertFalse($middleware->getAccessToken()->isExpired()); 86 | $this->assertEquals(200, $response->getStatusCode()); 87 | 88 | // Also, the refresh token should have changed. 89 | $newRefreshToken = $middleware->getRefreshToken(); 90 | $this->assertEquals('refreshToken', $newRefreshToken->getToken()); 91 | } 92 | 93 | public function testMiddlewareOnFailureUsesRefreshToken() 94 | { 95 | $credentials = [ 96 | 'client_id' => 'test', 97 | 'client_secret' => 'testSecret', 98 | ]; 99 | $client = $this->createClient( 100 | [ 101 | RequestOptions::AUTH => 'oauth2', 102 | ], 103 | [ 104 | MockOAuth2Server::KEY_TOKEN_INVALID_COUNT => 1, 105 | MockOAuth2Server::KEY_EXPECTED_QUERY_COUNT => 3, 106 | ] 107 | ); 108 | 109 | $accessTokenGrantType = new ClientCredentials($client, $credentials); 110 | 111 | $middleware = new MockOAuthMiddleware( 112 | $client, 113 | $accessTokenGrantType, 114 | new RefreshToken($client, $credentials), 115 | [ 116 | MockOAuthMiddleware::KEY_TOKEN_EXPIRED_ON_FAILURE_COUNT => 1, 117 | ] 118 | ); 119 | $handlerStack = $this->getHandlerStack(); 120 | $handlerStack->push($middleware->onBefore()); 121 | $handlerStack->push($middleware->modifyBeforeOnFailure()); 122 | $handlerStack->push($middleware->onFailure(5)); 123 | 124 | // Use a access token that isn't expired on the client side, but 125 | // the server thinks is expired. This should trigger the onFailure event 126 | // in the middleware, forcing it to try the refresh token grant type. 127 | $accessToken = new AccessToken('tokenInvalid', 'client_credentials', [ 128 | 'refresh_token' => 'refreshTokenOld', 129 | 'expires' => time() + 500, 130 | ]); 131 | $middleware->setAccessToken($accessToken); 132 | $this->assertFalse($middleware->getAccessToken()->isExpired()); 133 | 134 | //Will invoke once the onFailure 135 | $response = $client->get('/api/collection'); 136 | 137 | // Now, the access token should be valid. 138 | $this->assertFalse($middleware->getAccessToken()->isExpired()); 139 | $this->assertEquals(200, $response->getStatusCode()); 140 | 141 | // Also, the refresh token should have changed. 142 | $newRefreshToken = $middleware->getRefreshToken(); 143 | $this->assertEquals('refreshToken', $newRefreshToken->getToken()); 144 | } 145 | 146 | public function testMiddlewareWithValidNotExpiredToken() 147 | { 148 | $client = $this->createClient([ 149 | RequestOptions::AUTH => 'oauth2', 150 | RequestOptions::HTTP_ERRORS => false, 151 | ], [ 152 | MockOAuth2Server::KEY_EXPECTED_QUERY_COUNT => 1, 153 | ]); 154 | $credentials = [ 155 | 'client_id' => 'test', 156 | 'client_secret' => 'testSecret', 157 | ]; 158 | 159 | $accessTokenGrantType = new ClientCredentials($client, $credentials); 160 | 161 | $middleware = new OAuthMiddleware($client, $accessTokenGrantType); 162 | $handlerStack = $this->getHandlerStack(); 163 | $handlerStack->push($middleware->onBefore()); 164 | $handlerStack->push($middleware->onFailure(5)); 165 | 166 | // Set a valid token. 167 | $middleware->setAccessToken('token'); 168 | $this->assertEquals($middleware->getAccessToken()->getToken(), 'token'); 169 | $this->assertFalse($middleware->getAccessToken()->isExpired()); 170 | $response = $client->get('/api/collection'); 171 | $this->assertEquals(200, $response->getStatusCode()); 172 | } 173 | 174 | public function testOnFailureWhichReachesLimit() 175 | { 176 | $client = $this->createClient([ 177 | RequestOptions::AUTH => 'oauth2', 178 | RequestOptions::HTTP_ERRORS => false, 179 | ], [ 180 | MockOAuth2Server::KEY_TOKEN_INVALID_COUNT => 6, 181 | MockOAuth2Server::KEY_EXPECTED_QUERY_COUNT => 6, 182 | ]); 183 | $credentials = [ 184 | 'client_id' => 'test', 185 | 'client_secret' => 'testSecret', 186 | ]; 187 | 188 | $accessTokenGrantType = new ClientCredentials($client, $credentials); 189 | 190 | $middleware = new OAuthMiddleware($client, $accessTokenGrantType, new RefreshToken($client, $credentials)); 191 | $handlerStack = $this->getHandlerStack(); 192 | $handlerStack->push($middleware->onBefore()); 193 | $handlerStack->push($middleware->onFailure(5)); 194 | 195 | //Will invoke 5 times onFailure 196 | $middleware->setAccessToken('tokenInvalid'); 197 | $response = $client->get('/api/collection'); 198 | $this->assertEquals(401, $response->getStatusCode()); 199 | } 200 | 201 | public function testOnFailureWhichSuccessOnThirdTime() 202 | { 203 | $client = $this->createClient([ 204 | RequestOptions::AUTH => 'oauth2', 205 | RequestOptions::HTTP_ERRORS => false, 206 | ], [ 207 | MockOAuth2Server::KEY_TOKEN_INVALID_COUNT => 2, 208 | MockOAuth2Server::KEY_EXPECTED_QUERY_COUNT => 4, 209 | ]); 210 | $credentials = [ 211 | 'client_id' => 'test', 212 | 'client_secret' => 'testSecret', 213 | ]; 214 | 215 | $accessTokenGrantType = new ClientCredentials($client, $credentials); 216 | 217 | $middleware = new OAuthMiddleware($client, $accessTokenGrantType); 218 | $handlerStack = $this->getHandlerStack(); 219 | $handlerStack->push($middleware->onBefore()); 220 | $handlerStack->push($middleware->onFailure(5)); 221 | 222 | // Will invoke 2 times onFailure 223 | $response = $client->get('/api/collection'); 224 | $this->assertEquals(200, $response->getStatusCode()); 225 | } 226 | 227 | public function testSettingManualAccessTokenWithInvalidValue() 228 | { 229 | $this->expectException(\InvalidArgumentException::class); 230 | $this->expectExceptionMessage('Invalid access token'); 231 | 232 | $client = $this->createClient([], []); 233 | $middleware = new OAuthMiddleware($client); 234 | $middleware->setAccessToken([]); 235 | } 236 | 237 | public function testSettingManualRefreshTokenWhenNoAccessToken() 238 | { 239 | $this->expectException(\InvalidArgumentException::class); 240 | $this->expectExceptionMessage('Unable to update the refresh token. You have never set first the access token.'); 241 | 242 | $client = $this->createClient([], []); 243 | $middleware = new OAuthMiddleware($client); 244 | $middleware->setRefreshToken('refreshToken'); 245 | } 246 | 247 | public function testSettingManualRefreshTokenWithRefreshTokenGrantType() 248 | { 249 | $credentials = [ 250 | 'client_id' => 'test', 251 | 'client_secret' => 'testSecret', 252 | ]; 253 | $client = $this->createClient([], []); 254 | $accessTokenGrantType = new ClientCredentials($client, $credentials); 255 | $refreshTokenGrantType = new RefreshToken($client, $credentials); 256 | 257 | $middleware = new OAuthMiddleware($client, $accessTokenGrantType, $refreshTokenGrantType); 258 | $token = new AccessToken('token', 'client_credentials', ['refresh_token' => 'refreshTokenOld']); 259 | $middleware->setAccessToken($token); 260 | 261 | $this->assertEquals('refreshTokenOld', $middleware->getRefreshToken()->getToken()); 262 | $this->assertEquals('refreshTokenOld', $refreshTokenGrantType->getConfigByName(RefreshToken::CONFIG_REFRESH_TOKEN)); 263 | 264 | $middleware->setRefreshToken('refreshToken'); 265 | $this->assertEquals('refresh_token', $middleware->getRefreshToken()->getType()); 266 | $this->assertEquals('refreshToken', $middleware->getRefreshToken()->getToken()); 267 | $this->assertEquals('refreshToken', $refreshTokenGrantType->getConfigByName(RefreshToken::CONFIG_REFRESH_TOKEN)); 268 | } 269 | 270 | public function testSettingManualRefreshToken() 271 | { 272 | $client = $this->createClient([], []); 273 | 274 | $middleware = new OAuthMiddleware($client); 275 | $token = new AccessToken('token', 'client_credentials', ['refresh_token' => 'refreshTokenOld']); 276 | $middleware->setAccessToken($token); 277 | 278 | $this->assertEquals('refreshTokenOld', $middleware->getRefreshToken()->getToken()); 279 | 280 | $middleware->setRefreshToken('refreshToken'); 281 | $this->assertEquals('refresh_token', $middleware->getRefreshToken()->getType()); 282 | $this->assertEquals('refreshToken', $middleware->getRefreshToken()->getToken()); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /tests/MockOAuth2Server.php: -------------------------------------------------------------------------------- 1 | 3600, 34 | self::KEY_TOKEN_PATH => '/oauth2/token', 35 | self::KEY_EXPECTED_QUERY_COUNT => 1 36 | ]; 37 | 38 | $this->options = $options + $defaults; 39 | 40 | $handler = new MockHandler( 41 | $this->options[self::KEY_EXPECTED_QUERY_COUNT] > 0 ? 42 | array_fill( 43 | 0, 44 | $this->options[self::KEY_EXPECTED_QUERY_COUNT], 45 | function (RequestInterface $request, array $options) { 46 | return $this->getResult($request, $options); 47 | } 48 | ) 49 | : [] 50 | ); 51 | 52 | $this->handlerStack = HandlerStack::create($handler); 53 | } 54 | 55 | /** 56 | * @return HandlerStack 57 | */ 58 | public function getHandlerStack() 59 | { 60 | return $this->handlerStack; 61 | } 62 | 63 | /** 64 | * @param RequestInterface $request 65 | * @param array $options 66 | * 67 | * @throws \RuntimeException 68 | * 69 | * @return Response 70 | */ 71 | protected function getResult(RequestInterface $request, array $options) 72 | { 73 | if ($request->getUri()->getPath() === $this->options[self::KEY_TOKEN_PATH]) { 74 | return $this->oauth2Token($request, $options); 75 | } elseif (strpos($request->getUri()->getPath(), '/api/') !== false) { 76 | return $this->mockApiCall($request); 77 | } 78 | 79 | throw new \RuntimeException('Mock server cannot handle given request URI'); 80 | } 81 | 82 | /** 83 | * @param RequestInterface $request 84 | * @param array $options 85 | * 86 | * @throws \RuntimeException 87 | * 88 | * @return Response 89 | */ 90 | protected function oauth2Token(RequestInterface $request, array $options) 91 | { 92 | $body = $request->getBody()->__toString(); 93 | $requestBody = []; 94 | parse_str($body, $requestBody); 95 | $grantType = $requestBody['grant_type']; 96 | switch ($grantType) { 97 | case 'password': 98 | return $this->grantTypePassword($requestBody); 99 | 100 | case 'client_credentials': 101 | return $this->grantTypeClientCredentials($options); 102 | 103 | case 'refresh_token': 104 | return $this->grantTypeRefreshToken($requestBody); 105 | 106 | case 'urn:ietf:params:oauth:grant-type:jwt-bearer': 107 | return $this->grantTypeJwtBearer($requestBody); 108 | } 109 | 110 | throw new \RuntimeException("Test grant type not implemented: $grantType"); 111 | } 112 | 113 | /** 114 | * @return Response 115 | */ 116 | protected function validTokenResponse() 117 | { 118 | $token = [ 119 | 'access_token' => 'token', 120 | 'refresh_token' => 'refreshToken', 121 | 'token_type' => 'bearer', 122 | ]; 123 | 124 | if (isset($this->options[self::KEY_TOKEN_INVALID_COUNT])) { 125 | $token['access_token'] = 'tokenInvalid'; 126 | } elseif (isset($this->options[self::KEY_TOKEN_EXPIRES_IN])) { 127 | $token['expires_in'] = $this->options[self::KEY_TOKEN_EXPIRES_IN]; 128 | } 129 | 130 | return new Response(200, [], json_encode($token)); 131 | } 132 | 133 | /** 134 | * The response as expected by the MockHandler. 135 | * 136 | * @param array $requestBody 137 | * 138 | * @return Response 139 | */ 140 | protected function grantTypePassword(array $requestBody) 141 | { 142 | if ($requestBody['username'] != 'validUsername' || $requestBody['password'] != 'validPassword') { 143 | // @todo correct response headers 144 | return new Response(401); 145 | } 146 | 147 | return $this->validTokenResponse(); 148 | } 149 | 150 | /** 151 | * The response as expected by the MockHandler. 152 | * 153 | * @param array $options 154 | * 155 | * @return Response 156 | */ 157 | protected function grantTypeClientCredentials(array $options) 158 | { 159 | if (!isset($options['auth']) || !isset($options['auth'][1]) || $options['auth'][1] != 'testSecret') { 160 | // @todo correct response headers 161 | return new Response(401); 162 | } 163 | 164 | return $this->validTokenResponse(); 165 | } 166 | 167 | /** 168 | * @param array $requestBody 169 | * 170 | * @return Response 171 | */ 172 | protected function grantTypeRefreshToken(array $requestBody) 173 | { 174 | if ($requestBody['refresh_token'] == 'refreshTokenInvalid') { 175 | return new Response(401); 176 | } 177 | 178 | return $this->validTokenResponse(); 179 | } 180 | 181 | /** 182 | * @param array $requestBody 183 | * 184 | * @return Response 185 | */ 186 | protected function grantTypeJwtBearer(array $requestBody) 187 | { 188 | if (!array_key_exists('assertion', $requestBody)) { 189 | return new Response(401); 190 | } 191 | 192 | return $this->validTokenResponse(); 193 | } 194 | 195 | /** 196 | * @param RequestInterface $request 197 | * 198 | * @return Response 199 | */ 200 | protected function mockApiCall(RequestInterface $request) 201 | { 202 | if ( 203 | empty($request->getHeader('Authorization')) || 204 | ( 205 | $request->getHeader('Authorization')[0] == 'Bearer tokenInvalid' && 206 | isset($this->options[self::KEY_TOKEN_INVALID_COUNT]) && 207 | $this->tokenInvalidCount < $this->options[self::KEY_TOKEN_INVALID_COUNT] 208 | ) 209 | ) { 210 | if ($request->getHeader('Authorization')[0] == 'Bearer tokenInvalid') { 211 | ++$this->tokenInvalidCount; 212 | } 213 | 214 | return new Response(401); 215 | } 216 | 217 | return new Response(200, [], json_encode('Hello World!')); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /tests/MockOAuthMiddleware.php: -------------------------------------------------------------------------------- 1 | options = $options; 46 | $this->tokenExpiredOnFailureCount = 0; 47 | } 48 | 49 | /** 50 | * @return \Closure 51 | */ 52 | public function modifyBeforeOnFailure() 53 | { 54 | $calls = 0; 55 | 56 | return function (callable $handler) use (&$calls) { 57 | return function (RequestInterface $request, array $options) use ($handler, &$calls) { 58 | /** @var PromiseInterface */ 59 | $promise = $handler($request, $options); 60 | return $promise->then( 61 | function (ResponseInterface $response) use ($request, $options, &$calls) { 62 | if ( 63 | isset($this->options[self::KEY_TOKEN_EXPIRED_ON_FAILURE_COUNT]) && 64 | isset($options['auth']) && 65 | 'oauth2' == $options['auth'] && 66 | $this->grantType instanceof GrantTypeInterface && 67 | $this->grantType->getConfigByName(GrantTypeBase::CONFIG_TOKEN_URL) != $request->getUri()->getPath() 68 | ) { 69 | ++$this->tokenExpiredOnFailureCount; 70 | if ($this->tokenExpiredOnFailureCount <= $this->options[self::KEY_TOKEN_EXPIRED_ON_FAILURE_COUNT]) { 71 | $token = new AccessToken('tokenExpires', 'client_credentials', ['expires' => 0, 'refresh_token' => 'refreshTokenOld']); 72 | $this->setAccessToken($token); 73 | } 74 | } 75 | 76 | return $response; 77 | } 78 | ); 79 | }; 80 | }; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/TestBase.php: -------------------------------------------------------------------------------- 1 | server = new MockOAuth2Server($serverOptions); 26 | $this->client = new Client( 27 | ['handler' => $this->server->getHandlerStack()] + $options 28 | ); 29 | 30 | return $this->client; 31 | } 32 | 33 | /** 34 | * @return ClientInterface|null 35 | */ 36 | protected function getClient() 37 | { 38 | return $this->client; 39 | } 40 | 41 | /** 42 | * @return HandlerStack|null 43 | */ 44 | protected function getHandlerStack() 45 | { 46 | if ($this->server instanceof MockOAuth2Server) { 47 | return $this->server->getHandlerStack(); 48 | } 49 | 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCTT//DLGGM1jEJ 3 | kBPc69qjiS07nsYXwsawFChRBGu03MCUcyBr7wz6oq1jkCyJ0ZHAcTmY6bpdCpSf 4 | JM3dt16/GRvz+edmYB/CxnnIGQmlmH+kGAAQkA1wtkZLhYYRbpdb/aDE0Azx6M8h 5 | ibe+dOidlJJQhg1SSwPcVUVWpqs4atelf9QeQtrD92igUZhwpHjQgAE4WCrXPpaO 6 | x/07QiUge0pWHpFBamHwoalu3JLmWEED2uiAunKiY9+VJi6GAJABzIHbsh6i1XU3 7 | ATF2WNo7qgY2e8XlDanQypvq93qq+erwynclNJh+5yVhDCqD+IX0/knGb2MMdHTP 8 | XiU8pLjPAgMBAAECggEAJMl/h0/X9IGwsUCnlS3Y5anl/9OAiIJ9d48xGjpOY1YV 9 | SX0OhaWmyhhB0HE6jhglm7cquQL1JTL1NmDMgCfAo1wz3NN1c91hURSbaNrHy/Cv 10 | P1029uviT1lVaJqphkTly3Uk5sFF2ktXHnrzxb4QMPnfJ/ix7vEIv8cTj7YDYAz9 11 | WzdPk3dZ9HyWtt+hVc1fJSNdaqyNQyRzgiUuMCIu37j/MQ5mPWucm4siZcSzFfhl 12 | yp8JZX0GoKtNa1zCroyDGpOFt5iVUqInQhECbfW05YFW+1KZaGInqT22hHu4TDAg 13 | +2iiLdQnDf/ubNouMnw+wV5w922MowHGfcy2GSwaMQKBgQDDFGSVDP0KRhtfsdsk 14 | Km9nxQTJrxwL9K69vUGsr+xraC99BmXl+IrJRP9hiYl8YC1fhkQzB6h7LfG8KGyE 15 | V+FQQNSHuRMXtK76PU4cEqv8h0HBzxRix93dctAw8bYIfawqgPG+r3MLhBMMYy5O 16 | Pwms6YRZcDv3SKr8/CohdZVp9QKBgQDBUN6C0TgW8lKdemRuXtEpKDlCxDzXV6nt 17 | jPhHQ3gHVoDQkGyArZyFsa0DJmD/BOCVjCd+H2cO7EQeIJOHRxF9RerdRgfIt8lJ 18 | MtIUH3Ehep8R3oqSFneoZkdBjNH/tXpYTGhKfPpBZPYA0++lEr72nVYs78SMUrgE 19 | 7JZX1ysJMwKBgAqvpUrc6UeUy48UaRK0GGIw0rBRnVGyV5ghM+XHxUWk8WUB4rcU 20 | RFX+J5cqN5POmO2wpy+8bahBvgo2lKszPS5uPrYolzknNqaSkSLMiwtMRXfeZhl7 21 | JVYqIelsdDJG4BV79sIhTkYFOB3nmPPEVD1alVto4IANRQCSt6QZktO5AoGAcpN2 22 | vjw4rUkEdDfFbLEf8O/ZOFxM3ykjGxuRT9OKQXcgs/zVglLj0U2kiJhnpt6CKcC+ 23 | 6367O1oHaX/PUL9rez9EW8+U738Wex726lxUVg5yV0n6AWn1k8bC9vP6xz8Ne2YV 24 | 7ggy3y1yrLzwbXs12b8ZA1s8uBqS3MBIv1lVNYcCgYBYNpWtAWC5bIF/TgtrShrr 25 | Fw52T+W3Bf36codGT+E3WYlQiONDWt2H8Bd9EO1j7bVP5wk0CYdnTOKqSQTnxo0r 26 | THS6qHcin1XW566t09dLvoxCwRCSPsP8V3oZB9W31zndZNUhnxVT+RXNUu6i3XXz 27 | JTn6lp0rp9eHJEFV/s0Zkg== 28 | -----END PRIVATE KEY----- --------------------------------------------------------------------------------