├── .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 | [](https://github.com/stevenmaguire/oauth2-keycloak/releases)
3 | [](LICENSE.md)
4 | [](https://travis-ci.org/stevenmaguire/oauth2-keycloak)
5 | [](https://scrutinizer-ci.com/g/stevenmaguire/oauth2-keycloak/code-structure)
6 | [](https://scrutinizer-ci.com/g/stevenmaguire/oauth2-keycloak)
7 | [](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 |
--------------------------------------------------------------------------------