├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── examples └── index.php ├── phpunit.xml.dist ├── src └── Provider │ ├── Exception │ └── EncryptionConfigurationException.php │ ├── Keycloak.php │ └── KeycloakResourceOwner.php └── test └── src └── Provider └── KeycloakTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /vendor 3 | composer.phar 4 | composer.lock -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [test/*] 3 | checks: 4 | php: 5 | code_rating: true 6 | remove_extra_empty_lines: true 7 | remove_php_closing_tag: true 8 | remove_trailing_whitespace: true 9 | fix_use_statements: 10 | remove_unused: true 11 | preserve_multiple: false 12 | preserve_blanklines: true 13 | order_alphabetically: true 14 | fix_php_opening_tag: true 15 | fix_linefeed: true 16 | fix_line_ending: true 17 | fix_identation_4spaces: true 18 | fix_doc_comments: true 19 | tools: 20 | external_code_coverage: 21 | timeout: 600 22 | runs: 2 23 | php_analyzer: true 24 | php_code_coverage: false 25 | php_code_sniffer: 26 | config: 27 | standard: PSR2 28 | filter: 29 | paths: ['src'] 30 | php_loc: 31 | enabled: true 32 | excluded_dirs: [examples, vendor, test] 33 | php_cpd: 34 | enabled: true 35 | excluded_dirs: [examples, vendor, test] 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | php: 6 | - 7.2 7 | - 7.3 8 | - 7.4 9 | - 8.0 10 | - 8.1 11 | - 8.2 12 | 13 | matrix: 14 | include: 15 | - php: 5.6 16 | env: 'COMPOSER_FLAGS="--prefer-stable --prefer-lowest"' 17 | 18 | before_script: 19 | - travis_retry composer self-update 20 | - travis_retry composer install --no-interaction --prefer-source --dev 21 | - travis_retry phpenv rehash 22 | 23 | script: 24 | - ./vendor/bin/phpcs --standard=psr2 src/ 25 | - ./vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 26 | 27 | after_script: 28 | - wget https://scrutinizer-ci.com/ocular.phar 29 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All Notable changes to `oauth2-keycloak` will be documented in this file 3 | 4 | ## 2.1.0 - 2018-03-12 5 | 6 | ### Added 7 | - Introduce `getLogoutUrl` method on provider to build and return and authorized logout url - thanks @FlxPeters 8 | 9 | ### Deprecated 10 | - Nothing 11 | 12 | ### Fixed 13 | - Nothing 14 | 15 | ### Removed 16 | - Nothing 17 | 18 | ### Security 19 | - Nothing 20 | 21 | ## 2.0.0 - 2017-01-25 22 | 23 | ### Added 24 | - PHP 7.1 Support 25 | 26 | ### Deprecated 27 | - Nothing 28 | 29 | ### Fixed 30 | - Nothing 31 | 32 | ### Removed 33 | - PHP 5.5 Support 34 | 35 | ### Security 36 | - Nothing 37 | 38 | ## 1.0.0 - 2017-01-25 39 | 40 | Bump for base package parity 41 | 42 | ## 0.2.0 - 2016-12-07 43 | 44 | ### Added 45 | - JSON Web Token decryption support 46 | 47 | ### Deprecated 48 | - Nothing 49 | 50 | ### Fixed 51 | - Nothing 52 | 53 | ### Removed 54 | - Nothing 55 | 56 | ### Security 57 | - Nothing 58 | 59 | ## 0.1.0 - 2015-08-31 60 | 61 | ### Added 62 | - Initial release! 63 | 64 | ### Deprecated 65 | - Nothing 66 | 67 | ### Fixed 68 | - Nothing 69 | 70 | ### Removed 71 | - Nothing 72 | 73 | ### Security 74 | - Nothing 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/stevenmaguire/oauth2-keycloak). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the README and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option. 17 | 18 | - **Create topic branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. 23 | 24 | - **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass. 25 | 26 | - **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails. 27 | 28 | 29 | ## Running Tests 30 | 31 | ``` bash 32 | $ ./vendor/bin/phpunit 33 | ``` 34 | 35 | 36 | ## Running PHP Code Sniffer 37 | 38 | ``` bash 39 | $ ./vendor/bin/phpcs src --standard=psr2 -sp 40 | ``` 41 | 42 | **Happy coding**! 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Steven Maguire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keycloak Provider for OAuth 2.0 Client 2 | [![Latest Version](https://img.shields.io/github/release/stevenmaguire/oauth2-keycloak.svg?style=flat-square)](https://github.com/stevenmaguire/oauth2-keycloak/releases) 3 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 4 | [![Build Status](https://img.shields.io/travis/stevenmaguire/oauth2-keycloak/master.svg?style=flat-square)](https://travis-ci.org/stevenmaguire/oauth2-keycloak) 5 | [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/stevenmaguire/oauth2-keycloak.svg?style=flat-square)](https://scrutinizer-ci.com/g/stevenmaguire/oauth2-keycloak/code-structure) 6 | [![Quality Score](https://img.shields.io/scrutinizer/g/stevenmaguire/oauth2-keycloak.svg?style=flat-square)](https://scrutinizer-ci.com/g/stevenmaguire/oauth2-keycloak) 7 | [![Total Downloads](https://img.shields.io/packagist/dt/stevenmaguire/oauth2-keycloak.svg?style=flat-square)](https://packagist.org/packages/stevenmaguire/oauth2-keycloak) 8 | 9 | This package provides Keycloak OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client). 10 | 11 | ## Installation 12 | 13 | To install, use composer: 14 | 15 | ``` 16 | composer require stevenmaguire/oauth2-keycloak 17 | ``` 18 | 19 | ## Usage 20 | 21 | Usage is the same as The League's OAuth client, using `\Stevenmaguire\OAuth2\Client\Provider\Keycloak` as the provider. 22 | 23 | Use `authServerUrl` to specify the Keycloak server URL. You can lookup the correct value from the Keycloak client installer JSON under `auth-server-url`, eg. `http://localhost:8080/auth`. 24 | 25 | Use `realm` to specify the Keycloak realm name. You can lookup the correct value from the Keycloak client installer JSON under `resource`, eg. `master`. 26 | 27 | ### Authorization Code Flow 28 | 29 | ```php 30 | $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ 31 | 'authServerUrl' => '{keycloak-server-url}', 32 | 'realm' => '{keycloak-realm}', 33 | 'clientId' => '{keycloak-client-id}', 34 | 'clientSecret' => '{keycloak-client-secret}', 35 | 'redirectUri' => 'https://example.com/callback-url', 36 | 'encryptionAlgorithm' => 'RS256', // optional 37 | 'encryptionKeyPath' => '../key.pem' // optional 38 | 'encryptionKey' => 'contents_of_key_or_certificate' // optional 39 | 'version' => '20.0.1', // optional 40 | ]); 41 | 42 | if (!isset($_GET['code'])) { 43 | 44 | // If we don't have an authorization code then get one 45 | $authUrl = $provider->getAuthorizationUrl(); 46 | $_SESSION['oauth2state'] = $provider->getState(); 47 | header('Location: '.$authUrl); 48 | exit; 49 | 50 | // Check given state against previously stored one to mitigate CSRF attack 51 | } elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { 52 | 53 | unset($_SESSION['oauth2state']); 54 | exit('Invalid state, make sure HTTP sessions are enabled.'); 55 | 56 | } else { 57 | 58 | // Try to get an access token (using the authorization coe grant) 59 | try { 60 | $token = $provider->getAccessToken('authorization_code', [ 61 | 'code' => $_GET['code'] 62 | ]); 63 | } catch (Exception $e) { 64 | exit('Failed to get access token: '.$e->getMessage()); 65 | } 66 | 67 | // Optional: Now you have a token you can look up a users profile data 68 | try { 69 | 70 | // We got an access token, let's now get the user's details 71 | $user = $provider->getResourceOwner($token); 72 | 73 | // Use these details to create a new profile 74 | printf('Hello %s!', $user->getName()); 75 | 76 | } catch (Exception $e) { 77 | exit('Failed to get resource owner: '.$e->getMessage()); 78 | } 79 | 80 | // Use this to interact with an API on the users behalf 81 | echo $token->getToken(); 82 | } 83 | ``` 84 | 85 | ### Refreshing a Token 86 | 87 | ```php 88 | $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ 89 | 'authServerUrl' => '{keycloak-server-url}', 90 | 'realm' => '{keycloak-realm}', 91 | 'clientId' => '{keycloak-client-id}', 92 | 'clientSecret' => '{keycloak-client-secret}', 93 | 'redirectUri' => 'https://example.com/callback-url', 94 | ]); 95 | 96 | $token = $provider->getAccessToken('refresh_token', ['refresh_token' => $token->getRefreshToken()]); 97 | ``` 98 | 99 | ### Handling encryption 100 | 101 | If you've configured your Keycloak instance to use encryption, there are some advanced options available to you. 102 | 103 | #### Configure the provider to use the same encryption algorithm 104 | 105 | ```php 106 | $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ 107 | // ... 108 | 'encryptionAlgorithm' => 'RS256', 109 | ]); 110 | ``` 111 | 112 | or 113 | 114 | ```php 115 | $provider->setEncryptionAlgorithm('RS256'); 116 | ``` 117 | 118 | #### Configure the provider to use the expected decryption public key or certificate 119 | 120 | ##### By key value 121 | 122 | ```php 123 | $key = "-----BEGIN PUBLIC KEY-----\n....\n-----END PUBLIC KEY-----"; 124 | // or 125 | // $key = "-----BEGIN CERTIFICATE-----\n....\n-----END CERTIFICATE-----"; 126 | 127 | $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ 128 | // ... 129 | 'encryptionKey' => $key, 130 | ]); 131 | ``` 132 | 133 | or 134 | 135 | ```php 136 | $provider->setEncryptionKey($key); 137 | ``` 138 | 139 | ##### By key path 140 | 141 | ```php 142 | $keyPath = '../key.pem'; 143 | 144 | $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ 145 | // ... 146 | 'encryptionKeyPath' => $keyPath, 147 | ]); 148 | ``` 149 | 150 | or 151 | 152 | ```php 153 | $provider->setEncryptionKeyPath($keyPath); 154 | ``` 155 | 156 | ## Testing 157 | 158 | ``` bash 159 | $ ./vendor/bin/phpunit 160 | ``` 161 | 162 | ## Contributing 163 | 164 | Please see [CONTRIBUTING](https://github.com/stevenmaguire/oauth2-keycloak/blob/master/CONTRIBUTING.md) for details. 165 | 166 | 167 | ## Credits 168 | 169 | - [Steven Maguire](https://github.com/stevenmaguire) 170 | - [Martin Stefan](https://github.com/mstefan21) 171 | - [All Contributors](https://github.com/stevenmaguire/oauth2-keycloak/contributors) 172 | 173 | 174 | ## License 175 | 176 | The MIT License (MIT). Please see [License File](https://github.com/stevenmaguire/oauth2-keycloak/blob/master/LICENSE) for more information. 177 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stevenmaguire/oauth2-keycloak", 3 | "description": "Keycloak OAuth 2.0 Client Provider for The PHP League OAuth2-Client", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Steven Maguire", 8 | "email": "stevenmaguire@gmail.com", 9 | "homepage": "https://github.com/stevenmaguire" 10 | } 11 | ], 12 | "keywords": [ 13 | "oauth", 14 | "oauth2", 15 | "client", 16 | "authorization", 17 | "authorisation", 18 | "keycloak" 19 | ], 20 | "require": { 21 | "php": "~7.2 || ~8.0", 22 | "league/oauth2-client": "^2.0", 23 | "firebase/php-jwt": "^6.0" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "~9.6.4", 27 | "mockery/mockery": "~1.5.0", 28 | "squizlabs/php_codesniffer": "~3.7.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Stevenmaguire\\OAuth2\\Client\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Stevenmaguire\\OAuth2\\Client\\Test\\": "test/src/" 38 | } 39 | }, 40 | "extra": { 41 | "branch-alias": { 42 | "dev-master": "1.0.x-dev" 43 | } 44 | }, 45 | "scripts": { 46 | "test": [ 47 | "@putenv XDEBUG_MODE=coverage", 48 | "phpunit --colors=always" 49 | ] 50 | } 51 | } -------------------------------------------------------------------------------- /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": "bcd33ce014a8d8af132250cf40b70ea9", 8 | "packages": [ 9 | { 10 | "name": "firebase/php-jwt", 11 | "version": "v6.5.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/firebase/php-jwt.git", 15 | "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/firebase/php-jwt/zipball/e94e7353302b0c11ec3cfff7180cd0b1743975d2", 20 | "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^7.4||^8.0" 25 | }, 26 | "require-dev": { 27 | "guzzlehttp/guzzle": "^6.5||^7.4", 28 | "phpspec/prophecy-phpunit": "^2.0", 29 | "phpunit/phpunit": "^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 | "ext-sodium": "Support EdDSA (Ed25519) signatures", 36 | "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" 37 | }, 38 | "type": "library", 39 | "autoload": { 40 | "psr-4": { 41 | "Firebase\\JWT\\": "src" 42 | } 43 | }, 44 | "notification-url": "https://packagist.org/downloads/", 45 | "license": [ 46 | "BSD-3-Clause" 47 | ], 48 | "authors": [ 49 | { 50 | "name": "Neuman Vong", 51 | "email": "neuman+pear@twilio.com", 52 | "role": "Developer" 53 | }, 54 | { 55 | "name": "Anant Narayanan", 56 | "email": "anant@php.net", 57 | "role": "Developer" 58 | } 59 | ], 60 | "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", 61 | "homepage": "https://github.com/firebase/php-jwt", 62 | "keywords": [ 63 | "jwt", 64 | "php" 65 | ], 66 | "support": { 67 | "issues": "https://github.com/firebase/php-jwt/issues", 68 | "source": "https://github.com/firebase/php-jwt/tree/v6.5.0" 69 | }, 70 | "time": "2023-05-12T15:47:07+00:00" 71 | }, 72 | { 73 | "name": "guzzlehttp/guzzle", 74 | "version": "7.7.0", 75 | "source": { 76 | "type": "git", 77 | "url": "https://github.com/guzzle/guzzle.git", 78 | "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" 79 | }, 80 | "dist": { 81 | "type": "zip", 82 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", 83 | "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", 84 | "shasum": "" 85 | }, 86 | "require": { 87 | "ext-json": "*", 88 | "guzzlehttp/promises": "^1.5.3 || ^2.0", 89 | "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", 90 | "php": "^7.2.5 || ^8.0", 91 | "psr/http-client": "^1.0", 92 | "symfony/deprecation-contracts": "^2.2 || ^3.0" 93 | }, 94 | "provide": { 95 | "psr/http-client-implementation": "1.0" 96 | }, 97 | "require-dev": { 98 | "bamarni/composer-bin-plugin": "^1.8.1", 99 | "ext-curl": "*", 100 | "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", 101 | "php-http/message-factory": "^1.1", 102 | "phpunit/phpunit": "^8.5.29 || ^9.5.23", 103 | "psr/log": "^1.1 || ^2.0 || ^3.0" 104 | }, 105 | "suggest": { 106 | "ext-curl": "Required for CURL handler support", 107 | "ext-intl": "Required for Internationalized Domain Name (IDN) support", 108 | "psr/log": "Required for using the Log middleware" 109 | }, 110 | "type": "library", 111 | "extra": { 112 | "bamarni-bin": { 113 | "bin-links": true, 114 | "forward-command": false 115 | } 116 | }, 117 | "autoload": { 118 | "files": [ 119 | "src/functions_include.php" 120 | ], 121 | "psr-4": { 122 | "GuzzleHttp\\": "src/" 123 | } 124 | }, 125 | "notification-url": "https://packagist.org/downloads/", 126 | "license": [ 127 | "MIT" 128 | ], 129 | "authors": [ 130 | { 131 | "name": "Graham Campbell", 132 | "email": "hello@gjcampbell.co.uk", 133 | "homepage": "https://github.com/GrahamCampbell" 134 | }, 135 | { 136 | "name": "Michael Dowling", 137 | "email": "mtdowling@gmail.com", 138 | "homepage": "https://github.com/mtdowling" 139 | }, 140 | { 141 | "name": "Jeremy Lindblom", 142 | "email": "jeremeamia@gmail.com", 143 | "homepage": "https://github.com/jeremeamia" 144 | }, 145 | { 146 | "name": "George Mponos", 147 | "email": "gmponos@gmail.com", 148 | "homepage": "https://github.com/gmponos" 149 | }, 150 | { 151 | "name": "Tobias Nyholm", 152 | "email": "tobias.nyholm@gmail.com", 153 | "homepage": "https://github.com/Nyholm" 154 | }, 155 | { 156 | "name": "Márk Sági-Kazár", 157 | "email": "mark.sagikazar@gmail.com", 158 | "homepage": "https://github.com/sagikazarmark" 159 | }, 160 | { 161 | "name": "Tobias Schultze", 162 | "email": "webmaster@tubo-world.de", 163 | "homepage": "https://github.com/Tobion" 164 | } 165 | ], 166 | "description": "Guzzle is a PHP HTTP client library", 167 | "keywords": [ 168 | "client", 169 | "curl", 170 | "framework", 171 | "http", 172 | "http client", 173 | "psr-18", 174 | "psr-7", 175 | "rest", 176 | "web service" 177 | ], 178 | "support": { 179 | "issues": "https://github.com/guzzle/guzzle/issues", 180 | "source": "https://github.com/guzzle/guzzle/tree/7.7.0" 181 | }, 182 | "funding": [ 183 | { 184 | "url": "https://github.com/GrahamCampbell", 185 | "type": "github" 186 | }, 187 | { 188 | "url": "https://github.com/Nyholm", 189 | "type": "github" 190 | }, 191 | { 192 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", 193 | "type": "tidelift" 194 | } 195 | ], 196 | "time": "2023-05-21T14:04:53+00:00" 197 | }, 198 | { 199 | "name": "guzzlehttp/promises", 200 | "version": "2.0.0", 201 | "source": { 202 | "type": "git", 203 | "url": "https://github.com/guzzle/promises.git", 204 | "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6" 205 | }, 206 | "dist": { 207 | "type": "zip", 208 | "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6", 209 | "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6", 210 | "shasum": "" 211 | }, 212 | "require": { 213 | "php": "^7.2.5 || ^8.0" 214 | }, 215 | "require-dev": { 216 | "bamarni/composer-bin-plugin": "^1.8.1", 217 | "phpunit/phpunit": "^8.5.29 || ^9.5.23" 218 | }, 219 | "type": "library", 220 | "extra": { 221 | "bamarni-bin": { 222 | "bin-links": true, 223 | "forward-command": false 224 | } 225 | }, 226 | "autoload": { 227 | "psr-4": { 228 | "GuzzleHttp\\Promise\\": "src/" 229 | } 230 | }, 231 | "notification-url": "https://packagist.org/downloads/", 232 | "license": [ 233 | "MIT" 234 | ], 235 | "authors": [ 236 | { 237 | "name": "Graham Campbell", 238 | "email": "hello@gjcampbell.co.uk", 239 | "homepage": "https://github.com/GrahamCampbell" 240 | }, 241 | { 242 | "name": "Michael Dowling", 243 | "email": "mtdowling@gmail.com", 244 | "homepage": "https://github.com/mtdowling" 245 | }, 246 | { 247 | "name": "Tobias Nyholm", 248 | "email": "tobias.nyholm@gmail.com", 249 | "homepage": "https://github.com/Nyholm" 250 | }, 251 | { 252 | "name": "Tobias Schultze", 253 | "email": "webmaster@tubo-world.de", 254 | "homepage": "https://github.com/Tobion" 255 | } 256 | ], 257 | "description": "Guzzle promises library", 258 | "keywords": [ 259 | "promise" 260 | ], 261 | "support": { 262 | "issues": "https://github.com/guzzle/promises/issues", 263 | "source": "https://github.com/guzzle/promises/tree/2.0.0" 264 | }, 265 | "funding": [ 266 | { 267 | "url": "https://github.com/GrahamCampbell", 268 | "type": "github" 269 | }, 270 | { 271 | "url": "https://github.com/Nyholm", 272 | "type": "github" 273 | }, 274 | { 275 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", 276 | "type": "tidelift" 277 | } 278 | ], 279 | "time": "2023-05-21T13:50:22+00:00" 280 | }, 281 | { 282 | "name": "guzzlehttp/psr7", 283 | "version": "2.5.0", 284 | "source": { 285 | "type": "git", 286 | "url": "https://github.com/guzzle/psr7.git", 287 | "reference": "b635f279edd83fc275f822a1188157ffea568ff6" 288 | }, 289 | "dist": { 290 | "type": "zip", 291 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", 292 | "reference": "b635f279edd83fc275f822a1188157ffea568ff6", 293 | "shasum": "" 294 | }, 295 | "require": { 296 | "php": "^7.2.5 || ^8.0", 297 | "psr/http-factory": "^1.0", 298 | "psr/http-message": "^1.1 || ^2.0", 299 | "ralouphie/getallheaders": "^3.0" 300 | }, 301 | "provide": { 302 | "psr/http-factory-implementation": "1.0", 303 | "psr/http-message-implementation": "1.0" 304 | }, 305 | "require-dev": { 306 | "bamarni/composer-bin-plugin": "^1.8.1", 307 | "http-interop/http-factory-tests": "^0.9", 308 | "phpunit/phpunit": "^8.5.29 || ^9.5.23" 309 | }, 310 | "suggest": { 311 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" 312 | }, 313 | "type": "library", 314 | "extra": { 315 | "bamarni-bin": { 316 | "bin-links": true, 317 | "forward-command": false 318 | } 319 | }, 320 | "autoload": { 321 | "psr-4": { 322 | "GuzzleHttp\\Psr7\\": "src/" 323 | } 324 | }, 325 | "notification-url": "https://packagist.org/downloads/", 326 | "license": [ 327 | "MIT" 328 | ], 329 | "authors": [ 330 | { 331 | "name": "Graham Campbell", 332 | "email": "hello@gjcampbell.co.uk", 333 | "homepage": "https://github.com/GrahamCampbell" 334 | }, 335 | { 336 | "name": "Michael Dowling", 337 | "email": "mtdowling@gmail.com", 338 | "homepage": "https://github.com/mtdowling" 339 | }, 340 | { 341 | "name": "George Mponos", 342 | "email": "gmponos@gmail.com", 343 | "homepage": "https://github.com/gmponos" 344 | }, 345 | { 346 | "name": "Tobias Nyholm", 347 | "email": "tobias.nyholm@gmail.com", 348 | "homepage": "https://github.com/Nyholm" 349 | }, 350 | { 351 | "name": "Márk Sági-Kazár", 352 | "email": "mark.sagikazar@gmail.com", 353 | "homepage": "https://github.com/sagikazarmark" 354 | }, 355 | { 356 | "name": "Tobias Schultze", 357 | "email": "webmaster@tubo-world.de", 358 | "homepage": "https://github.com/Tobion" 359 | }, 360 | { 361 | "name": "Márk Sági-Kazár", 362 | "email": "mark.sagikazar@gmail.com", 363 | "homepage": "https://sagikazarmark.hu" 364 | } 365 | ], 366 | "description": "PSR-7 message implementation that also provides common utility methods", 367 | "keywords": [ 368 | "http", 369 | "message", 370 | "psr-7", 371 | "request", 372 | "response", 373 | "stream", 374 | "uri", 375 | "url" 376 | ], 377 | "support": { 378 | "issues": "https://github.com/guzzle/psr7/issues", 379 | "source": "https://github.com/guzzle/psr7/tree/2.5.0" 380 | }, 381 | "funding": [ 382 | { 383 | "url": "https://github.com/GrahamCampbell", 384 | "type": "github" 385 | }, 386 | { 387 | "url": "https://github.com/Nyholm", 388 | "type": "github" 389 | }, 390 | { 391 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", 392 | "type": "tidelift" 393 | } 394 | ], 395 | "time": "2023-04-17T16:11:26+00:00" 396 | }, 397 | { 398 | "name": "league/oauth2-client", 399 | "version": "2.7.0", 400 | "source": { 401 | "type": "git", 402 | "url": "https://github.com/thephpleague/oauth2-client.git", 403 | "reference": "160d6274b03562ebeb55ed18399281d8118b76c8" 404 | }, 405 | "dist": { 406 | "type": "zip", 407 | "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/160d6274b03562ebeb55ed18399281d8118b76c8", 408 | "reference": "160d6274b03562ebeb55ed18399281d8118b76c8", 409 | "shasum": "" 410 | }, 411 | "require": { 412 | "guzzlehttp/guzzle": "^6.0 || ^7.0", 413 | "paragonie/random_compat": "^1 || ^2 || ^9.99", 414 | "php": "^5.6 || ^7.0 || ^8.0" 415 | }, 416 | "require-dev": { 417 | "mockery/mockery": "^1.3.5", 418 | "php-parallel-lint/php-parallel-lint": "^1.3.1", 419 | "phpunit/phpunit": "^5.7 || ^6.0 || ^9.5", 420 | "squizlabs/php_codesniffer": "^2.3 || ^3.0" 421 | }, 422 | "type": "library", 423 | "extra": { 424 | "branch-alias": { 425 | "dev-2.x": "2.0.x-dev" 426 | } 427 | }, 428 | "autoload": { 429 | "psr-4": { 430 | "League\\OAuth2\\Client\\": "src/" 431 | } 432 | }, 433 | "notification-url": "https://packagist.org/downloads/", 434 | "license": [ 435 | "MIT" 436 | ], 437 | "authors": [ 438 | { 439 | "name": "Alex Bilbie", 440 | "email": "hello@alexbilbie.com", 441 | "homepage": "http://www.alexbilbie.com", 442 | "role": "Developer" 443 | }, 444 | { 445 | "name": "Woody Gilk", 446 | "homepage": "https://github.com/shadowhand", 447 | "role": "Contributor" 448 | } 449 | ], 450 | "description": "OAuth 2.0 Client Library", 451 | "keywords": [ 452 | "Authentication", 453 | "SSO", 454 | "authorization", 455 | "identity", 456 | "idp", 457 | "oauth", 458 | "oauth2", 459 | "single sign on" 460 | ], 461 | "support": { 462 | "issues": "https://github.com/thephpleague/oauth2-client/issues", 463 | "source": "https://github.com/thephpleague/oauth2-client/tree/2.7.0" 464 | }, 465 | "time": "2023-04-16T18:19:15+00:00" 466 | }, 467 | { 468 | "name": "paragonie/random_compat", 469 | "version": "v9.99.100", 470 | "source": { 471 | "type": "git", 472 | "url": "https://github.com/paragonie/random_compat.git", 473 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" 474 | }, 475 | "dist": { 476 | "type": "zip", 477 | "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", 478 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", 479 | "shasum": "" 480 | }, 481 | "require": { 482 | "php": ">= 7" 483 | }, 484 | "require-dev": { 485 | "phpunit/phpunit": "4.*|5.*", 486 | "vimeo/psalm": "^1" 487 | }, 488 | "suggest": { 489 | "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." 490 | }, 491 | "type": "library", 492 | "notification-url": "https://packagist.org/downloads/", 493 | "license": [ 494 | "MIT" 495 | ], 496 | "authors": [ 497 | { 498 | "name": "Paragon Initiative Enterprises", 499 | "email": "security@paragonie.com", 500 | "homepage": "https://paragonie.com" 501 | } 502 | ], 503 | "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", 504 | "keywords": [ 505 | "csprng", 506 | "polyfill", 507 | "pseudorandom", 508 | "random" 509 | ], 510 | "support": { 511 | "email": "info@paragonie.com", 512 | "issues": "https://github.com/paragonie/random_compat/issues", 513 | "source": "https://github.com/paragonie/random_compat" 514 | }, 515 | "time": "2020-10-15T08:29:30+00:00" 516 | }, 517 | { 518 | "name": "psr/http-client", 519 | "version": "1.0.2", 520 | "source": { 521 | "type": "git", 522 | "url": "https://github.com/php-fig/http-client.git", 523 | "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" 524 | }, 525 | "dist": { 526 | "type": "zip", 527 | "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", 528 | "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", 529 | "shasum": "" 530 | }, 531 | "require": { 532 | "php": "^7.0 || ^8.0", 533 | "psr/http-message": "^1.0 || ^2.0" 534 | }, 535 | "type": "library", 536 | "extra": { 537 | "branch-alias": { 538 | "dev-master": "1.0.x-dev" 539 | } 540 | }, 541 | "autoload": { 542 | "psr-4": { 543 | "Psr\\Http\\Client\\": "src/" 544 | } 545 | }, 546 | "notification-url": "https://packagist.org/downloads/", 547 | "license": [ 548 | "MIT" 549 | ], 550 | "authors": [ 551 | { 552 | "name": "PHP-FIG", 553 | "homepage": "https://www.php-fig.org/" 554 | } 555 | ], 556 | "description": "Common interface for HTTP clients", 557 | "homepage": "https://github.com/php-fig/http-client", 558 | "keywords": [ 559 | "http", 560 | "http-client", 561 | "psr", 562 | "psr-18" 563 | ], 564 | "support": { 565 | "source": "https://github.com/php-fig/http-client/tree/1.0.2" 566 | }, 567 | "time": "2023-04-10T20:12:12+00:00" 568 | }, 569 | { 570 | "name": "psr/http-factory", 571 | "version": "1.0.2", 572 | "source": { 573 | "type": "git", 574 | "url": "https://github.com/php-fig/http-factory.git", 575 | "reference": "e616d01114759c4c489f93b099585439f795fe35" 576 | }, 577 | "dist": { 578 | "type": "zip", 579 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", 580 | "reference": "e616d01114759c4c489f93b099585439f795fe35", 581 | "shasum": "" 582 | }, 583 | "require": { 584 | "php": ">=7.0.0", 585 | "psr/http-message": "^1.0 || ^2.0" 586 | }, 587 | "type": "library", 588 | "extra": { 589 | "branch-alias": { 590 | "dev-master": "1.0.x-dev" 591 | } 592 | }, 593 | "autoload": { 594 | "psr-4": { 595 | "Psr\\Http\\Message\\": "src/" 596 | } 597 | }, 598 | "notification-url": "https://packagist.org/downloads/", 599 | "license": [ 600 | "MIT" 601 | ], 602 | "authors": [ 603 | { 604 | "name": "PHP-FIG", 605 | "homepage": "https://www.php-fig.org/" 606 | } 607 | ], 608 | "description": "Common interfaces for PSR-7 HTTP message factories", 609 | "keywords": [ 610 | "factory", 611 | "http", 612 | "message", 613 | "psr", 614 | "psr-17", 615 | "psr-7", 616 | "request", 617 | "response" 618 | ], 619 | "support": { 620 | "source": "https://github.com/php-fig/http-factory/tree/1.0.2" 621 | }, 622 | "time": "2023-04-10T20:10:41+00:00" 623 | }, 624 | { 625 | "name": "psr/http-message", 626 | "version": "2.0", 627 | "source": { 628 | "type": "git", 629 | "url": "https://github.com/php-fig/http-message.git", 630 | "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" 631 | }, 632 | "dist": { 633 | "type": "zip", 634 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", 635 | "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", 636 | "shasum": "" 637 | }, 638 | "require": { 639 | "php": "^7.2 || ^8.0" 640 | }, 641 | "type": "library", 642 | "extra": { 643 | "branch-alias": { 644 | "dev-master": "2.0.x-dev" 645 | } 646 | }, 647 | "autoload": { 648 | "psr-4": { 649 | "Psr\\Http\\Message\\": "src/" 650 | } 651 | }, 652 | "notification-url": "https://packagist.org/downloads/", 653 | "license": [ 654 | "MIT" 655 | ], 656 | "authors": [ 657 | { 658 | "name": "PHP-FIG", 659 | "homepage": "https://www.php-fig.org/" 660 | } 661 | ], 662 | "description": "Common interface for HTTP messages", 663 | "homepage": "https://github.com/php-fig/http-message", 664 | "keywords": [ 665 | "http", 666 | "http-message", 667 | "psr", 668 | "psr-7", 669 | "request", 670 | "response" 671 | ], 672 | "support": { 673 | "source": "https://github.com/php-fig/http-message/tree/2.0" 674 | }, 675 | "time": "2023-04-04T09:54:51+00:00" 676 | }, 677 | { 678 | "name": "ralouphie/getallheaders", 679 | "version": "3.0.3", 680 | "source": { 681 | "type": "git", 682 | "url": "https://github.com/ralouphie/getallheaders.git", 683 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 684 | }, 685 | "dist": { 686 | "type": "zip", 687 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 688 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 689 | "shasum": "" 690 | }, 691 | "require": { 692 | "php": ">=5.6" 693 | }, 694 | "require-dev": { 695 | "php-coveralls/php-coveralls": "^2.1", 696 | "phpunit/phpunit": "^5 || ^6.5" 697 | }, 698 | "type": "library", 699 | "autoload": { 700 | "files": [ 701 | "src/getallheaders.php" 702 | ] 703 | }, 704 | "notification-url": "https://packagist.org/downloads/", 705 | "license": [ 706 | "MIT" 707 | ], 708 | "authors": [ 709 | { 710 | "name": "Ralph Khattar", 711 | "email": "ralph.khattar@gmail.com" 712 | } 713 | ], 714 | "description": "A polyfill for getallheaders.", 715 | "support": { 716 | "issues": "https://github.com/ralouphie/getallheaders/issues", 717 | "source": "https://github.com/ralouphie/getallheaders/tree/develop" 718 | }, 719 | "time": "2019-03-08T08:55:37+00:00" 720 | }, 721 | { 722 | "name": "symfony/deprecation-contracts", 723 | "version": "v3.2.1", 724 | "source": { 725 | "type": "git", 726 | "url": "https://github.com/symfony/deprecation-contracts.git", 727 | "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" 728 | }, 729 | "dist": { 730 | "type": "zip", 731 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", 732 | "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", 733 | "shasum": "" 734 | }, 735 | "require": { 736 | "php": ">=8.1" 737 | }, 738 | "type": "library", 739 | "extra": { 740 | "branch-alias": { 741 | "dev-main": "3.3-dev" 742 | }, 743 | "thanks": { 744 | "name": "symfony/contracts", 745 | "url": "https://github.com/symfony/contracts" 746 | } 747 | }, 748 | "autoload": { 749 | "files": [ 750 | "function.php" 751 | ] 752 | }, 753 | "notification-url": "https://packagist.org/downloads/", 754 | "license": [ 755 | "MIT" 756 | ], 757 | "authors": [ 758 | { 759 | "name": "Nicolas Grekas", 760 | "email": "p@tchwork.com" 761 | }, 762 | { 763 | "name": "Symfony Community", 764 | "homepage": "https://symfony.com/contributors" 765 | } 766 | ], 767 | "description": "A generic function and convention to trigger deprecation notices", 768 | "homepage": "https://symfony.com", 769 | "support": { 770 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" 771 | }, 772 | "funding": [ 773 | { 774 | "url": "https://symfony.com/sponsor", 775 | "type": "custom" 776 | }, 777 | { 778 | "url": "https://github.com/fabpot", 779 | "type": "github" 780 | }, 781 | { 782 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 783 | "type": "tidelift" 784 | } 785 | ], 786 | "time": "2023-03-01T10:25:55+00:00" 787 | } 788 | ], 789 | "packages-dev": [ 790 | { 791 | "name": "doctrine/instantiator", 792 | "version": "2.0.0", 793 | "source": { 794 | "type": "git", 795 | "url": "https://github.com/doctrine/instantiator.git", 796 | "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" 797 | }, 798 | "dist": { 799 | "type": "zip", 800 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", 801 | "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", 802 | "shasum": "" 803 | }, 804 | "require": { 805 | "php": "^8.1" 806 | }, 807 | "require-dev": { 808 | "doctrine/coding-standard": "^11", 809 | "ext-pdo": "*", 810 | "ext-phar": "*", 811 | "phpbench/phpbench": "^1.2", 812 | "phpstan/phpstan": "^1.9.4", 813 | "phpstan/phpstan-phpunit": "^1.3", 814 | "phpunit/phpunit": "^9.5.27", 815 | "vimeo/psalm": "^5.4" 816 | }, 817 | "type": "library", 818 | "autoload": { 819 | "psr-4": { 820 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 821 | } 822 | }, 823 | "notification-url": "https://packagist.org/downloads/", 824 | "license": [ 825 | "MIT" 826 | ], 827 | "authors": [ 828 | { 829 | "name": "Marco Pivetta", 830 | "email": "ocramius@gmail.com", 831 | "homepage": "https://ocramius.github.io/" 832 | } 833 | ], 834 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 835 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 836 | "keywords": [ 837 | "constructor", 838 | "instantiate" 839 | ], 840 | "support": { 841 | "issues": "https://github.com/doctrine/instantiator/issues", 842 | "source": "https://github.com/doctrine/instantiator/tree/2.0.0" 843 | }, 844 | "funding": [ 845 | { 846 | "url": "https://www.doctrine-project.org/sponsorship.html", 847 | "type": "custom" 848 | }, 849 | { 850 | "url": "https://www.patreon.com/phpdoctrine", 851 | "type": "patreon" 852 | }, 853 | { 854 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 855 | "type": "tidelift" 856 | } 857 | ], 858 | "time": "2022-12-30T00:23:10+00:00" 859 | }, 860 | { 861 | "name": "hamcrest/hamcrest-php", 862 | "version": "v2.0.1", 863 | "source": { 864 | "type": "git", 865 | "url": "https://github.com/hamcrest/hamcrest-php.git", 866 | "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" 867 | }, 868 | "dist": { 869 | "type": "zip", 870 | "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", 871 | "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", 872 | "shasum": "" 873 | }, 874 | "require": { 875 | "php": "^5.3|^7.0|^8.0" 876 | }, 877 | "replace": { 878 | "cordoval/hamcrest-php": "*", 879 | "davedevelopment/hamcrest-php": "*", 880 | "kodova/hamcrest-php": "*" 881 | }, 882 | "require-dev": { 883 | "phpunit/php-file-iterator": "^1.4 || ^2.0", 884 | "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" 885 | }, 886 | "type": "library", 887 | "extra": { 888 | "branch-alias": { 889 | "dev-master": "2.1-dev" 890 | } 891 | }, 892 | "autoload": { 893 | "classmap": [ 894 | "hamcrest" 895 | ] 896 | }, 897 | "notification-url": "https://packagist.org/downloads/", 898 | "license": [ 899 | "BSD-3-Clause" 900 | ], 901 | "description": "This is the PHP port of Hamcrest Matchers", 902 | "keywords": [ 903 | "test" 904 | ], 905 | "support": { 906 | "issues": "https://github.com/hamcrest/hamcrest-php/issues", 907 | "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" 908 | }, 909 | "time": "2020-07-09T08:09:16+00:00" 910 | }, 911 | { 912 | "name": "mockery/mockery", 913 | "version": "1.5.1", 914 | "source": { 915 | "type": "git", 916 | "url": "https://github.com/mockery/mockery.git", 917 | "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e" 918 | }, 919 | "dist": { 920 | "type": "zip", 921 | "url": "https://api.github.com/repos/mockery/mockery/zipball/e92dcc83d5a51851baf5f5591d32cb2b16e3684e", 922 | "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e", 923 | "shasum": "" 924 | }, 925 | "require": { 926 | "hamcrest/hamcrest-php": "^2.0.1", 927 | "lib-pcre": ">=7.0", 928 | "php": "^7.3 || ^8.0" 929 | }, 930 | "conflict": { 931 | "phpunit/phpunit": "<8.0" 932 | }, 933 | "require-dev": { 934 | "phpunit/phpunit": "^8.5 || ^9.3" 935 | }, 936 | "type": "library", 937 | "extra": { 938 | "branch-alias": { 939 | "dev-master": "1.4.x-dev" 940 | } 941 | }, 942 | "autoload": { 943 | "psr-0": { 944 | "Mockery": "library/" 945 | } 946 | }, 947 | "notification-url": "https://packagist.org/downloads/", 948 | "license": [ 949 | "BSD-3-Clause" 950 | ], 951 | "authors": [ 952 | { 953 | "name": "Pádraic Brady", 954 | "email": "padraic.brady@gmail.com", 955 | "homepage": "http://blog.astrumfutura.com" 956 | }, 957 | { 958 | "name": "Dave Marshall", 959 | "email": "dave.marshall@atstsolutions.co.uk", 960 | "homepage": "http://davedevelopment.co.uk" 961 | } 962 | ], 963 | "description": "Mockery is a simple yet flexible PHP mock object framework", 964 | "homepage": "https://github.com/mockery/mockery", 965 | "keywords": [ 966 | "BDD", 967 | "TDD", 968 | "library", 969 | "mock", 970 | "mock objects", 971 | "mockery", 972 | "stub", 973 | "test", 974 | "test double", 975 | "testing" 976 | ], 977 | "support": { 978 | "issues": "https://github.com/mockery/mockery/issues", 979 | "source": "https://github.com/mockery/mockery/tree/1.5.1" 980 | }, 981 | "time": "2022-09-07T15:32:08+00:00" 982 | }, 983 | { 984 | "name": "myclabs/deep-copy", 985 | "version": "1.11.1", 986 | "source": { 987 | "type": "git", 988 | "url": "https://github.com/myclabs/DeepCopy.git", 989 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" 990 | }, 991 | "dist": { 992 | "type": "zip", 993 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 994 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 995 | "shasum": "" 996 | }, 997 | "require": { 998 | "php": "^7.1 || ^8.0" 999 | }, 1000 | "conflict": { 1001 | "doctrine/collections": "<1.6.8", 1002 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 1003 | }, 1004 | "require-dev": { 1005 | "doctrine/collections": "^1.6.8", 1006 | "doctrine/common": "^2.13.3 || ^3.2.2", 1007 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 1008 | }, 1009 | "type": "library", 1010 | "autoload": { 1011 | "files": [ 1012 | "src/DeepCopy/deep_copy.php" 1013 | ], 1014 | "psr-4": { 1015 | "DeepCopy\\": "src/DeepCopy/" 1016 | } 1017 | }, 1018 | "notification-url": "https://packagist.org/downloads/", 1019 | "license": [ 1020 | "MIT" 1021 | ], 1022 | "description": "Create deep copies (clones) of your objects", 1023 | "keywords": [ 1024 | "clone", 1025 | "copy", 1026 | "duplicate", 1027 | "object", 1028 | "object graph" 1029 | ], 1030 | "support": { 1031 | "issues": "https://github.com/myclabs/DeepCopy/issues", 1032 | "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" 1033 | }, 1034 | "funding": [ 1035 | { 1036 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 1037 | "type": "tidelift" 1038 | } 1039 | ], 1040 | "time": "2023-03-08T13:26:56+00:00" 1041 | }, 1042 | { 1043 | "name": "nikic/php-parser", 1044 | "version": "v4.15.5", 1045 | "source": { 1046 | "type": "git", 1047 | "url": "https://github.com/nikic/PHP-Parser.git", 1048 | "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e" 1049 | }, 1050 | "dist": { 1051 | "type": "zip", 1052 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e", 1053 | "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e", 1054 | "shasum": "" 1055 | }, 1056 | "require": { 1057 | "ext-tokenizer": "*", 1058 | "php": ">=7.0" 1059 | }, 1060 | "require-dev": { 1061 | "ircmaxell/php-yacc": "^0.0.7", 1062 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 1063 | }, 1064 | "bin": [ 1065 | "bin/php-parse" 1066 | ], 1067 | "type": "library", 1068 | "extra": { 1069 | "branch-alias": { 1070 | "dev-master": "4.9-dev" 1071 | } 1072 | }, 1073 | "autoload": { 1074 | "psr-4": { 1075 | "PhpParser\\": "lib/PhpParser" 1076 | } 1077 | }, 1078 | "notification-url": "https://packagist.org/downloads/", 1079 | "license": [ 1080 | "BSD-3-Clause" 1081 | ], 1082 | "authors": [ 1083 | { 1084 | "name": "Nikita Popov" 1085 | } 1086 | ], 1087 | "description": "A PHP parser written in PHP", 1088 | "keywords": [ 1089 | "parser", 1090 | "php" 1091 | ], 1092 | "support": { 1093 | "issues": "https://github.com/nikic/PHP-Parser/issues", 1094 | "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5" 1095 | }, 1096 | "time": "2023-05-19T20:20:00+00:00" 1097 | }, 1098 | { 1099 | "name": "phar-io/manifest", 1100 | "version": "2.0.3", 1101 | "source": { 1102 | "type": "git", 1103 | "url": "https://github.com/phar-io/manifest.git", 1104 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53" 1105 | }, 1106 | "dist": { 1107 | "type": "zip", 1108 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", 1109 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53", 1110 | "shasum": "" 1111 | }, 1112 | "require": { 1113 | "ext-dom": "*", 1114 | "ext-phar": "*", 1115 | "ext-xmlwriter": "*", 1116 | "phar-io/version": "^3.0.1", 1117 | "php": "^7.2 || ^8.0" 1118 | }, 1119 | "type": "library", 1120 | "extra": { 1121 | "branch-alias": { 1122 | "dev-master": "2.0.x-dev" 1123 | } 1124 | }, 1125 | "autoload": { 1126 | "classmap": [ 1127 | "src/" 1128 | ] 1129 | }, 1130 | "notification-url": "https://packagist.org/downloads/", 1131 | "license": [ 1132 | "BSD-3-Clause" 1133 | ], 1134 | "authors": [ 1135 | { 1136 | "name": "Arne Blankerts", 1137 | "email": "arne@blankerts.de", 1138 | "role": "Developer" 1139 | }, 1140 | { 1141 | "name": "Sebastian Heuer", 1142 | "email": "sebastian@phpeople.de", 1143 | "role": "Developer" 1144 | }, 1145 | { 1146 | "name": "Sebastian Bergmann", 1147 | "email": "sebastian@phpunit.de", 1148 | "role": "Developer" 1149 | } 1150 | ], 1151 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 1152 | "support": { 1153 | "issues": "https://github.com/phar-io/manifest/issues", 1154 | "source": "https://github.com/phar-io/manifest/tree/2.0.3" 1155 | }, 1156 | "time": "2021-07-20T11:28:43+00:00" 1157 | }, 1158 | { 1159 | "name": "phar-io/version", 1160 | "version": "3.2.1", 1161 | "source": { 1162 | "type": "git", 1163 | "url": "https://github.com/phar-io/version.git", 1164 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 1165 | }, 1166 | "dist": { 1167 | "type": "zip", 1168 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 1169 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 1170 | "shasum": "" 1171 | }, 1172 | "require": { 1173 | "php": "^7.2 || ^8.0" 1174 | }, 1175 | "type": "library", 1176 | "autoload": { 1177 | "classmap": [ 1178 | "src/" 1179 | ] 1180 | }, 1181 | "notification-url": "https://packagist.org/downloads/", 1182 | "license": [ 1183 | "BSD-3-Clause" 1184 | ], 1185 | "authors": [ 1186 | { 1187 | "name": "Arne Blankerts", 1188 | "email": "arne@blankerts.de", 1189 | "role": "Developer" 1190 | }, 1191 | { 1192 | "name": "Sebastian Heuer", 1193 | "email": "sebastian@phpeople.de", 1194 | "role": "Developer" 1195 | }, 1196 | { 1197 | "name": "Sebastian Bergmann", 1198 | "email": "sebastian@phpunit.de", 1199 | "role": "Developer" 1200 | } 1201 | ], 1202 | "description": "Library for handling version information and constraints", 1203 | "support": { 1204 | "issues": "https://github.com/phar-io/version/issues", 1205 | "source": "https://github.com/phar-io/version/tree/3.2.1" 1206 | }, 1207 | "time": "2022-02-21T01:04:05+00:00" 1208 | }, 1209 | { 1210 | "name": "phpunit/php-code-coverage", 1211 | "version": "9.2.26", 1212 | "source": { 1213 | "type": "git", 1214 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 1215 | "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" 1216 | }, 1217 | "dist": { 1218 | "type": "zip", 1219 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", 1220 | "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", 1221 | "shasum": "" 1222 | }, 1223 | "require": { 1224 | "ext-dom": "*", 1225 | "ext-libxml": "*", 1226 | "ext-xmlwriter": "*", 1227 | "nikic/php-parser": "^4.15", 1228 | "php": ">=7.3", 1229 | "phpunit/php-file-iterator": "^3.0.3", 1230 | "phpunit/php-text-template": "^2.0.2", 1231 | "sebastian/code-unit-reverse-lookup": "^2.0.2", 1232 | "sebastian/complexity": "^2.0", 1233 | "sebastian/environment": "^5.1.2", 1234 | "sebastian/lines-of-code": "^1.0.3", 1235 | "sebastian/version": "^3.0.1", 1236 | "theseer/tokenizer": "^1.2.0" 1237 | }, 1238 | "require-dev": { 1239 | "phpunit/phpunit": "^9.3" 1240 | }, 1241 | "suggest": { 1242 | "ext-pcov": "PHP extension that provides line coverage", 1243 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 1244 | }, 1245 | "type": "library", 1246 | "extra": { 1247 | "branch-alias": { 1248 | "dev-master": "9.2-dev" 1249 | } 1250 | }, 1251 | "autoload": { 1252 | "classmap": [ 1253 | "src/" 1254 | ] 1255 | }, 1256 | "notification-url": "https://packagist.org/downloads/", 1257 | "license": [ 1258 | "BSD-3-Clause" 1259 | ], 1260 | "authors": [ 1261 | { 1262 | "name": "Sebastian Bergmann", 1263 | "email": "sebastian@phpunit.de", 1264 | "role": "lead" 1265 | } 1266 | ], 1267 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 1268 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 1269 | "keywords": [ 1270 | "coverage", 1271 | "testing", 1272 | "xunit" 1273 | ], 1274 | "support": { 1275 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 1276 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" 1277 | }, 1278 | "funding": [ 1279 | { 1280 | "url": "https://github.com/sebastianbergmann", 1281 | "type": "github" 1282 | } 1283 | ], 1284 | "time": "2023-03-06T12:58:08+00:00" 1285 | }, 1286 | { 1287 | "name": "phpunit/php-file-iterator", 1288 | "version": "3.0.6", 1289 | "source": { 1290 | "type": "git", 1291 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 1292 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" 1293 | }, 1294 | "dist": { 1295 | "type": "zip", 1296 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 1297 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 1298 | "shasum": "" 1299 | }, 1300 | "require": { 1301 | "php": ">=7.3" 1302 | }, 1303 | "require-dev": { 1304 | "phpunit/phpunit": "^9.3" 1305 | }, 1306 | "type": "library", 1307 | "extra": { 1308 | "branch-alias": { 1309 | "dev-master": "3.0-dev" 1310 | } 1311 | }, 1312 | "autoload": { 1313 | "classmap": [ 1314 | "src/" 1315 | ] 1316 | }, 1317 | "notification-url": "https://packagist.org/downloads/", 1318 | "license": [ 1319 | "BSD-3-Clause" 1320 | ], 1321 | "authors": [ 1322 | { 1323 | "name": "Sebastian Bergmann", 1324 | "email": "sebastian@phpunit.de", 1325 | "role": "lead" 1326 | } 1327 | ], 1328 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 1329 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 1330 | "keywords": [ 1331 | "filesystem", 1332 | "iterator" 1333 | ], 1334 | "support": { 1335 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 1336 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" 1337 | }, 1338 | "funding": [ 1339 | { 1340 | "url": "https://github.com/sebastianbergmann", 1341 | "type": "github" 1342 | } 1343 | ], 1344 | "time": "2021-12-02T12:48:52+00:00" 1345 | }, 1346 | { 1347 | "name": "phpunit/php-invoker", 1348 | "version": "3.1.1", 1349 | "source": { 1350 | "type": "git", 1351 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 1352 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" 1353 | }, 1354 | "dist": { 1355 | "type": "zip", 1356 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 1357 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 1358 | "shasum": "" 1359 | }, 1360 | "require": { 1361 | "php": ">=7.3" 1362 | }, 1363 | "require-dev": { 1364 | "ext-pcntl": "*", 1365 | "phpunit/phpunit": "^9.3" 1366 | }, 1367 | "suggest": { 1368 | "ext-pcntl": "*" 1369 | }, 1370 | "type": "library", 1371 | "extra": { 1372 | "branch-alias": { 1373 | "dev-master": "3.1-dev" 1374 | } 1375 | }, 1376 | "autoload": { 1377 | "classmap": [ 1378 | "src/" 1379 | ] 1380 | }, 1381 | "notification-url": "https://packagist.org/downloads/", 1382 | "license": [ 1383 | "BSD-3-Clause" 1384 | ], 1385 | "authors": [ 1386 | { 1387 | "name": "Sebastian Bergmann", 1388 | "email": "sebastian@phpunit.de", 1389 | "role": "lead" 1390 | } 1391 | ], 1392 | "description": "Invoke callables with a timeout", 1393 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 1394 | "keywords": [ 1395 | "process" 1396 | ], 1397 | "support": { 1398 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 1399 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" 1400 | }, 1401 | "funding": [ 1402 | { 1403 | "url": "https://github.com/sebastianbergmann", 1404 | "type": "github" 1405 | } 1406 | ], 1407 | "time": "2020-09-28T05:58:55+00:00" 1408 | }, 1409 | { 1410 | "name": "phpunit/php-text-template", 1411 | "version": "2.0.4", 1412 | "source": { 1413 | "type": "git", 1414 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 1415 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" 1416 | }, 1417 | "dist": { 1418 | "type": "zip", 1419 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 1420 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 1421 | "shasum": "" 1422 | }, 1423 | "require": { 1424 | "php": ">=7.3" 1425 | }, 1426 | "require-dev": { 1427 | "phpunit/phpunit": "^9.3" 1428 | }, 1429 | "type": "library", 1430 | "extra": { 1431 | "branch-alias": { 1432 | "dev-master": "2.0-dev" 1433 | } 1434 | }, 1435 | "autoload": { 1436 | "classmap": [ 1437 | "src/" 1438 | ] 1439 | }, 1440 | "notification-url": "https://packagist.org/downloads/", 1441 | "license": [ 1442 | "BSD-3-Clause" 1443 | ], 1444 | "authors": [ 1445 | { 1446 | "name": "Sebastian Bergmann", 1447 | "email": "sebastian@phpunit.de", 1448 | "role": "lead" 1449 | } 1450 | ], 1451 | "description": "Simple template engine.", 1452 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 1453 | "keywords": [ 1454 | "template" 1455 | ], 1456 | "support": { 1457 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 1458 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" 1459 | }, 1460 | "funding": [ 1461 | { 1462 | "url": "https://github.com/sebastianbergmann", 1463 | "type": "github" 1464 | } 1465 | ], 1466 | "time": "2020-10-26T05:33:50+00:00" 1467 | }, 1468 | { 1469 | "name": "phpunit/php-timer", 1470 | "version": "5.0.3", 1471 | "source": { 1472 | "type": "git", 1473 | "url": "https://github.com/sebastianbergmann/php-timer.git", 1474 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" 1475 | }, 1476 | "dist": { 1477 | "type": "zip", 1478 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 1479 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 1480 | "shasum": "" 1481 | }, 1482 | "require": { 1483 | "php": ">=7.3" 1484 | }, 1485 | "require-dev": { 1486 | "phpunit/phpunit": "^9.3" 1487 | }, 1488 | "type": "library", 1489 | "extra": { 1490 | "branch-alias": { 1491 | "dev-master": "5.0-dev" 1492 | } 1493 | }, 1494 | "autoload": { 1495 | "classmap": [ 1496 | "src/" 1497 | ] 1498 | }, 1499 | "notification-url": "https://packagist.org/downloads/", 1500 | "license": [ 1501 | "BSD-3-Clause" 1502 | ], 1503 | "authors": [ 1504 | { 1505 | "name": "Sebastian Bergmann", 1506 | "email": "sebastian@phpunit.de", 1507 | "role": "lead" 1508 | } 1509 | ], 1510 | "description": "Utility class for timing", 1511 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 1512 | "keywords": [ 1513 | "timer" 1514 | ], 1515 | "support": { 1516 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 1517 | "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" 1518 | }, 1519 | "funding": [ 1520 | { 1521 | "url": "https://github.com/sebastianbergmann", 1522 | "type": "github" 1523 | } 1524 | ], 1525 | "time": "2020-10-26T13:16:10+00:00" 1526 | }, 1527 | { 1528 | "name": "phpunit/phpunit", 1529 | "version": "9.6.8", 1530 | "source": { 1531 | "type": "git", 1532 | "url": "https://github.com/sebastianbergmann/phpunit.git", 1533 | "reference": "17d621b3aff84d0c8b62539e269e87d8d5baa76e" 1534 | }, 1535 | "dist": { 1536 | "type": "zip", 1537 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/17d621b3aff84d0c8b62539e269e87d8d5baa76e", 1538 | "reference": "17d621b3aff84d0c8b62539e269e87d8d5baa76e", 1539 | "shasum": "" 1540 | }, 1541 | "require": { 1542 | "doctrine/instantiator": "^1.3.1 || ^2", 1543 | "ext-dom": "*", 1544 | "ext-json": "*", 1545 | "ext-libxml": "*", 1546 | "ext-mbstring": "*", 1547 | "ext-xml": "*", 1548 | "ext-xmlwriter": "*", 1549 | "myclabs/deep-copy": "^1.10.1", 1550 | "phar-io/manifest": "^2.0.3", 1551 | "phar-io/version": "^3.0.2", 1552 | "php": ">=7.3", 1553 | "phpunit/php-code-coverage": "^9.2.13", 1554 | "phpunit/php-file-iterator": "^3.0.5", 1555 | "phpunit/php-invoker": "^3.1.1", 1556 | "phpunit/php-text-template": "^2.0.3", 1557 | "phpunit/php-timer": "^5.0.2", 1558 | "sebastian/cli-parser": "^1.0.1", 1559 | "sebastian/code-unit": "^1.0.6", 1560 | "sebastian/comparator": "^4.0.8", 1561 | "sebastian/diff": "^4.0.3", 1562 | "sebastian/environment": "^5.1.3", 1563 | "sebastian/exporter": "^4.0.5", 1564 | "sebastian/global-state": "^5.0.1", 1565 | "sebastian/object-enumerator": "^4.0.3", 1566 | "sebastian/resource-operations": "^3.0.3", 1567 | "sebastian/type": "^3.2", 1568 | "sebastian/version": "^3.0.2" 1569 | }, 1570 | "suggest": { 1571 | "ext-soap": "To be able to generate mocks based on WSDL files", 1572 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 1573 | }, 1574 | "bin": [ 1575 | "phpunit" 1576 | ], 1577 | "type": "library", 1578 | "extra": { 1579 | "branch-alias": { 1580 | "dev-master": "9.6-dev" 1581 | } 1582 | }, 1583 | "autoload": { 1584 | "files": [ 1585 | "src/Framework/Assert/Functions.php" 1586 | ], 1587 | "classmap": [ 1588 | "src/" 1589 | ] 1590 | }, 1591 | "notification-url": "https://packagist.org/downloads/", 1592 | "license": [ 1593 | "BSD-3-Clause" 1594 | ], 1595 | "authors": [ 1596 | { 1597 | "name": "Sebastian Bergmann", 1598 | "email": "sebastian@phpunit.de", 1599 | "role": "lead" 1600 | } 1601 | ], 1602 | "description": "The PHP Unit Testing framework.", 1603 | "homepage": "https://phpunit.de/", 1604 | "keywords": [ 1605 | "phpunit", 1606 | "testing", 1607 | "xunit" 1608 | ], 1609 | "support": { 1610 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 1611 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy", 1612 | "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.8" 1613 | }, 1614 | "funding": [ 1615 | { 1616 | "url": "https://phpunit.de/sponsors.html", 1617 | "type": "custom" 1618 | }, 1619 | { 1620 | "url": "https://github.com/sebastianbergmann", 1621 | "type": "github" 1622 | }, 1623 | { 1624 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", 1625 | "type": "tidelift" 1626 | } 1627 | ], 1628 | "time": "2023-05-11T05:14:45+00:00" 1629 | }, 1630 | { 1631 | "name": "sebastian/cli-parser", 1632 | "version": "1.0.1", 1633 | "source": { 1634 | "type": "git", 1635 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 1636 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" 1637 | }, 1638 | "dist": { 1639 | "type": "zip", 1640 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", 1641 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", 1642 | "shasum": "" 1643 | }, 1644 | "require": { 1645 | "php": ">=7.3" 1646 | }, 1647 | "require-dev": { 1648 | "phpunit/phpunit": "^9.3" 1649 | }, 1650 | "type": "library", 1651 | "extra": { 1652 | "branch-alias": { 1653 | "dev-master": "1.0-dev" 1654 | } 1655 | }, 1656 | "autoload": { 1657 | "classmap": [ 1658 | "src/" 1659 | ] 1660 | }, 1661 | "notification-url": "https://packagist.org/downloads/", 1662 | "license": [ 1663 | "BSD-3-Clause" 1664 | ], 1665 | "authors": [ 1666 | { 1667 | "name": "Sebastian Bergmann", 1668 | "email": "sebastian@phpunit.de", 1669 | "role": "lead" 1670 | } 1671 | ], 1672 | "description": "Library for parsing CLI options", 1673 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 1674 | "support": { 1675 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 1676 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" 1677 | }, 1678 | "funding": [ 1679 | { 1680 | "url": "https://github.com/sebastianbergmann", 1681 | "type": "github" 1682 | } 1683 | ], 1684 | "time": "2020-09-28T06:08:49+00:00" 1685 | }, 1686 | { 1687 | "name": "sebastian/code-unit", 1688 | "version": "1.0.8", 1689 | "source": { 1690 | "type": "git", 1691 | "url": "https://github.com/sebastianbergmann/code-unit.git", 1692 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" 1693 | }, 1694 | "dist": { 1695 | "type": "zip", 1696 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", 1697 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", 1698 | "shasum": "" 1699 | }, 1700 | "require": { 1701 | "php": ">=7.3" 1702 | }, 1703 | "require-dev": { 1704 | "phpunit/phpunit": "^9.3" 1705 | }, 1706 | "type": "library", 1707 | "extra": { 1708 | "branch-alias": { 1709 | "dev-master": "1.0-dev" 1710 | } 1711 | }, 1712 | "autoload": { 1713 | "classmap": [ 1714 | "src/" 1715 | ] 1716 | }, 1717 | "notification-url": "https://packagist.org/downloads/", 1718 | "license": [ 1719 | "BSD-3-Clause" 1720 | ], 1721 | "authors": [ 1722 | { 1723 | "name": "Sebastian Bergmann", 1724 | "email": "sebastian@phpunit.de", 1725 | "role": "lead" 1726 | } 1727 | ], 1728 | "description": "Collection of value objects that represent the PHP code units", 1729 | "homepage": "https://github.com/sebastianbergmann/code-unit", 1730 | "support": { 1731 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 1732 | "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" 1733 | }, 1734 | "funding": [ 1735 | { 1736 | "url": "https://github.com/sebastianbergmann", 1737 | "type": "github" 1738 | } 1739 | ], 1740 | "time": "2020-10-26T13:08:54+00:00" 1741 | }, 1742 | { 1743 | "name": "sebastian/code-unit-reverse-lookup", 1744 | "version": "2.0.3", 1745 | "source": { 1746 | "type": "git", 1747 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1748 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" 1749 | }, 1750 | "dist": { 1751 | "type": "zip", 1752 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1753 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1754 | "shasum": "" 1755 | }, 1756 | "require": { 1757 | "php": ">=7.3" 1758 | }, 1759 | "require-dev": { 1760 | "phpunit/phpunit": "^9.3" 1761 | }, 1762 | "type": "library", 1763 | "extra": { 1764 | "branch-alias": { 1765 | "dev-master": "2.0-dev" 1766 | } 1767 | }, 1768 | "autoload": { 1769 | "classmap": [ 1770 | "src/" 1771 | ] 1772 | }, 1773 | "notification-url": "https://packagist.org/downloads/", 1774 | "license": [ 1775 | "BSD-3-Clause" 1776 | ], 1777 | "authors": [ 1778 | { 1779 | "name": "Sebastian Bergmann", 1780 | "email": "sebastian@phpunit.de" 1781 | } 1782 | ], 1783 | "description": "Looks up which function or method a line of code belongs to", 1784 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1785 | "support": { 1786 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 1787 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" 1788 | }, 1789 | "funding": [ 1790 | { 1791 | "url": "https://github.com/sebastianbergmann", 1792 | "type": "github" 1793 | } 1794 | ], 1795 | "time": "2020-09-28T05:30:19+00:00" 1796 | }, 1797 | { 1798 | "name": "sebastian/comparator", 1799 | "version": "4.0.8", 1800 | "source": { 1801 | "type": "git", 1802 | "url": "https://github.com/sebastianbergmann/comparator.git", 1803 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a" 1804 | }, 1805 | "dist": { 1806 | "type": "zip", 1807 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", 1808 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a", 1809 | "shasum": "" 1810 | }, 1811 | "require": { 1812 | "php": ">=7.3", 1813 | "sebastian/diff": "^4.0", 1814 | "sebastian/exporter": "^4.0" 1815 | }, 1816 | "require-dev": { 1817 | "phpunit/phpunit": "^9.3" 1818 | }, 1819 | "type": "library", 1820 | "extra": { 1821 | "branch-alias": { 1822 | "dev-master": "4.0-dev" 1823 | } 1824 | }, 1825 | "autoload": { 1826 | "classmap": [ 1827 | "src/" 1828 | ] 1829 | }, 1830 | "notification-url": "https://packagist.org/downloads/", 1831 | "license": [ 1832 | "BSD-3-Clause" 1833 | ], 1834 | "authors": [ 1835 | { 1836 | "name": "Sebastian Bergmann", 1837 | "email": "sebastian@phpunit.de" 1838 | }, 1839 | { 1840 | "name": "Jeff Welch", 1841 | "email": "whatthejeff@gmail.com" 1842 | }, 1843 | { 1844 | "name": "Volker Dusch", 1845 | "email": "github@wallbash.com" 1846 | }, 1847 | { 1848 | "name": "Bernhard Schussek", 1849 | "email": "bschussek@2bepublished.at" 1850 | } 1851 | ], 1852 | "description": "Provides the functionality to compare PHP values for equality", 1853 | "homepage": "https://github.com/sebastianbergmann/comparator", 1854 | "keywords": [ 1855 | "comparator", 1856 | "compare", 1857 | "equality" 1858 | ], 1859 | "support": { 1860 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1861 | "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" 1862 | }, 1863 | "funding": [ 1864 | { 1865 | "url": "https://github.com/sebastianbergmann", 1866 | "type": "github" 1867 | } 1868 | ], 1869 | "time": "2022-09-14T12:41:17+00:00" 1870 | }, 1871 | { 1872 | "name": "sebastian/complexity", 1873 | "version": "2.0.2", 1874 | "source": { 1875 | "type": "git", 1876 | "url": "https://github.com/sebastianbergmann/complexity.git", 1877 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" 1878 | }, 1879 | "dist": { 1880 | "type": "zip", 1881 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", 1882 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", 1883 | "shasum": "" 1884 | }, 1885 | "require": { 1886 | "nikic/php-parser": "^4.7", 1887 | "php": ">=7.3" 1888 | }, 1889 | "require-dev": { 1890 | "phpunit/phpunit": "^9.3" 1891 | }, 1892 | "type": "library", 1893 | "extra": { 1894 | "branch-alias": { 1895 | "dev-master": "2.0-dev" 1896 | } 1897 | }, 1898 | "autoload": { 1899 | "classmap": [ 1900 | "src/" 1901 | ] 1902 | }, 1903 | "notification-url": "https://packagist.org/downloads/", 1904 | "license": [ 1905 | "BSD-3-Clause" 1906 | ], 1907 | "authors": [ 1908 | { 1909 | "name": "Sebastian Bergmann", 1910 | "email": "sebastian@phpunit.de", 1911 | "role": "lead" 1912 | } 1913 | ], 1914 | "description": "Library for calculating the complexity of PHP code units", 1915 | "homepage": "https://github.com/sebastianbergmann/complexity", 1916 | "support": { 1917 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 1918 | "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" 1919 | }, 1920 | "funding": [ 1921 | { 1922 | "url": "https://github.com/sebastianbergmann", 1923 | "type": "github" 1924 | } 1925 | ], 1926 | "time": "2020-10-26T15:52:27+00:00" 1927 | }, 1928 | { 1929 | "name": "sebastian/diff", 1930 | "version": "4.0.5", 1931 | "source": { 1932 | "type": "git", 1933 | "url": "https://github.com/sebastianbergmann/diff.git", 1934 | "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" 1935 | }, 1936 | "dist": { 1937 | "type": "zip", 1938 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", 1939 | "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", 1940 | "shasum": "" 1941 | }, 1942 | "require": { 1943 | "php": ">=7.3" 1944 | }, 1945 | "require-dev": { 1946 | "phpunit/phpunit": "^9.3", 1947 | "symfony/process": "^4.2 || ^5" 1948 | }, 1949 | "type": "library", 1950 | "extra": { 1951 | "branch-alias": { 1952 | "dev-master": "4.0-dev" 1953 | } 1954 | }, 1955 | "autoload": { 1956 | "classmap": [ 1957 | "src/" 1958 | ] 1959 | }, 1960 | "notification-url": "https://packagist.org/downloads/", 1961 | "license": [ 1962 | "BSD-3-Clause" 1963 | ], 1964 | "authors": [ 1965 | { 1966 | "name": "Sebastian Bergmann", 1967 | "email": "sebastian@phpunit.de" 1968 | }, 1969 | { 1970 | "name": "Kore Nordmann", 1971 | "email": "mail@kore-nordmann.de" 1972 | } 1973 | ], 1974 | "description": "Diff implementation", 1975 | "homepage": "https://github.com/sebastianbergmann/diff", 1976 | "keywords": [ 1977 | "diff", 1978 | "udiff", 1979 | "unidiff", 1980 | "unified diff" 1981 | ], 1982 | "support": { 1983 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1984 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" 1985 | }, 1986 | "funding": [ 1987 | { 1988 | "url": "https://github.com/sebastianbergmann", 1989 | "type": "github" 1990 | } 1991 | ], 1992 | "time": "2023-05-07T05:35:17+00:00" 1993 | }, 1994 | { 1995 | "name": "sebastian/environment", 1996 | "version": "5.1.5", 1997 | "source": { 1998 | "type": "git", 1999 | "url": "https://github.com/sebastianbergmann/environment.git", 2000 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" 2001 | }, 2002 | "dist": { 2003 | "type": "zip", 2004 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 2005 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 2006 | "shasum": "" 2007 | }, 2008 | "require": { 2009 | "php": ">=7.3" 2010 | }, 2011 | "require-dev": { 2012 | "phpunit/phpunit": "^9.3" 2013 | }, 2014 | "suggest": { 2015 | "ext-posix": "*" 2016 | }, 2017 | "type": "library", 2018 | "extra": { 2019 | "branch-alias": { 2020 | "dev-master": "5.1-dev" 2021 | } 2022 | }, 2023 | "autoload": { 2024 | "classmap": [ 2025 | "src/" 2026 | ] 2027 | }, 2028 | "notification-url": "https://packagist.org/downloads/", 2029 | "license": [ 2030 | "BSD-3-Clause" 2031 | ], 2032 | "authors": [ 2033 | { 2034 | "name": "Sebastian Bergmann", 2035 | "email": "sebastian@phpunit.de" 2036 | } 2037 | ], 2038 | "description": "Provides functionality to handle HHVM/PHP environments", 2039 | "homepage": "http://www.github.com/sebastianbergmann/environment", 2040 | "keywords": [ 2041 | "Xdebug", 2042 | "environment", 2043 | "hhvm" 2044 | ], 2045 | "support": { 2046 | "issues": "https://github.com/sebastianbergmann/environment/issues", 2047 | "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" 2048 | }, 2049 | "funding": [ 2050 | { 2051 | "url": "https://github.com/sebastianbergmann", 2052 | "type": "github" 2053 | } 2054 | ], 2055 | "time": "2023-02-03T06:03:51+00:00" 2056 | }, 2057 | { 2058 | "name": "sebastian/exporter", 2059 | "version": "4.0.5", 2060 | "source": { 2061 | "type": "git", 2062 | "url": "https://github.com/sebastianbergmann/exporter.git", 2063 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" 2064 | }, 2065 | "dist": { 2066 | "type": "zip", 2067 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 2068 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 2069 | "shasum": "" 2070 | }, 2071 | "require": { 2072 | "php": ">=7.3", 2073 | "sebastian/recursion-context": "^4.0" 2074 | }, 2075 | "require-dev": { 2076 | "ext-mbstring": "*", 2077 | "phpunit/phpunit": "^9.3" 2078 | }, 2079 | "type": "library", 2080 | "extra": { 2081 | "branch-alias": { 2082 | "dev-master": "4.0-dev" 2083 | } 2084 | }, 2085 | "autoload": { 2086 | "classmap": [ 2087 | "src/" 2088 | ] 2089 | }, 2090 | "notification-url": "https://packagist.org/downloads/", 2091 | "license": [ 2092 | "BSD-3-Clause" 2093 | ], 2094 | "authors": [ 2095 | { 2096 | "name": "Sebastian Bergmann", 2097 | "email": "sebastian@phpunit.de" 2098 | }, 2099 | { 2100 | "name": "Jeff Welch", 2101 | "email": "whatthejeff@gmail.com" 2102 | }, 2103 | { 2104 | "name": "Volker Dusch", 2105 | "email": "github@wallbash.com" 2106 | }, 2107 | { 2108 | "name": "Adam Harvey", 2109 | "email": "aharvey@php.net" 2110 | }, 2111 | { 2112 | "name": "Bernhard Schussek", 2113 | "email": "bschussek@gmail.com" 2114 | } 2115 | ], 2116 | "description": "Provides the functionality to export PHP variables for visualization", 2117 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 2118 | "keywords": [ 2119 | "export", 2120 | "exporter" 2121 | ], 2122 | "support": { 2123 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 2124 | "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" 2125 | }, 2126 | "funding": [ 2127 | { 2128 | "url": "https://github.com/sebastianbergmann", 2129 | "type": "github" 2130 | } 2131 | ], 2132 | "time": "2022-09-14T06:03:37+00:00" 2133 | }, 2134 | { 2135 | "name": "sebastian/global-state", 2136 | "version": "5.0.5", 2137 | "source": { 2138 | "type": "git", 2139 | "url": "https://github.com/sebastianbergmann/global-state.git", 2140 | "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" 2141 | }, 2142 | "dist": { 2143 | "type": "zip", 2144 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", 2145 | "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", 2146 | "shasum": "" 2147 | }, 2148 | "require": { 2149 | "php": ">=7.3", 2150 | "sebastian/object-reflector": "^2.0", 2151 | "sebastian/recursion-context": "^4.0" 2152 | }, 2153 | "require-dev": { 2154 | "ext-dom": "*", 2155 | "phpunit/phpunit": "^9.3" 2156 | }, 2157 | "suggest": { 2158 | "ext-uopz": "*" 2159 | }, 2160 | "type": "library", 2161 | "extra": { 2162 | "branch-alias": { 2163 | "dev-master": "5.0-dev" 2164 | } 2165 | }, 2166 | "autoload": { 2167 | "classmap": [ 2168 | "src/" 2169 | ] 2170 | }, 2171 | "notification-url": "https://packagist.org/downloads/", 2172 | "license": [ 2173 | "BSD-3-Clause" 2174 | ], 2175 | "authors": [ 2176 | { 2177 | "name": "Sebastian Bergmann", 2178 | "email": "sebastian@phpunit.de" 2179 | } 2180 | ], 2181 | "description": "Snapshotting of global state", 2182 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 2183 | "keywords": [ 2184 | "global state" 2185 | ], 2186 | "support": { 2187 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 2188 | "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" 2189 | }, 2190 | "funding": [ 2191 | { 2192 | "url": "https://github.com/sebastianbergmann", 2193 | "type": "github" 2194 | } 2195 | ], 2196 | "time": "2022-02-14T08:28:10+00:00" 2197 | }, 2198 | { 2199 | "name": "sebastian/lines-of-code", 2200 | "version": "1.0.3", 2201 | "source": { 2202 | "type": "git", 2203 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 2204 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" 2205 | }, 2206 | "dist": { 2207 | "type": "zip", 2208 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", 2209 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", 2210 | "shasum": "" 2211 | }, 2212 | "require": { 2213 | "nikic/php-parser": "^4.6", 2214 | "php": ">=7.3" 2215 | }, 2216 | "require-dev": { 2217 | "phpunit/phpunit": "^9.3" 2218 | }, 2219 | "type": "library", 2220 | "extra": { 2221 | "branch-alias": { 2222 | "dev-master": "1.0-dev" 2223 | } 2224 | }, 2225 | "autoload": { 2226 | "classmap": [ 2227 | "src/" 2228 | ] 2229 | }, 2230 | "notification-url": "https://packagist.org/downloads/", 2231 | "license": [ 2232 | "BSD-3-Clause" 2233 | ], 2234 | "authors": [ 2235 | { 2236 | "name": "Sebastian Bergmann", 2237 | "email": "sebastian@phpunit.de", 2238 | "role": "lead" 2239 | } 2240 | ], 2241 | "description": "Library for counting the lines of code in PHP source code", 2242 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 2243 | "support": { 2244 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 2245 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" 2246 | }, 2247 | "funding": [ 2248 | { 2249 | "url": "https://github.com/sebastianbergmann", 2250 | "type": "github" 2251 | } 2252 | ], 2253 | "time": "2020-11-28T06:42:11+00:00" 2254 | }, 2255 | { 2256 | "name": "sebastian/object-enumerator", 2257 | "version": "4.0.4", 2258 | "source": { 2259 | "type": "git", 2260 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 2261 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" 2262 | }, 2263 | "dist": { 2264 | "type": "zip", 2265 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", 2266 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", 2267 | "shasum": "" 2268 | }, 2269 | "require": { 2270 | "php": ">=7.3", 2271 | "sebastian/object-reflector": "^2.0", 2272 | "sebastian/recursion-context": "^4.0" 2273 | }, 2274 | "require-dev": { 2275 | "phpunit/phpunit": "^9.3" 2276 | }, 2277 | "type": "library", 2278 | "extra": { 2279 | "branch-alias": { 2280 | "dev-master": "4.0-dev" 2281 | } 2282 | }, 2283 | "autoload": { 2284 | "classmap": [ 2285 | "src/" 2286 | ] 2287 | }, 2288 | "notification-url": "https://packagist.org/downloads/", 2289 | "license": [ 2290 | "BSD-3-Clause" 2291 | ], 2292 | "authors": [ 2293 | { 2294 | "name": "Sebastian Bergmann", 2295 | "email": "sebastian@phpunit.de" 2296 | } 2297 | ], 2298 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 2299 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 2300 | "support": { 2301 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 2302 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" 2303 | }, 2304 | "funding": [ 2305 | { 2306 | "url": "https://github.com/sebastianbergmann", 2307 | "type": "github" 2308 | } 2309 | ], 2310 | "time": "2020-10-26T13:12:34+00:00" 2311 | }, 2312 | { 2313 | "name": "sebastian/object-reflector", 2314 | "version": "2.0.4", 2315 | "source": { 2316 | "type": "git", 2317 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 2318 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" 2319 | }, 2320 | "dist": { 2321 | "type": "zip", 2322 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 2323 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 2324 | "shasum": "" 2325 | }, 2326 | "require": { 2327 | "php": ">=7.3" 2328 | }, 2329 | "require-dev": { 2330 | "phpunit/phpunit": "^9.3" 2331 | }, 2332 | "type": "library", 2333 | "extra": { 2334 | "branch-alias": { 2335 | "dev-master": "2.0-dev" 2336 | } 2337 | }, 2338 | "autoload": { 2339 | "classmap": [ 2340 | "src/" 2341 | ] 2342 | }, 2343 | "notification-url": "https://packagist.org/downloads/", 2344 | "license": [ 2345 | "BSD-3-Clause" 2346 | ], 2347 | "authors": [ 2348 | { 2349 | "name": "Sebastian Bergmann", 2350 | "email": "sebastian@phpunit.de" 2351 | } 2352 | ], 2353 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 2354 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 2355 | "support": { 2356 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 2357 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" 2358 | }, 2359 | "funding": [ 2360 | { 2361 | "url": "https://github.com/sebastianbergmann", 2362 | "type": "github" 2363 | } 2364 | ], 2365 | "time": "2020-10-26T13:14:26+00:00" 2366 | }, 2367 | { 2368 | "name": "sebastian/recursion-context", 2369 | "version": "4.0.5", 2370 | "source": { 2371 | "type": "git", 2372 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 2373 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" 2374 | }, 2375 | "dist": { 2376 | "type": "zip", 2377 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 2378 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 2379 | "shasum": "" 2380 | }, 2381 | "require": { 2382 | "php": ">=7.3" 2383 | }, 2384 | "require-dev": { 2385 | "phpunit/phpunit": "^9.3" 2386 | }, 2387 | "type": "library", 2388 | "extra": { 2389 | "branch-alias": { 2390 | "dev-master": "4.0-dev" 2391 | } 2392 | }, 2393 | "autoload": { 2394 | "classmap": [ 2395 | "src/" 2396 | ] 2397 | }, 2398 | "notification-url": "https://packagist.org/downloads/", 2399 | "license": [ 2400 | "BSD-3-Clause" 2401 | ], 2402 | "authors": [ 2403 | { 2404 | "name": "Sebastian Bergmann", 2405 | "email": "sebastian@phpunit.de" 2406 | }, 2407 | { 2408 | "name": "Jeff Welch", 2409 | "email": "whatthejeff@gmail.com" 2410 | }, 2411 | { 2412 | "name": "Adam Harvey", 2413 | "email": "aharvey@php.net" 2414 | } 2415 | ], 2416 | "description": "Provides functionality to recursively process PHP variables", 2417 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 2418 | "support": { 2419 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 2420 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" 2421 | }, 2422 | "funding": [ 2423 | { 2424 | "url": "https://github.com/sebastianbergmann", 2425 | "type": "github" 2426 | } 2427 | ], 2428 | "time": "2023-02-03T06:07:39+00:00" 2429 | }, 2430 | { 2431 | "name": "sebastian/resource-operations", 2432 | "version": "3.0.3", 2433 | "source": { 2434 | "type": "git", 2435 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 2436 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" 2437 | }, 2438 | "dist": { 2439 | "type": "zip", 2440 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 2441 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 2442 | "shasum": "" 2443 | }, 2444 | "require": { 2445 | "php": ">=7.3" 2446 | }, 2447 | "require-dev": { 2448 | "phpunit/phpunit": "^9.0" 2449 | }, 2450 | "type": "library", 2451 | "extra": { 2452 | "branch-alias": { 2453 | "dev-master": "3.0-dev" 2454 | } 2455 | }, 2456 | "autoload": { 2457 | "classmap": [ 2458 | "src/" 2459 | ] 2460 | }, 2461 | "notification-url": "https://packagist.org/downloads/", 2462 | "license": [ 2463 | "BSD-3-Clause" 2464 | ], 2465 | "authors": [ 2466 | { 2467 | "name": "Sebastian Bergmann", 2468 | "email": "sebastian@phpunit.de" 2469 | } 2470 | ], 2471 | "description": "Provides a list of PHP built-in functions that operate on resources", 2472 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 2473 | "support": { 2474 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 2475 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" 2476 | }, 2477 | "funding": [ 2478 | { 2479 | "url": "https://github.com/sebastianbergmann", 2480 | "type": "github" 2481 | } 2482 | ], 2483 | "time": "2020-09-28T06:45:17+00:00" 2484 | }, 2485 | { 2486 | "name": "sebastian/type", 2487 | "version": "3.2.1", 2488 | "source": { 2489 | "type": "git", 2490 | "url": "https://github.com/sebastianbergmann/type.git", 2491 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" 2492 | }, 2493 | "dist": { 2494 | "type": "zip", 2495 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 2496 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 2497 | "shasum": "" 2498 | }, 2499 | "require": { 2500 | "php": ">=7.3" 2501 | }, 2502 | "require-dev": { 2503 | "phpunit/phpunit": "^9.5" 2504 | }, 2505 | "type": "library", 2506 | "extra": { 2507 | "branch-alias": { 2508 | "dev-master": "3.2-dev" 2509 | } 2510 | }, 2511 | "autoload": { 2512 | "classmap": [ 2513 | "src/" 2514 | ] 2515 | }, 2516 | "notification-url": "https://packagist.org/downloads/", 2517 | "license": [ 2518 | "BSD-3-Clause" 2519 | ], 2520 | "authors": [ 2521 | { 2522 | "name": "Sebastian Bergmann", 2523 | "email": "sebastian@phpunit.de", 2524 | "role": "lead" 2525 | } 2526 | ], 2527 | "description": "Collection of value objects that represent the types of the PHP type system", 2528 | "homepage": "https://github.com/sebastianbergmann/type", 2529 | "support": { 2530 | "issues": "https://github.com/sebastianbergmann/type/issues", 2531 | "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" 2532 | }, 2533 | "funding": [ 2534 | { 2535 | "url": "https://github.com/sebastianbergmann", 2536 | "type": "github" 2537 | } 2538 | ], 2539 | "time": "2023-02-03T06:13:03+00:00" 2540 | }, 2541 | { 2542 | "name": "sebastian/version", 2543 | "version": "3.0.2", 2544 | "source": { 2545 | "type": "git", 2546 | "url": "https://github.com/sebastianbergmann/version.git", 2547 | "reference": "c6c1022351a901512170118436c764e473f6de8c" 2548 | }, 2549 | "dist": { 2550 | "type": "zip", 2551 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", 2552 | "reference": "c6c1022351a901512170118436c764e473f6de8c", 2553 | "shasum": "" 2554 | }, 2555 | "require": { 2556 | "php": ">=7.3" 2557 | }, 2558 | "type": "library", 2559 | "extra": { 2560 | "branch-alias": { 2561 | "dev-master": "3.0-dev" 2562 | } 2563 | }, 2564 | "autoload": { 2565 | "classmap": [ 2566 | "src/" 2567 | ] 2568 | }, 2569 | "notification-url": "https://packagist.org/downloads/", 2570 | "license": [ 2571 | "BSD-3-Clause" 2572 | ], 2573 | "authors": [ 2574 | { 2575 | "name": "Sebastian Bergmann", 2576 | "email": "sebastian@phpunit.de", 2577 | "role": "lead" 2578 | } 2579 | ], 2580 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 2581 | "homepage": "https://github.com/sebastianbergmann/version", 2582 | "support": { 2583 | "issues": "https://github.com/sebastianbergmann/version/issues", 2584 | "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" 2585 | }, 2586 | "funding": [ 2587 | { 2588 | "url": "https://github.com/sebastianbergmann", 2589 | "type": "github" 2590 | } 2591 | ], 2592 | "time": "2020-09-28T06:39:44+00:00" 2593 | }, 2594 | { 2595 | "name": "squizlabs/php_codesniffer", 2596 | "version": "3.7.2", 2597 | "source": { 2598 | "type": "git", 2599 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", 2600 | "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" 2601 | }, 2602 | "dist": { 2603 | "type": "zip", 2604 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", 2605 | "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", 2606 | "shasum": "" 2607 | }, 2608 | "require": { 2609 | "ext-simplexml": "*", 2610 | "ext-tokenizer": "*", 2611 | "ext-xmlwriter": "*", 2612 | "php": ">=5.4.0" 2613 | }, 2614 | "require-dev": { 2615 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" 2616 | }, 2617 | "bin": [ 2618 | "bin/phpcs", 2619 | "bin/phpcbf" 2620 | ], 2621 | "type": "library", 2622 | "extra": { 2623 | "branch-alias": { 2624 | "dev-master": "3.x-dev" 2625 | } 2626 | }, 2627 | "notification-url": "https://packagist.org/downloads/", 2628 | "license": [ 2629 | "BSD-3-Clause" 2630 | ], 2631 | "authors": [ 2632 | { 2633 | "name": "Greg Sherwood", 2634 | "role": "lead" 2635 | } 2636 | ], 2637 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 2638 | "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", 2639 | "keywords": [ 2640 | "phpcs", 2641 | "standards", 2642 | "static analysis" 2643 | ], 2644 | "support": { 2645 | "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", 2646 | "source": "https://github.com/squizlabs/PHP_CodeSniffer", 2647 | "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" 2648 | }, 2649 | "time": "2023-02-22T23:07:41+00:00" 2650 | }, 2651 | { 2652 | "name": "theseer/tokenizer", 2653 | "version": "1.2.1", 2654 | "source": { 2655 | "type": "git", 2656 | "url": "https://github.com/theseer/tokenizer.git", 2657 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" 2658 | }, 2659 | "dist": { 2660 | "type": "zip", 2661 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", 2662 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", 2663 | "shasum": "" 2664 | }, 2665 | "require": { 2666 | "ext-dom": "*", 2667 | "ext-tokenizer": "*", 2668 | "ext-xmlwriter": "*", 2669 | "php": "^7.2 || ^8.0" 2670 | }, 2671 | "type": "library", 2672 | "autoload": { 2673 | "classmap": [ 2674 | "src/" 2675 | ] 2676 | }, 2677 | "notification-url": "https://packagist.org/downloads/", 2678 | "license": [ 2679 | "BSD-3-Clause" 2680 | ], 2681 | "authors": [ 2682 | { 2683 | "name": "Arne Blankerts", 2684 | "email": "arne@blankerts.de", 2685 | "role": "Developer" 2686 | } 2687 | ], 2688 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2689 | "support": { 2690 | "issues": "https://github.com/theseer/tokenizer/issues", 2691 | "source": "https://github.com/theseer/tokenizer/tree/1.2.1" 2692 | }, 2693 | "funding": [ 2694 | { 2695 | "url": "https://github.com/theseer", 2696 | "type": "github" 2697 | } 2698 | ], 2699 | "time": "2021-07-28T10:34:58+00:00" 2700 | } 2701 | ], 2702 | "aliases": [], 2703 | "minimum-stability": "stable", 2704 | "stability-flags": [], 2705 | "prefer-stable": false, 2706 | "prefer-lowest": false, 2707 | "platform": { 2708 | "php": "~7.2 || ~8.0" 2709 | }, 2710 | "platform-dev": [], 2711 | "plugin-api-version": "2.3.0" 2712 | } 2713 | -------------------------------------------------------------------------------- /examples/index.php: -------------------------------------------------------------------------------- 1 | '', 9 | 'realm' => '', 10 | 'clientId' => '', 11 | 'clientSecret' => '', 12 | 'redirectUri' => '', 13 | 'encryptionAlgorithm' => null, 14 | 'encryptionKey' => null, 15 | 'encryptionKeyPath' => null 16 | ]); 17 | 18 | if (!isset($_GET['code'])) { 19 | // If we don't have an authorization code then get one 20 | $authUrl = $provider->getAuthorizationUrl(); 21 | $_SESSION['oauth2state'] = $provider->getState(); 22 | header('Location: '.$authUrl); 23 | exit; 24 | 25 | // Check given state against previously stored one to mitigate CSRF attack 26 | } elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { 27 | unset($_SESSION['oauth2state']); 28 | exit('Invalid state, make sure HTTP sessions are enabled.'); 29 | } else { 30 | // Try to get an access token (using the authorization coe grant) 31 | try { 32 | $token = $provider->getAccessToken('authorization_code', [ 33 | 'code' => $_GET['code'] 34 | ]); 35 | } catch (Exception $e) { 36 | exit('Failed to get access token: '.$e->getMessage()); 37 | } 38 | 39 | // Optional: Now you have a token you can look up a users profile data 40 | try { 41 | 42 | // We got an access token, let's now get the user's details 43 | $user = $provider->getResourceOwner($token); 44 | // Use these details to create a new profile 45 | printf('Hello %s!\n
', $user->getName()); 46 | 47 | } catch (Exception $e) { 48 | exit('Failed to get resource owner: '.$e->getMessage()); 49 | } 50 | 51 | // Use this to interact with an API on the users behalf 52 | echo $token->getToken(); 53 | } 54 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 14 | 15 | src 16 | 17 | 18 | vendor 19 | src/autoload.php 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | ./test/ 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Provider/Exception/EncryptionConfigurationException.php: -------------------------------------------------------------------------------- 1 | setEncryptionKeyPath($options['encryptionKeyPath']); 74 | unset($options['encryptionKeyPath']); 75 | } 76 | 77 | if (isset($options['version'])) { 78 | $this->setVersion($options['version']); 79 | } 80 | 81 | parent::__construct($options, $collaborators); 82 | } 83 | 84 | /** 85 | * Attempts to decrypt the given response. 86 | * 87 | * @param string|array|null $response 88 | * 89 | * @return string|array|null 90 | * @throws EncryptionConfigurationException 91 | */ 92 | public function decryptResponse($response) 93 | { 94 | if (!is_string($response)) { 95 | return $response; 96 | } 97 | 98 | if ($this->usesEncryption()) { 99 | return json_decode( 100 | json_encode( 101 | JWT::decode( 102 | $response, 103 | new Key( 104 | $this->encryptionKey, 105 | $this->encryptionAlgorithm 106 | ) 107 | ) 108 | ), 109 | true 110 | ); 111 | } 112 | 113 | throw EncryptionConfigurationException::undeterminedEncryption(); 114 | } 115 | 116 | /** 117 | * Get authorization url to begin OAuth flow 118 | * 119 | * @return string 120 | */ 121 | public function getBaseAuthorizationUrl() 122 | { 123 | return $this->getBaseUrlWithRealm().'/protocol/openid-connect/auth'; 124 | } 125 | 126 | /** 127 | * Get access token url to retrieve token 128 | * 129 | * @param array $params 130 | * 131 | * @return string 132 | */ 133 | public function getBaseAccessTokenUrl(array $params) 134 | { 135 | return $this->getBaseUrlWithRealm().'/protocol/openid-connect/token'; 136 | } 137 | 138 | /** 139 | * Get provider url to fetch user details 140 | * 141 | * @param AccessToken $token 142 | * 143 | * @return string 144 | */ 145 | public function getResourceOwnerDetailsUrl(AccessToken $token) 146 | { 147 | return $this->getBaseUrlWithRealm().'/protocol/openid-connect/userinfo'; 148 | } 149 | 150 | /** 151 | * Builds the logout URL. 152 | * 153 | * @param array $options 154 | * @return string Authorization URL 155 | */ 156 | public function getLogoutUrl(array $options = []) 157 | { 158 | $base = $this->getBaseLogoutUrl(); 159 | $params = $this->getAuthorizationParameters($options); 160 | 161 | // Starting with keycloak 18.0.0, the parameter redirect_uri is no longer supported on logout. 162 | // As of this version the parameter is called post_logout_redirect_uri. In addition to this 163 | // a parameter id_token_hint has to be provided. 164 | if ($this->validateGteVersion('18.0.0')) { 165 | if (isset($options['access_token']) === true) { 166 | $accessToken = $options['access_token']; 167 | 168 | $params['id_token_hint'] = $accessToken->getValues()['id_token']; 169 | $params['post_logout_redirect_uri'] = $params['redirect_uri']; 170 | } 171 | 172 | unset($params['redirect_uri']); 173 | } 174 | 175 | $query = $this->getAuthorizationQuery($params); 176 | return $this->appendQuery($base, $query); 177 | } 178 | 179 | /** 180 | * Get logout url to logout of session token 181 | * 182 | * @return string 183 | */ 184 | private function getBaseLogoutUrl() 185 | { 186 | return $this->getBaseUrlWithRealm() . '/protocol/openid-connect/logout'; 187 | } 188 | 189 | /** 190 | * Creates base url from provider configuration. 191 | * 192 | * @return string 193 | */ 194 | protected function getBaseUrlWithRealm() 195 | { 196 | return $this->authServerUrl.'/realms/'.$this->realm; 197 | } 198 | 199 | /** 200 | * Get the default scopes used by this provider. 201 | * 202 | * This should not be a complete list of all scopes, but the minimum 203 | * required for the provider user interface! 204 | * 205 | * @return string[] 206 | */ 207 | protected function getDefaultScopes() 208 | { 209 | $scopes = [ 210 | 'profile', 211 | 'email' 212 | ]; 213 | if ($this->validateGteVersion('20.0.0')) { 214 | $scopes[] = 'openid'; 215 | } 216 | return $scopes; 217 | } 218 | 219 | /** 220 | * Returns the string that should be used to separate scopes when building 221 | * the URL for requesting an access token. 222 | * 223 | * @return string Scope separator, defaults to ',' 224 | */ 225 | protected function getScopeSeparator() 226 | { 227 | return ' '; 228 | } 229 | 230 | 231 | /** 232 | * Check a provider response for errors. 233 | * 234 | * @throws IdentityProviderException 235 | * @param ResponseInterface $response 236 | * @param string $data Parsed response data 237 | * @return void 238 | */ 239 | protected function checkResponse(ResponseInterface $response, $data) 240 | { 241 | if (!empty($data['error'])) { 242 | $error = $data['error']; 243 | if (isset($data['error_description'])) { 244 | $error .= ': '.$data['error_description']; 245 | } 246 | throw new IdentityProviderException($error, $response->getStatusCode(), $data); 247 | } 248 | } 249 | 250 | /** 251 | * Generate a user object from a successful user details request. 252 | * 253 | * @param array $response 254 | * @param AccessToken $token 255 | * @return KeycloakResourceOwner 256 | */ 257 | protected function createResourceOwner(array $response, AccessToken $token) 258 | { 259 | return new KeycloakResourceOwner($response); 260 | } 261 | 262 | /** 263 | * Requests and returns the resource owner of given access token. 264 | * 265 | * @param AccessToken $token 266 | * @return KeycloakResourceOwner 267 | * @throws EncryptionConfigurationException 268 | */ 269 | public function getResourceOwner(AccessToken $token) 270 | { 271 | $response = $this->fetchResourceOwnerDetails($token); 272 | 273 | // We are always getting an array. We have to check if it is 274 | // the array we created 275 | if (array_key_exists('jwt', $response)) { 276 | $response = $response['jwt']; 277 | } 278 | 279 | $response = $this->decryptResponse($response); 280 | 281 | return $this->createResourceOwner($response, $token); 282 | } 283 | 284 | /** 285 | * Updates expected encryption algorithm of Keycloak instance. 286 | * 287 | * @param string $encryptionAlgorithm 288 | * 289 | * @return Keycloak 290 | */ 291 | public function setEncryptionAlgorithm($encryptionAlgorithm) 292 | { 293 | $this->encryptionAlgorithm = $encryptionAlgorithm; 294 | 295 | return $this; 296 | } 297 | 298 | /** 299 | * Updates expected encryption key of Keycloak instance. 300 | * 301 | * @param string $encryptionKey 302 | * 303 | * @return Keycloak 304 | */ 305 | public function setEncryptionKey($encryptionKey) 306 | { 307 | $this->encryptionKey = $encryptionKey; 308 | 309 | return $this; 310 | } 311 | 312 | /** 313 | * Updates expected encryption key of Keycloak instance to content of given 314 | * file path. 315 | * 316 | * @param string $encryptionKeyPath 317 | * 318 | * @return Keycloak 319 | */ 320 | public function setEncryptionKeyPath($encryptionKeyPath) 321 | { 322 | try { 323 | $this->encryptionKey = file_get_contents($encryptionKeyPath); 324 | } catch (Exception $e) { 325 | // Not sure how to handle this yet. 326 | } 327 | 328 | return $this; 329 | } 330 | 331 | /** 332 | * Updates the keycloak version. 333 | * 334 | * @param string $version 335 | * 336 | * @return Keycloak 337 | */ 338 | public function setVersion($version) 339 | { 340 | $this->version = $version; 341 | 342 | return $this; 343 | } 344 | 345 | /** 346 | * Checks if provider is configured to use encryption. 347 | * 348 | * @return bool 349 | */ 350 | public function usesEncryption() 351 | { 352 | return (bool) $this->encryptionAlgorithm && $this->encryptionKey; 353 | } 354 | 355 | /** 356 | * Parses the response according to its content-type header. 357 | * 358 | * @throws UnexpectedValueException 359 | * @param ResponseInterface $response 360 | * @return array 361 | */ 362 | protected function parseResponse(ResponseInterface $response) 363 | { 364 | // We have a problem with keycloak when the userinfo responses 365 | // with a jwt token 366 | // Because it just return a jwt as string with the header 367 | // application/jwt 368 | // This can't be parsed to a array 369 | // Dont know why this function only allow an array as return value... 370 | $content = (string) $response->getBody(); 371 | $type = $this->getContentType($response); 372 | 373 | if (strpos($type, 'jwt') !== false) { 374 | // Here we make the temporary array 375 | return ['jwt' => $content]; 376 | } 377 | 378 | return parent::parseResponse($response); 379 | } 380 | 381 | /** 382 | * Validate if version is greater or equal 383 | * 384 | * @param string $version 385 | * @return bool 386 | */ 387 | private function validateGteVersion($version) 388 | { 389 | return (isset($this->version) && version_compare($this->version, $version, '>=')); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /src/Provider/KeycloakResourceOwner.php: -------------------------------------------------------------------------------- 1 | response = $response; 24 | } 25 | 26 | /** 27 | * Get resource owner id 28 | * 29 | * @return string|null 30 | */ 31 | public function getId() 32 | { 33 | return \array_key_exists('sub', $this->response) ? $this->response['sub'] : null; 34 | } 35 | 36 | /** 37 | * Get resource owner email 38 | * 39 | * @return string|null 40 | */ 41 | public function getEmail() 42 | { 43 | return \array_key_exists('email', $this->response) ? $this->response['email'] : null; 44 | } 45 | 46 | /** 47 | * Get resource owner name 48 | * 49 | * @return string|null 50 | */ 51 | public function getName() 52 | { 53 | return \array_key_exists('name', $this->response) ? $this->response['name'] : null; 54 | } 55 | 56 | /** 57 | * Get resource owner username 58 | * 59 | * @return string|null 60 | */ 61 | public function getUsername() 62 | { 63 | return \array_key_exists('preferred_username', $this->response) ? $this->response['preferred_username'] : null; 64 | } 65 | 66 | /** 67 | * Get resource owner first name 68 | * 69 | * @return string|null 70 | */ 71 | public function getFirstName() 72 | { 73 | return \array_key_exists('given_name', $this->response) ? $this->response['given_name'] : null; 74 | } 75 | 76 | /** 77 | * Get resource owner last name 78 | * 79 | * @return string|null 80 | */ 81 | public function getLastName() 82 | { 83 | return \array_key_exists('family_name', $this->response) ? $this->response['family_name'] : null; 84 | } 85 | 86 | /** 87 | * Return all of the owner details available as an array. 88 | * 89 | * @return array 90 | */ 91 | public function toArray() 92 | { 93 | return $this->response; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test/src/Provider/KeycloakTest.php: -------------------------------------------------------------------------------- 1 | provider = new Keycloak([ 99 | 'authServerUrl' => 'http://mock.url/auth', 100 | 'realm' => 'mock_realm', 101 | 'clientId' => 'mock_client_id', 102 | 'clientSecret' => 'mock_secret', 103 | 'redirectUri' => 'none', 104 | ]); 105 | } 106 | 107 | public function tearDown(): void 108 | { 109 | m::close(); 110 | parent::tearDown(); 111 | } 112 | 113 | public function testAuthorizationUrl() 114 | { 115 | $url = $this->provider->getAuthorizationUrl(); 116 | $uri = parse_url($url); 117 | parse_str($uri['query'], $query); 118 | 119 | $this->assertArrayHasKey('client_id', $query); 120 | $this->assertArrayHasKey('redirect_uri', $query); 121 | $this->assertArrayHasKey('state', $query); 122 | $this->assertArrayHasKey('scope', $query); 123 | $this->assertArrayHasKey('response_type', $query); 124 | $this->assertArrayHasKey('approval_prompt', $query); 125 | $this->assertNotNull($this->provider->getState()); 126 | } 127 | 128 | public function testEncryptionAlgorithm() 129 | { 130 | $algorithm = uniqid(); 131 | $provider = new Keycloak([ 132 | 'encryptionAlgorithm' => $algorithm, 133 | ]); 134 | 135 | $this->assertEquals($algorithm, $provider->encryptionAlgorithm); 136 | 137 | $algorithm = uniqid(); 138 | $provider->setEncryptionAlgorithm($algorithm); 139 | 140 | $this->assertEquals($algorithm, $provider->encryptionAlgorithm); 141 | } 142 | 143 | public function testEncryptionKey() 144 | { 145 | $key = uniqid(); 146 | $provider = new Keycloak([ 147 | 'encryptionKey' => $key, 148 | ]); 149 | 150 | $this->assertEquals($key, $provider->encryptionKey); 151 | 152 | $key = uniqid(); 153 | $provider->setEncryptionKey($key); 154 | 155 | $this->assertEquals($key, $provider->encryptionKey); 156 | } 157 | 158 | public function testEncryptionKeyPath() 159 | { 160 | global $mockFileGetContents; 161 | $path = uniqid(); 162 | $key = uniqid(); 163 | $mockFileGetContents = $key; 164 | 165 | $provider = new Keycloak([ 166 | 'encryptionKeyPath' => $path, 167 | ]); 168 | 169 | $this->assertEquals($key, $provider->encryptionKey); 170 | 171 | $path = uniqid(); 172 | $key = uniqid(); 173 | $mockFileGetContents = $key; 174 | 175 | $provider->setEncryptionKeyPath($path); 176 | 177 | $this->assertEquals($key, $provider->encryptionKey); 178 | } 179 | 180 | public function testEncryptionKeyPathFails() 181 | { 182 | $this->markTestIncomplete('Need to assess the test to see what is required to be checked.'); 183 | 184 | global $mockFileGetContents; 185 | $path = uniqid(); 186 | $key = uniqid(); 187 | $mockFileGetContents = new \Exception(); 188 | 189 | $provider = new Keycloak([ 190 | 'encryptionKeyPath' => $path, 191 | ]); 192 | 193 | $provider->setEncryptionKeyPath($path); 194 | } 195 | 196 | public function testScopes() 197 | { 198 | $scopeSeparator = ' '; 199 | $options = ['scope' => [uniqid(), uniqid()]]; 200 | $query = ['scope' => implode($scopeSeparator, $options['scope'])]; 201 | $url = $this->provider->getAuthorizationUrl($options); 202 | $encodedScope = $this->buildQueryString($query); 203 | $this->assertStringContainsString($encodedScope, $url); 204 | } 205 | 206 | public function testGetAuthorizationUrl() 207 | { 208 | $url = $this->provider->getAuthorizationUrl(); 209 | $uri = parse_url($url); 210 | 211 | $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/auth', $uri['path']); 212 | } 213 | 214 | public function testGetLogoutUrl() 215 | { 216 | $url = $this->provider->getLogoutUrl(); 217 | $uri = parse_url($url); 218 | 219 | $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/logout', $uri['path']); 220 | } 221 | 222 | public function testGetLogoutUrlWithIdTokenHint() 223 | { 224 | $this->provider->setVersion('18.0.0'); 225 | 226 | $options = [ 227 | 'access_token' => new AccessToken( 228 | [ 229 | 'id_token' => 'the_id_token', 230 | 'access_token' => 'the_access_token', 231 | ] 232 | ), 233 | ]; 234 | $url = $this->provider->getLogoutUrl($options); 235 | $uri = parse_url($url); 236 | 237 | $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/logout', $uri['path']); 238 | $this->assertStringContainsString('id_token_hint=the_id_token', $uri['query']); 239 | } 240 | 241 | public function testGetBaseAccessTokenUrl() 242 | { 243 | $params = []; 244 | 245 | $url = $this->provider->getBaseAccessTokenUrl($params); 246 | $uri = parse_url($url); 247 | 248 | $this->assertEquals('/auth/realms/mock_realm/protocol/openid-connect/token', $uri['path']); 249 | } 250 | 251 | public function testGetAccessToken() 252 | { 253 | $stream = $this->createMock(StreamInterface::class); 254 | $stream 255 | ->method('__toString') 256 | ->willReturn('{"access_token":"mock_access_token","scope":"email","token_type":"bearer"}'); 257 | 258 | $response = m::mock('Psr\Http\Message\ResponseInterface'); 259 | $response 260 | ->shouldReceive('getBody') 261 | ->andReturn($stream); 262 | $response 263 | ->shouldReceive('getHeader') 264 | ->andReturn(['content-type' => 'json']); 265 | 266 | $client = m::mock('GuzzleHttp\ClientInterface'); 267 | $client->shouldReceive('send') 268 | ->times(1) 269 | ->andReturn($response); 270 | $this->provider->setHttpClient($client); 271 | 272 | $token = $this 273 | ->provider 274 | ->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); 275 | 276 | $this->assertEquals('mock_access_token', $token->getToken()); 277 | $this->assertNull($token->getExpires()); 278 | $this->assertNull($token->getRefreshToken()); 279 | $this->assertNull($token->getResourceOwnerId()); 280 | } 281 | 282 | public function testUserData() 283 | { 284 | $userId = rand(1000, 9999); 285 | $name = uniqid(); 286 | $email = uniqid(); 287 | $username = uniqid(); 288 | $firstName = uniqid(); 289 | $lastName = uniqid(); 290 | 291 | $getAccessTokenResponseStream = $this->createMock(StreamInterface::class); 292 | $getAccessTokenResponseStream 293 | ->method('__toString') 294 | ->willReturn( 295 | '{"access_token":"mock_access_token","expires":"3600","refresh_token":"mock_refresh_token","otherKey":[1234]}' 296 | ); 297 | 298 | $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); 299 | $postResponse 300 | ->shouldReceive('getBody') 301 | ->andReturn($getAccessTokenResponseStream); 302 | $postResponse 303 | ->shouldReceive('getHeader') 304 | ->andReturn(['content-type' => 'json']); 305 | 306 | $getResourceOwnerResponseStream = $this->createMock(StreamInterface::class); 307 | $getResourceOwnerResponseStream 308 | ->method('__toString') 309 | ->willReturn( 310 | sprintf( 311 | '{"sub": "%s", "name": "%s", "email": "%s", "preferred_username": "%s", "given_name": "%s", "family_name": "%s"}', 312 | $userId, 313 | $name, 314 | $email, 315 | $username, 316 | $firstName, 317 | $lastName 318 | ) 319 | ); 320 | 321 | $userResponse = m::mock('Psr\Http\Message\ResponseInterface'); 322 | $userResponse 323 | ->shouldReceive('getBody') 324 | ->andReturn($getResourceOwnerResponseStream); 325 | $userResponse 326 | ->shouldReceive('getHeader') 327 | ->andReturn(['content-type' => 'json']); 328 | 329 | $client = m::mock('GuzzleHttp\ClientInterface'); 330 | $client 331 | ->shouldReceive('send') 332 | ->andReturn($postResponse, $userResponse); 333 | $this->provider->setHttpClient($client); 334 | 335 | $token = $this->provider->getAccessToken( 336 | 'authorization_code', 337 | [ 338 | 'code' => 'mock_authorization_code', 339 | 'access_token' => 'mock_access_token', 340 | ] 341 | ); 342 | $user = $this->provider->getResourceOwner($token); 343 | 344 | $this->assertEquals($userId, $user->getId()); 345 | $this->assertEquals($userId, $user->toArray()['sub']); 346 | $this->assertEquals($name, $user->getName()); 347 | $this->assertEquals($name, $user->toArray()['name']); 348 | $this->assertEquals($email, $user->getEmail()); 349 | $this->assertEquals($email, $user->toArray()['email']); 350 | $this->assertEquals($username, $user->getUsername()); 351 | $this->assertEquals($username, $user->toArray()['preferred_username']); 352 | $this->assertEquals($firstName, $user->getFirstName()); 353 | $this->assertEquals($firstName, $user->toArray()['given_name']); 354 | $this->assertEquals($lastName, $user->getLastName()); 355 | $this->assertEquals($lastName, $user->toArray()['family_name']); 356 | } 357 | 358 | public function testUserDataWithEncryption() 359 | { 360 | $jwt = JWT::encode( 361 | json_decode( 362 | sprintf( 363 | $this->jwtTemplate, 364 | (new DateTimeImmutable())->add(new DateInterval('PT1H'))->getTimestamp(), 365 | (new DateTimeImmutable())->sub(new DateInterval('P1D'))->getTimestamp(), 366 | (new DateTimeImmutable())->sub(new DateInterval('P1D'))->getTimestamp() 367 | ), 368 | true 369 | ), 370 | self::ENCRYPTION_KEY, 371 | self::ENCRYPTION_ALGORITHM 372 | ); 373 | 374 | $getAccessTokenResponseStream = $this->createMock(StreamInterface::class); 375 | $getAccessTokenResponseStream 376 | ->method('__toString') 377 | ->willReturn( 378 | sprintf( 379 | '{"access_token":"%s","expires":"3600","refresh_token":"mock_refresh_token","otherKey":[1234]}', 380 | $jwt 381 | ) 382 | ); 383 | 384 | $accessTokenResponse = m::mock('Psr\Http\Message\ResponseInterface'); 385 | $accessTokenResponse 386 | ->shouldReceive('getBody') 387 | ->andReturn($getAccessTokenResponseStream); 388 | $accessTokenResponse 389 | ->shouldReceive('getHeader') 390 | ->andReturn(['content-type' => 'json']); 391 | $accessTokenResponse 392 | ->shouldReceive('getStatusCode') 393 | ->andReturn(200); 394 | 395 | $getResourceOwnerResponseStream = $this->createMock(StreamInterface::class); 396 | $getResourceOwnerResponseStream 397 | ->method('__toString') 398 | ->willReturn($jwt); 399 | 400 | $resourceOwnerResponse = m::mock('Psr\Http\Message\ResponseInterface'); 401 | $resourceOwnerResponse 402 | ->shouldReceive('getBody') 403 | ->andReturn($getResourceOwnerResponseStream); 404 | $resourceOwnerResponse 405 | ->shouldReceive('getHeader') 406 | ->andReturn(['content-type' => 'application/jwt']); 407 | $resourceOwnerResponse 408 | ->shouldReceive('getStatusCode') 409 | ->andReturn(200); 410 | 411 | $client = m::mock('GuzzleHttp\ClientInterface'); 412 | $client 413 | ->shouldReceive('send') 414 | ->times(2) 415 | ->andReturn($accessTokenResponse, $resourceOwnerResponse); 416 | $this->provider->setHttpClient($client); 417 | 418 | $token = $this 419 | ->provider 420 | ->setEncryptionAlgorithm(self::ENCRYPTION_ALGORITHM) 421 | ->setEncryptionKey(self::ENCRYPTION_KEY) 422 | ->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); 423 | $user = $this->provider->getResourceOwner($token); 424 | 425 | $email = "test-user@example.org"; 426 | $name = "Test User"; 427 | $userId = "4332085e-b944-4acc-9eb1-27d8f5405f3e"; 428 | $username = "test-user"; 429 | $firstName = "Test"; 430 | $lastName = "User"; 431 | 432 | $this->assertEquals($userId, $user->getId()); 433 | $this->assertEquals($userId, $user->toArray()['sub']); 434 | $this->assertEquals($name, $user->getName()); 435 | $this->assertEquals($name, $user->toArray()['name']); 436 | $this->assertEquals($email, $user->getEmail()); 437 | $this->assertEquals($email, $user->toArray()['email']); 438 | $this->assertEquals($username, $user->getUsername()); 439 | $this->assertEquals($username, $user->toArray()['preferred_username']); 440 | $this->assertEquals($firstName, $user->getFirstName()); 441 | $this->assertEquals($firstName, $user->toArray()['given_name']); 442 | $this->assertEquals($lastName, $user->getLastName()); 443 | $this->assertEquals($lastName, $user->toArray()['family_name']); 444 | } 445 | 446 | public function testUserDataFailsWhenEncryptionEncounteredAndNotConfigured() 447 | { 448 | $this->expectException(EncryptionConfigurationException::class); 449 | 450 | $accessTokenResponseStream = $this->createMock(StreamInterface::class); 451 | $accessTokenResponseStream 452 | ->method('__toString') 453 | ->willReturn( 454 | '{"access_token":"mock_access_token","expires":"3600","refresh_token":"mock_refresh_token","otherKey":[1234]}' 455 | ); 456 | 457 | $getAccessTokenResponse = m::mock('Psr\Http\Message\ResponseInterface'); 458 | $getAccessTokenResponse 459 | ->shouldReceive('getBody') 460 | ->andReturn($accessTokenResponseStream); 461 | $getAccessTokenResponse 462 | ->shouldReceive('getHeader') 463 | ->andReturn(['content-type' => 'json']); 464 | $getAccessTokenResponse 465 | ->shouldReceive('getStatusCode') 466 | ->andReturn(200); 467 | 468 | $resourceOwnerResponseStream = $this->createMock(StreamInterface::class); 469 | $resourceOwnerResponseStream 470 | ->method('__toString') 471 | ->willReturn(uniqid()); 472 | 473 | $getResourceOwnerResponse = m::mock('Psr\Http\Message\ResponseInterface'); 474 | $getResourceOwnerResponse 475 | ->shouldReceive('getBody') 476 | ->andReturn($resourceOwnerResponseStream); 477 | $getResourceOwnerResponse 478 | ->shouldReceive('getHeader') 479 | ->andReturn(['content-type' => 'application/jwt']); 480 | $getResourceOwnerResponse 481 | ->shouldReceive('getStatusCode') 482 | ->andReturn(200); 483 | 484 | $client = m::mock('GuzzleHttp\ClientInterface'); 485 | $client 486 | ->shouldReceive('send') 487 | ->times(2) 488 | ->andReturn($getAccessTokenResponse, $getResourceOwnerResponse); 489 | $this->provider->setHttpClient($client); 490 | 491 | $token = $this->provider->getAccessToken( 492 | 'authorization_code', # 493 | ['code' => 'mock_authorization_code'] 494 | ); 495 | $user = $this->provider->getResourceOwner($token); 496 | } 497 | 498 | public function testErrorResponse() 499 | { 500 | $this->expectException(IdentityProviderException::class); 501 | 502 | $accessTokenResponseStream = $this->createMock(StreamInterface::class); 503 | $accessTokenResponseStream 504 | ->method('__toString') 505 | ->willReturn( 506 | '{"error": "invalid_grant", "error_description": "Code not found"}' 507 | ); 508 | 509 | $response = m::mock('Psr\Http\Message\ResponseInterface'); 510 | $response 511 | ->shouldReceive('getBody') 512 | ->andReturn($accessTokenResponseStream); 513 | $response 514 | ->shouldReceive('getHeader') 515 | $response 516 | ->shouldReceive('getStatusCode') 517 | ->andReturn(401); 518 | 519 | $client = m::mock('GuzzleHttp\ClientInterface'); 520 | $client 521 | ->shouldReceive('send') 522 | ->times(1) 523 | ->andReturn($response); 524 | $this->provider->setHttpClient($client); 525 | 526 | $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); 527 | } 528 | 529 | public function testCanDecryptResponseThrowsExceptionIfResponseIsNotAStringAndEncryptionIsNotUsed() 530 | { 531 | $this->expectException(EncryptionConfigurationException::class); 532 | 533 | $this->provider->decryptResponse(''); 534 | 535 | $this->assertFalse($this->provider->usesEncryption()); 536 | } 537 | 538 | public function testCanDecryptResponseReturnsResponseWhenEncryptionIsUsed() 539 | { 540 | $jwtPayload = json_decode( 541 | sprintf( 542 | $this->jwtTemplate, 543 | (new DateTimeImmutable())->add(new DateInterval('PT1H'))->getTimestamp(), 544 | (new DateTimeImmutable())->sub(new DateInterval('P1D'))->getTimestamp(), 545 | (new DateTimeImmutable())->sub(new DateInterval('P1D'))->getTimestamp() 546 | ), 547 | true 548 | ); 549 | $jwt = JWT::encode( 550 | $jwtPayload, 551 | self::ENCRYPTION_KEY, 552 | self::ENCRYPTION_ALGORITHM 553 | ); 554 | 555 | $this->provider 556 | ->setEncryptionAlgorithm(self::ENCRYPTION_ALGORITHM) 557 | ->setEncryptionKey(self::ENCRYPTION_KEY); 558 | 559 | $response = $this->provider->decryptResponse($jwt); 560 | 561 | $this->assertSame($jwtPayload, $response); 562 | } 563 | } 564 | } 565 | --------------------------------------------------------------------------------