diff --git a/data/web/inc/lib/composer.json b/data/web/inc/lib/composer.json index c2fef5fb1..a803291f8 100644 --- a/data/web/inc/lib/composer.json +++ b/data/web/inc/lib/composer.json @@ -10,7 +10,7 @@ "mustangostang/spyc": "^0.6.3", "directorytree/ldaprecord": "^2.4", "twig/twig": "^3.0", - "stevenmaguire/oauth2-keycloak": "^3.2", + "stevenmaguire/oauth2-keycloak": "^4.0", "league/oauth2-client": "^2.7" } } diff --git a/data/web/inc/lib/composer.lock b/data/web/inc/lib/composer.lock index 349056afc..0d55230c4 100644 --- a/data/web/inc/lib/composer.lock +++ b/data/web/inc/lib/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ee35a2bf8c80a87b6825c3e86635f709", + "content-hash": "8f5a147cdb147b935a158b86f47a4747", "packages": [ { "name": "bshaffer/oauth2-server-php", @@ -218,25 +218,31 @@ }, { "name": "firebase/php-jwt", - "version": "v5.5.1", + "version": "v6.5.0", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "83b609028194aa042ea33b5af2d41a7427de80e6" + "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6", - "reference": "83b609028194aa042ea33b5af2d41a7427de80e6", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/e94e7353302b0c11ec3cfff7180cd0b1743975d2", + "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.4||^8.0" }, "require-dev": { - "phpunit/phpunit": ">=4.8 <=9" + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" }, "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" }, "type": "library", @@ -269,9 +275,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v5.5.1" + "source": "https://github.com/firebase/php-jwt/tree/v6.5.0" }, - "time": "2021-11-08T20:18:51+00:00" + "time": "2023-05-12T15:47:07+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1703,26 +1709,27 @@ }, { "name": "stevenmaguire/oauth2-keycloak", - "version": "3.2.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/stevenmaguire/oauth2-keycloak.git", - "reference": "34e4824f5fa26aa8e90f1258859c75570c12d27a" + "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/34e4824f5fa26aa8e90f1258859c75570c12d27a", - "reference": "34e4824f5fa26aa8e90f1258859c75570c12d27a", + "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", + "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", "shasum": "" }, "require": { - "firebase/php-jwt": "~4.0|~5.0", - "league/oauth2-client": "^2.0" + "firebase/php-jwt": "^4.0 || ^5.0 || ^6.0", + "league/oauth2-client": "^2.0", + "php": "~7.2 || ~8.0" }, "require-dev": { - "mockery/mockery": "~0.9", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "mockery/mockery": "~1.5.0", + "phpunit/phpunit": "~9.6.4", + "squizlabs/php_codesniffer": "~3.7.0" }, "type": "library", "extra": { @@ -1757,9 +1764,9 @@ ], "support": { "issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues", - "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/3.2.0" + "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/4.0.0" }, - "time": "2022-12-16T12:46:38+00:00" + "time": "2023-03-14T09:43:47+00:00" }, { "name": "symfony/deprecation-contracts", diff --git a/data/web/inc/lib/vendor/composer/installed.json b/data/web/inc/lib/vendor/composer/installed.json index ef7cac481..0b7725517 100644 --- a/data/web/inc/lib/vendor/composer/installed.json +++ b/data/web/inc/lib/vendor/composer/installed.json @@ -217,29 +217,35 @@ }, { "name": "firebase/php-jwt", - "version": "v5.5.1", - "version_normalized": "5.5.1.0", + "version": "v6.5.0", + "version_normalized": "6.5.0.0", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "83b609028194aa042ea33b5af2d41a7427de80e6" + "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6", - "reference": "83b609028194aa042ea33b5af2d41a7427de80e6", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/e94e7353302b0c11ec3cfff7180cd0b1743975d2", + "reference": "e94e7353302b0c11ec3cfff7180cd0b1743975d2", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.4||^8.0" }, "require-dev": { - "phpunit/phpunit": ">=4.8 <=9" + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" }, "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" }, - "time": "2021-11-08T20:18:51+00:00", + "time": "2023-05-12T15:47:07+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -271,7 +277,7 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v5.5.1" + "source": "https://github.com/firebase/php-jwt/tree/v6.5.0" }, "install-path": "../firebase/php-jwt" }, @@ -1759,29 +1765,30 @@ }, { "name": "stevenmaguire/oauth2-keycloak", - "version": "3.2.0", - "version_normalized": "3.2.0.0", + "version": "4.0.0", + "version_normalized": "4.0.0.0", "source": { "type": "git", "url": "https://github.com/stevenmaguire/oauth2-keycloak.git", - "reference": "34e4824f5fa26aa8e90f1258859c75570c12d27a" + "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/34e4824f5fa26aa8e90f1258859c75570c12d27a", - "reference": "34e4824f5fa26aa8e90f1258859c75570c12d27a", + "url": "https://api.github.com/repos/stevenmaguire/oauth2-keycloak/zipball/05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", + "reference": "05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d", "shasum": "" }, "require": { - "firebase/php-jwt": "~4.0|~5.0", - "league/oauth2-client": "^2.0" + "firebase/php-jwt": "^4.0 || ^5.0 || ^6.0", + "league/oauth2-client": "^2.0", + "php": "~7.2 || ~8.0" }, "require-dev": { - "mockery/mockery": "~0.9", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "mockery/mockery": "~1.5.0", + "phpunit/phpunit": "~9.6.4", + "squizlabs/php_codesniffer": "~3.7.0" }, - "time": "2022-12-16T12:46:38+00:00", + "time": "2023-03-14T09:43:47+00:00", "type": "library", "extra": { "branch-alias": { @@ -1816,7 +1823,7 @@ ], "support": { "issues": "https://github.com/stevenmaguire/oauth2-keycloak/issues", - "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/3.2.0" + "source": "https://github.com/stevenmaguire/oauth2-keycloak/tree/4.0.0" }, "install-path": "../stevenmaguire/oauth2-keycloak" }, diff --git a/data/web/inc/lib/vendor/composer/installed.php b/data/web/inc/lib/vendor/composer/installed.php index a9737b061..d7cc77743 100644 --- a/data/web/inc/lib/vendor/composer/installed.php +++ b/data/web/inc/lib/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '07edec4ea50b8eedae10c28eba0b4b2774df537e', + 'reference' => '34e7b3f613661fe2b3c3eb1d316a63dfe111022e', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -13,7 +13,7 @@ '__root__' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '07edec4ea50b8eedae10c28eba0b4b2774df537e', + 'reference' => '34e7b3f613661fe2b3c3eb1d316a63dfe111022e', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -53,9 +53,9 @@ ), ), 'firebase/php-jwt' => array( - 'pretty_version' => 'v5.5.1', - 'version' => '5.5.1.0', - 'reference' => '83b609028194aa042ea33b5af2d41a7427de80e6', + 'pretty_version' => 'v6.5.0', + 'version' => '6.5.0.0', + 'reference' => 'e94e7353302b0c11ec3cfff7180cd0b1743975d2', 'type' => 'library', 'install_path' => __DIR__ . '/../firebase/php-jwt', 'aliases' => array(), @@ -275,9 +275,9 @@ 'dev_requirement' => false, ), 'stevenmaguire/oauth2-keycloak' => array( - 'pretty_version' => '3.2.0', - 'version' => '3.2.0.0', - 'reference' => '34e4824f5fa26aa8e90f1258859c75570c12d27a', + 'pretty_version' => '4.0.0', + 'version' => '4.0.0.0', + 'reference' => '05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d', 'type' => 'library', 'install_path' => __DIR__ . '/../stevenmaguire/oauth2-keycloak', 'aliases' => array(), diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/CHANGELOG.md b/data/web/inc/lib/vendor/firebase/php-jwt/CHANGELOG.md new file mode 100644 index 000000000..35e97fe86 --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/CHANGELOG.md @@ -0,0 +1,117 @@ +# Changelog + +## [6.5.0](https://github.com/firebase/php-jwt/compare/v6.4.0...v6.5.0) (2023-05-12) + + +### Bug Fixes + +* allow KID of '0' ([#505](https://github.com/firebase/php-jwt/issues/505)) ([9dc46a9](https://github.com/firebase/php-jwt/commit/9dc46a9c3e5801294249cfd2554c5363c9f9326a)) + + +### Miscellaneous Chores + +* drop support for PHP 7.3 ([#495](https://github.com/firebase/php-jwt/issues/495)) + +## [6.4.0](https://github.com/firebase/php-jwt/compare/v6.3.2...v6.4.0) (2023-02-08) + + +### Features + +* add support for W3C ES256K ([#462](https://github.com/firebase/php-jwt/issues/462)) ([213924f](https://github.com/firebase/php-jwt/commit/213924f51936291fbbca99158b11bd4ae56c2c95)) +* improve caching by only decoding jwks when necessary ([#486](https://github.com/firebase/php-jwt/issues/486)) ([78d3ed1](https://github.com/firebase/php-jwt/commit/78d3ed1073553f7d0bbffa6c2010009a0d483d5c)) + +## [6.3.2](https://github.com/firebase/php-jwt/compare/v6.3.1...v6.3.2) (2022-11-01) + + +### Bug Fixes + +* check kid before using as array index ([bad1b04](https://github.com/firebase/php-jwt/commit/bad1b040d0c736bbf86814c6b5ae614f517cf7bd)) + +## [6.3.1](https://github.com/firebase/php-jwt/compare/v6.3.0...v6.3.1) (2022-11-01) + + +### Bug Fixes + +* casing of GET for PSR compat ([#451](https://github.com/firebase/php-jwt/issues/451)) ([60b52b7](https://github.com/firebase/php-jwt/commit/60b52b71978790eafcf3b95cfbd83db0439e8d22)) +* string interpolation format for php 8.2 ([#446](https://github.com/firebase/php-jwt/issues/446)) ([2e07d8a](https://github.com/firebase/php-jwt/commit/2e07d8a1524d12b69b110ad649f17461d068b8f2)) + +## 6.3.0 / 2022-07-15 + + - Added ES256 support to JWK parsing ([#399](https://github.com/firebase/php-jwt/pull/399)) + - Fixed potential caching error in `CachedKeySet` by caching jwks as strings ([#435](https://github.com/firebase/php-jwt/pull/435)) + +## 6.2.0 / 2022-05-14 + + - Added `CachedKeySet` ([#397](https://github.com/firebase/php-jwt/pull/397)) + - Added `$defaultAlg` parameter to `JWT::parseKey` and `JWT::parseKeySet` ([#426](https://github.com/firebase/php-jwt/pull/426)). + +## 6.1.0 / 2022-03-23 + + - Drop support for PHP 5.3, 5.4, 5.5, 5.6, and 7.0 + - Add parameter typing and return types where possible + +## 6.0.0 / 2022-01-24 + + - **Backwards-Compatibility Breaking Changes**: See the [Release Notes](https://github.com/firebase/php-jwt/releases/tag/v6.0.0) for more information. + - New Key object to prevent key/algorithm type confusion (#365) + - Add JWK support (#273) + - Add ES256 support (#256) + - Add ES384 support (#324) + - Add Ed25519 support (#343) + +## 5.0.0 / 2017-06-26 +- Support RS384 and RS512. + See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)! +- Add an example for RS256 openssl. + See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)! +- Detect invalid Base64 encoding in signature. + See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)! +- Update `JWT::verify` to handle OpenSSL errors. + See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)! +- Add `array` type hinting to `decode` method + See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)! +- Add all JSON error types. + See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)! +- Bugfix 'kid' not in given key list. + See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)! +- Miscellaneous cleanup, documentation and test fixes. + See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115), + [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and + [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman), + [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)! + +## 4.0.0 / 2016-07-17 +- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)! +- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)! +- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)! +- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)! + +## 3.0.0 / 2015-07-22 +- Minimum PHP version updated from `5.2.0` to `5.3.0`. +- Add `\Firebase\JWT` namespace. See +[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to +[@Dashron](https://github.com/Dashron)! +- Require a non-empty key to decode and verify a JWT. See +[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to +[@sjones608](https://github.com/sjones608)! +- Cleaner documentation blocks in the code. See +[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to +[@johanderuijter](https://github.com/johanderuijter)! + +## 2.2.0 / 2015-06-22 +- Add support for adding custom, optional JWT headers to `JWT::encode()`. See +[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to +[@mcocaro](https://github.com/mcocaro)! + +## 2.1.0 / 2015-05-20 +- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew +between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)! +- Add support for passing an object implementing the `ArrayAccess` interface for +`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)! + +## 2.0.0 / 2015-04-01 +- **Note**: It is strongly recommended that you update to > v2.0.0 to address + known security vulnerabilities in prior versions when both symmetric and + asymmetric keys are used together. +- Update signature for `JWT::decode(...)` to require an array of supported + algorithms to use when verifying token signatures. diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/README.md b/data/web/inc/lib/vendor/firebase/php-jwt/README.md index 1d392cd12..f03826673 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/README.md +++ b/data/web/inc/lib/vendor/firebase/php-jwt/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/firebase/php-jwt.png?branch=master)](https://travis-ci.org/firebase/php-jwt) +![Build Status](https://github.com/firebase/php-jwt/actions/workflows/tests.yml/badge.svg) [![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt) [![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt) [![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt) @@ -29,13 +29,13 @@ Example use Firebase\JWT\JWT; use Firebase\JWT\Key; -$key = "example_key"; -$payload = array( - "iss" => "http://example.org", - "aud" => "http://example.com", - "iat" => 1356999524, - "nbf" => 1357000000 -); +$key = 'example_key'; +$payload = [ + 'iss' => 'http://example.org', + 'aud' => 'http://example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; /** * IMPORTANT: @@ -65,6 +65,40 @@ $decoded_array = (array) $decoded; JWT::$leeway = 60; // $leeway in seconds $decoded = JWT::decode($jwt, new Key($key, 'HS256')); ``` +Example encode/decode headers +------- +Decoding the JWT headers without verifying the JWT first is NOT recommended, and is not supported by +this library. This is because without verifying the JWT, the header values could have been tampered with. +Any value pulled from an unverified header should be treated as if it could be any string sent in from an +attacker. If this is something you still want to do in your application for whatever reason, it's possible to +decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT +header part: +```php +use Firebase\JWT\JWT; + +$key = 'example_key'; +$payload = [ + 'iss' => 'http://example.org', + 'aud' => 'http://example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$headers = [ + 'x-forwarded-for' => 'www.google.com' +]; + +// Encode headers in the JWT string +$jwt = JWT::encode($payload, $key, 'HS256', null, $headers); + +// Decode headers from the JWT string WITHOUT validation +// **IMPORTANT**: This operation is vulnerable to attacks, as the JWT has not yet been verified. +// These headers could be any value sent by an attacker. +list($headersB64, $payloadB64, $sig) = explode('.', $jwt); +$decoded = json_decode(base64_decode($headersB64), true); + +print_r($decoded); +``` Example with RS256 (openssl) ---------------------------- ```php @@ -73,37 +107,52 @@ use Firebase\JWT\Key; $privateKey = << "example.org", - "aud" => "example.com", - "iat" => 1356999524, - "nbf" => 1357000000 -); +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; $jwt = JWT::encode($payload, $privateKey, 'RS256'); echo "Encode:\n" . print_r($jwt, true) . "\n"; @@ -139,12 +188,12 @@ $privateKey = openssl_pkey_get_private( $passphrase ); -$payload = array( - "iss" => "example.org", - "aud" => "example.com", - "iat" => 1356999524, - "nbf" => 1357000000 -); +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; $jwt = JWT::encode($payload, $privateKey, 'RS256'); echo "Encode:\n" . print_r($jwt, true) . "\n"; @@ -173,12 +222,12 @@ $privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair)); $publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair)); -$payload = array( - "iss" => "example.org", - "aud" => "example.com", - "iat" => 1356999524, - "nbf" => 1357000000 -); +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; $jwt = JWT::encode($payload, $privateKey, 'EdDSA'); echo "Encode:\n" . print_r($jwt, true) . "\n"; @@ -187,6 +236,44 @@ $decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA')); echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; ```` +Example with multiple keys +-------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Example RSA keys from previous example +// $privateKey1 = '...'; +// $publicKey1 = '...'; + +// Example EdDSA keys from previous example +// $privateKey2 = '...'; +// $publicKey2 = '...'; + +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt1 = JWT::encode($payload, $privateKey1, 'RS256', 'kid1'); +$jwt2 = JWT::encode($payload, $privateKey2, 'EdDSA', 'kid2'); +echo "Encode 1:\n" . print_r($jwt1, true) . "\n"; +echo "Encode 2:\n" . print_r($jwt2, true) . "\n"; + +$keys = [ + 'kid1' => new Key($publicKey1, 'RS256'), + 'kid2' => new Key($publicKey2, 'EdDSA'), +]; + +$decoded1 = JWT::decode($jwt1, $keys); +$decoded2 = JWT::decode($jwt2, $keys); + +echo "Decode 1:\n" . print_r((array) $decoded1, true) . "\n"; +echo "Decode 2:\n" . print_r((array) $decoded2, true) . "\n"; +``` + Using JWKs ---------- @@ -198,72 +285,115 @@ use Firebase\JWT\JWT; // this endpoint: https://www.gstatic.com/iap/verify/public_key-jwk $jwks = ['keys' => []]; -// JWK::parseKeySet($jwks) returns an associative array of **kid** to private -// key. Pass this as the second parameter to JWT::decode. -// NOTE: The deprecated $supportedAlgorithm must be supplied when parsing from JWK. -JWT::decode($payload, JWK::parseKeySet($jwks), $supportedAlgorithm); +// JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key +// objects. Pass this as the second parameter to JWT::decode. +JWT::decode($payload, JWK::parseKeySet($jwks)); ``` -Changelog ---------- +Using Cached Key Sets +--------------------- -#### 5.0.0 / 2017-06-26 -- Support RS384 and RS512. - See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)! -- Add an example for RS256 openssl. - See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)! -- Detect invalid Base64 encoding in signature. - See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)! -- Update `JWT::verify` to handle OpenSSL errors. - See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)! -- Add `array` type hinting to `decode` method - See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)! -- Add all JSON error types. - See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)! -- Bugfix 'kid' not in given key list. - See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)! -- Miscellaneous cleanup, documentation and test fixes. - See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115), - [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and - [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman), - [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)! +The `CachedKeySet` class can be used to fetch and cache JWKS (JSON Web Key Sets) from a public URI. +This has the following advantages: -#### 4.0.0 / 2016-07-17 -- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)! -- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)! -- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)! -- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)! +1. The results are cached for performance. +2. If an unrecognized key is requested, the cache is refreshed, to accomodate for key rotation. +3. If rate limiting is enabled, the JWKS URI will not make more than 10 requests a second. -#### 3.0.0 / 2015-07-22 -- Minimum PHP version updated from `5.2.0` to `5.3.0`. -- Add `\Firebase\JWT` namespace. See -[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to -[@Dashron](https://github.com/Dashron)! -- Require a non-empty key to decode and verify a JWT. See -[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to -[@sjones608](https://github.com/sjones608)! -- Cleaner documentation blocks in the code. See -[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to -[@johanderuijter](https://github.com/johanderuijter)! +```php +use Firebase\JWT\CachedKeySet; +use Firebase\JWT\JWT; -#### 2.2.0 / 2015-06-22 -- Add support for adding custom, optional JWT headers to `JWT::encode()`. See -[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to -[@mcocaro](https://github.com/mcocaro)! +// The URI for the JWKS you wish to cache the results from +$jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk'; -#### 2.1.0 / 2015-05-20 -- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew -between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)! -- Add support for passing an object implementing the `ArrayAccess` interface for -`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)! +// Create an HTTP client (can be any PSR-7 compatible HTTP client) +$httpClient = new GuzzleHttp\Client(); -#### 2.0.0 / 2015-04-01 -- **Note**: It is strongly recommended that you update to > v2.0.0 to address - known security vulnerabilities in prior versions when both symmetric and - asymmetric keys are used together. -- Update signature for `JWT::decode(...)` to require an array of supported - algorithms to use when verifying token signatures. +// Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory) +$httpFactory = new GuzzleHttp\Psr\HttpFactory(); +// Create a cache item pool (can be any PSR-6 compatible cache item pool) +$cacheItemPool = Phpfastcache\CacheManager::getInstance('files'); + +$keySet = new CachedKeySet( + $jwksUri, + $httpClient, + $httpFactory, + $cacheItemPool, + null, // $expiresAfter int seconds to set the JWKS to expire + true // $rateLimit true to enable rate limit of 10 RPS on lookup of invalid keys +); + +$jwt = 'eyJhbGci...'; // Some JWT signed by a key from the $jwkUri above +$decoded = JWT::decode($jwt, $keySet); +``` + +Miscellaneous +------------- + +#### Exception Handling + +When a call to `JWT::decode` is invalid, it will throw one of the following exceptions: + +```php +use Firebase\JWT\JWT; +use Firebase\JWT\SignatureInvalidException; +use Firebase\JWT\BeforeValidException; +use Firebase\JWT\ExpiredException; +use DomainException; +use InvalidArgumentException; +use UnexpectedValueException; + +try { + $decoded = JWT::decode($payload, $keys); +} catch (InvalidArgumentException $e) { + // provided key/key-array is empty or malformed. +} catch (DomainException $e) { + // provided algorithm is unsupported OR + // provided key is invalid OR + // unknown error thrown in openSSL or libsodium OR + // libsodium is required but not available. +} catch (SignatureInvalidException $e) { + // provided JWT signature verification failed. +} catch (BeforeValidException $e) { + // provided JWT is trying to be used before "nbf" claim OR + // provided JWT is trying to be used before "iat" claim. +} catch (ExpiredException $e) { + // provided JWT is trying to be used after "exp" claim. +} catch (UnexpectedValueException $e) { + // provided JWT is malformed OR + // provided JWT is missing an algorithm / using an unsupported algorithm OR + // provided JWT algorithm does not match provided key OR + // provided key ID in key/key-array is empty or invalid. +} +``` + +All exceptions in the `Firebase\JWT` namespace extend `UnexpectedValueException`, and can be simplified +like this: + +```php +try { + $decoded = JWT::decode($payload, $keys); +} catch (LogicException $e) { + // errors having to do with environmental setup or malformed JWT Keys +} catch (UnexpectedValueException $e) { + // errors having to do with JWT signature and claims +} +``` + +#### Casting to array + +The return value of `JWT::decode` is the generic PHP object `stdClass`. If you'd like to handle with arrays +instead, you can do the following: + +```php +// return type is stdClass +$decoded = JWT::decode($payload, $keys); + +// cast to array +$decoded = json_decode(json_encode($decoded), true); +``` Tests ----- diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/composer.json b/data/web/inc/lib/vendor/firebase/php-jwt/composer.json index 6146e2dcc..e23dfe378 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/composer.json +++ b/data/web/inc/lib/vendor/firebase/php-jwt/composer.json @@ -20,10 +20,11 @@ ], "license": "BSD-3-Clause", "require": { - "php": ">=5.3.0" + "php": "^7.4||^8.0" }, "suggest": { - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "ext-sodium": "Support EdDSA (Ed25519) signatures" }, "autoload": { "psr-4": { @@ -31,6 +32,11 @@ } }, "require-dev": { - "phpunit/phpunit": ">=4.8 <=9" + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" } } diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/CachedKeySet.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/CachedKeySet.php new file mode 100644 index 000000000..baf801f13 --- /dev/null +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/CachedKeySet.php @@ -0,0 +1,258 @@ + + */ +class CachedKeySet implements ArrayAccess +{ + /** + * @var string + */ + private $jwksUri; + /** + * @var ClientInterface + */ + private $httpClient; + /** + * @var RequestFactoryInterface + */ + private $httpFactory; + /** + * @var CacheItemPoolInterface + */ + private $cache; + /** + * @var ?int + */ + private $expiresAfter; + /** + * @var ?CacheItemInterface + */ + private $cacheItem; + /** + * @var array> + */ + private $keySet; + /** + * @var string + */ + private $cacheKey; + /** + * @var string + */ + private $cacheKeyPrefix = 'jwks'; + /** + * @var int + */ + private $maxKeyLength = 64; + /** + * @var bool + */ + private $rateLimit; + /** + * @var string + */ + private $rateLimitCacheKey; + /** + * @var int + */ + private $maxCallsPerMinute = 10; + /** + * @var string|null + */ + private $defaultAlg; + + public function __construct( + string $jwksUri, + ClientInterface $httpClient, + RequestFactoryInterface $httpFactory, + CacheItemPoolInterface $cache, + int $expiresAfter = null, + bool $rateLimit = false, + string $defaultAlg = null + ) { + $this->jwksUri = $jwksUri; + $this->httpClient = $httpClient; + $this->httpFactory = $httpFactory; + $this->cache = $cache; + $this->expiresAfter = $expiresAfter; + $this->rateLimit = $rateLimit; + $this->defaultAlg = $defaultAlg; + $this->setCacheKeys(); + } + + /** + * @param string $keyId + * @return Key + */ + public function offsetGet($keyId): Key + { + if (!$this->keyIdExists($keyId)) { + throw new OutOfBoundsException('Key ID not found'); + } + return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg); + } + + /** + * @param string $keyId + * @return bool + */ + public function offsetExists($keyId): bool + { + return $this->keyIdExists($keyId); + } + + /** + * @param string $offset + * @param Key $value + */ + public function offsetSet($offset, $value): void + { + throw new LogicException('Method not implemented'); + } + + /** + * @param string $offset + */ + public function offsetUnset($offset): void + { + throw new LogicException('Method not implemented'); + } + + /** + * @return array + */ + private function formatJwksForCache(string $jwks): array + { + $jwks = json_decode($jwks, true); + + if (!isset($jwks['keys'])) { + throw new UnexpectedValueException('"keys" member must exist in the JWK Set'); + } + + if (empty($jwks['keys'])) { + throw new InvalidArgumentException('JWK Set did not contain any keys'); + } + + $keys = []; + foreach ($jwks['keys'] as $k => $v) { + $kid = isset($v['kid']) ? $v['kid'] : $k; + $keys[(string) $kid] = $v; + } + + return $keys; + } + + private function keyIdExists(string $keyId): bool + { + if (null === $this->keySet) { + $item = $this->getCacheItem(); + // Try to load keys from cache + if ($item->isHit()) { + // item found! retrieve it + $this->keySet = $item->get(); + // If the cached item is a string, the JWKS response was cached (previous behavior). + // Parse this into expected format array instead. + if (\is_string($this->keySet)) { + $this->keySet = $this->formatJwksForCache($this->keySet); + } + } + } + + if (!isset($this->keySet[$keyId])) { + if ($this->rateLimitExceeded()) { + return false; + } + $request = $this->httpFactory->createRequest('GET', $this->jwksUri); + $jwksResponse = $this->httpClient->sendRequest($request); + $this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody()); + + if (!isset($this->keySet[$keyId])) { + return false; + } + + $item = $this->getCacheItem(); + $item->set($this->keySet); + if ($this->expiresAfter) { + $item->expiresAfter($this->expiresAfter); + } + $this->cache->save($item); + } + + return true; + } + + private function rateLimitExceeded(): bool + { + if (!$this->rateLimit) { + return false; + } + + $cacheItem = $this->cache->getItem($this->rateLimitCacheKey); + if (!$cacheItem->isHit()) { + $cacheItem->expiresAfter(1); // # of calls are cached each minute + } + + $callsPerMinute = (int) $cacheItem->get(); + if (++$callsPerMinute > $this->maxCallsPerMinute) { + return true; + } + $cacheItem->set($callsPerMinute); + $this->cache->save($cacheItem); + return false; + } + + private function getCacheItem(): CacheItemInterface + { + if (\is_null($this->cacheItem)) { + $this->cacheItem = $this->cache->getItem($this->cacheKey); + } + + return $this->cacheItem; + } + + private function setCacheKeys(): void + { + if (empty($this->jwksUri)) { + throw new RuntimeException('JWKS URI is empty'); + } + + // ensure we do not have illegal characters + $key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri); + + // add prefix + $key = $this->cacheKeyPrefix . $key; + + // Hash keys if they exceed $maxKeyLength of 64 + if (\strlen($key) > $this->maxKeyLength) { + $key = substr(hash('sha256', $key), 0, $this->maxKeyLength); + } + + $this->cacheKey = $key; + + if ($this->rateLimit) { + // add prefix + $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key; + + // Hash keys if they exceed $maxKeyLength of 64 + if (\strlen($rateLimitKey) > $this->maxKeyLength) { + $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength); + } + + $this->rateLimitCacheKey = $rateLimitKey; + } + } +} diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/JWK.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWK.php index 981a9ba7f..c7eff8ae4 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/src/JWK.php +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWK.php @@ -20,12 +20,25 @@ use UnexpectedValueException; */ class JWK { + private const OID = '1.2.840.10045.2.1'; + private const ASN1_OBJECT_IDENTIFIER = 0x06; + private const ASN1_SEQUENCE = 0x10; // also defined in JWT + private const ASN1_BIT_STRING = 0x03; + private const EC_CURVES = [ + 'P-256' => '1.2.840.10045.3.1.7', // Len: 64 + 'secp256k1' => '1.3.132.0.10', // Len: 64 + // 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported) + // 'P-521' => '1.3.132.0.35', // Len: 132 (not supported) + ]; + /** * Parse a set of JWK keys * - * @param array $jwks The JSON Web Key Set as an associative array + * @param array $jwks The JSON Web Key Set as an associative array + * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the + * JSON Web Key Set * - * @return array An associative array that represents the set of keys + * @return array An associative array of key IDs (kid) to Key objects * * @throws InvalidArgumentException Provided JWK Set is empty * @throws UnexpectedValueException Provided JWK Set was invalid @@ -33,21 +46,22 @@ class JWK * * @uses parseKey */ - public static function parseKeySet(array $jwks) + public static function parseKeySet(array $jwks, string $defaultAlg = null): array { - $keys = array(); + $keys = []; if (!isset($jwks['keys'])) { throw new UnexpectedValueException('"keys" member must exist in the JWK Set'); } + if (empty($jwks['keys'])) { throw new InvalidArgumentException('JWK Set did not contain any keys'); } foreach ($jwks['keys'] as $k => $v) { $kid = isset($v['kid']) ? $v['kid'] : $k; - if ($key = self::parseKey($v)) { - $keys[$kid] = $key; + if ($key = self::parseKey($v, $defaultAlg)) { + $keys[(string) $kid] = $key; } } @@ -61,9 +75,11 @@ class JWK /** * Parse a JWK key * - * @param array $jwk An individual JWK + * @param array $jwk An individual JWK + * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the + * JSON Web Key Set * - * @return resource|array An associative array that represents the key + * @return Key The key object for the JWK * * @throws InvalidArgumentException Provided JWK is empty * @throws UnexpectedValueException Provided JWK was invalid @@ -71,15 +87,27 @@ class JWK * * @uses createPemFromModulusAndExponent */ - public static function parseKey(array $jwk) + public static function parseKey(array $jwk, string $defaultAlg = null): ?Key { if (empty($jwk)) { throw new InvalidArgumentException('JWK must not be empty'); } + if (!isset($jwk['kty'])) { throw new UnexpectedValueException('JWK must contain a "kty" parameter'); } + if (!isset($jwk['alg'])) { + if (\is_null($defaultAlg)) { + // The "alg" parameter is optional in a KTY, but an algorithm is required + // for parsing in this library. Use the $defaultAlg parameter when parsing the + // key set in order to prevent this error. + // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 + throw new UnexpectedValueException('JWK must contain an "alg" parameter'); + } + $jwk['alg'] = $defaultAlg; + } + switch ($jwk['kty']) { case 'RSA': if (!empty($jwk['d'])) { @@ -96,11 +124,72 @@ class JWK 'OpenSSL error: ' . \openssl_error_string() ); } - return $publicKey; + return new Key($publicKey, $jwk['alg']); + case 'EC': + if (isset($jwk['d'])) { + // The key is actually a private key + throw new UnexpectedValueException('Key data must be for a public key'); + } + + if (empty($jwk['crv'])) { + throw new UnexpectedValueException('crv not set'); + } + + if (!isset(self::EC_CURVES[$jwk['crv']])) { + throw new DomainException('Unrecognised or unsupported EC curve'); + } + + if (empty($jwk['x']) || empty($jwk['y'])) { + throw new UnexpectedValueException('x and y not set'); + } + + $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']); + return new Key($publicKey, $jwk['alg']); default: // Currently only RSA is supported break; } + + return null; + } + + /** + * Converts the EC JWK values to pem format. + * + * @param string $crv The EC curve (only P-256 is supported) + * @param string $x The EC x-coordinate + * @param string $y The EC y-coordinate + * + * @return string + */ + private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string + { + $pem = + self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER( + self::ASN1_OBJECT_IDENTIFIER, + self::encodeOID(self::OID) + ) + . self::encodeDER( + self::ASN1_OBJECT_IDENTIFIER, + self::encodeOID(self::EC_CURVES[$crv]) + ) + ) . + self::encodeDER( + self::ASN1_BIT_STRING, + \chr(0x00) . \chr(0x04) + . JWT::urlsafeB64Decode($x) + . JWT::urlsafeB64Decode($y) + ) + ); + + return sprintf( + "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", + wordwrap(base64_encode($pem), 64, "\n", true) + ); } /** @@ -113,22 +202,22 @@ class JWK * * @uses encodeLength */ - private static function createPemFromModulusAndExponent($n, $e) - { - $modulus = JWT::urlsafeB64Decode($n); - $publicExponent = JWT::urlsafeB64Decode($e); + private static function createPemFromModulusAndExponent( + string $n, + string $e + ): string { + $mod = JWT::urlsafeB64Decode($n); + $exp = JWT::urlsafeB64Decode($e); - $components = array( - 'modulus' => \pack('Ca*a*', 2, self::encodeLength(\strlen($modulus)), $modulus), - 'publicExponent' => \pack('Ca*a*', 2, self::encodeLength(\strlen($publicExponent)), $publicExponent) - ); + $modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod); + $publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp); $rsaPublicKey = \pack( 'Ca*a*a*', 48, - self::encodeLength(\strlen($components['modulus']) + \strlen($components['publicExponent'])), - $components['modulus'], - $components['publicExponent'] + self::encodeLength(\strlen($modulus) + \strlen($publicExponent)), + $modulus, + $publicExponent ); // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. @@ -143,11 +232,9 @@ class JWK $rsaOID . $rsaPublicKey ); - $rsaPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . + return "-----BEGIN PUBLIC KEY-----\r\n" . \chunk_split(\base64_encode($rsaPublicKey), 64) . '-----END PUBLIC KEY-----'; - - return $rsaPublicKey; } /** @@ -159,7 +246,7 @@ class JWK * @param int $length * @return string */ - private static function encodeLength($length) + private static function encodeLength(int $length): string { if ($length <= 0x7F) { return \chr($length); @@ -169,4 +256,68 @@ class JWK return \pack('Ca*', 0x80 | \strlen($temp), $temp); } + + /** + * Encodes a value into a DER object. + * Also defined in Firebase\JWT\JWT + * + * @param int $type DER tag + * @param string $value the value to encode + * @return string the encoded object + */ + private static function encodeDER(int $type, string $value): string + { + $tag_header = 0; + if ($type === self::ASN1_SEQUENCE) { + $tag_header |= 0x20; + } + + // Type + $der = \chr($tag_header | $type); + + // Length + $der .= \chr(\strlen($value)); + + return $der . $value; + } + + /** + * Encodes a string into a DER-encoded OID. + * + * @param string $oid the OID string + * @return string the binary DER-encoded OID + */ + private static function encodeOID(string $oid): string + { + $octets = explode('.', $oid); + + // Get the first octet + $first = (int) array_shift($octets); + $second = (int) array_shift($octets); + $oid = \chr($first * 40 + $second); + + // Iterate over subsequent octets + foreach ($octets as $octet) { + if ($octet == 0) { + $oid .= \chr(0x00); + continue; + } + $bin = ''; + + while ($octet) { + $bin .= \chr(0x80 | ($octet & 0x7f)); + $octet >>= 7; + } + $bin[0] = $bin[0] & \chr(0x7f); + + // Convert to big endian if necessary + if (pack('V', 65534) == pack('L', 65534)) { + $oid .= strrev($bin); + } else { + $oid .= $bin; + } + } + + return $oid; + } } diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php index ec1641bc4..c83ff0990 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/JWT.php @@ -3,12 +3,14 @@ namespace Firebase\JWT; use ArrayAccess; +use DateTime; use DomainException; use Exception; use InvalidArgumentException; use OpenSSLAsymmetricKey; +use OpenSSLCertificate; +use stdClass; use UnexpectedValueException; -use DateTime; /** * JSON Web Token implementation, based on this spec: @@ -25,52 +27,62 @@ use DateTime; */ class JWT { - const ASN1_INTEGER = 0x02; - const ASN1_SEQUENCE = 0x10; - const ASN1_BIT_STRING = 0x03; + private const ASN1_INTEGER = 0x02; + private const ASN1_SEQUENCE = 0x10; + private const ASN1_BIT_STRING = 0x03; /** * When checking nbf, iat or expiration times, * we want to provide some extra leeway time to * account for clock skew. + * + * @var int */ public static $leeway = 0; /** * Allow the current timestamp to be specified. * Useful for fixing a value within unit testing. - * * Will default to PHP time() value if null. + * + * @var ?int */ public static $timestamp = null; - public static $supported_algs = array( - 'ES384' => array('openssl', 'SHA384'), - 'ES256' => array('openssl', 'SHA256'), - 'HS256' => array('hash_hmac', 'SHA256'), - 'HS384' => array('hash_hmac', 'SHA384'), - 'HS512' => array('hash_hmac', 'SHA512'), - 'RS256' => array('openssl', 'SHA256'), - 'RS384' => array('openssl', 'SHA384'), - 'RS512' => array('openssl', 'SHA512'), - 'EdDSA' => array('sodium_crypto', 'EdDSA'), - ); + /** + * @var array + */ + public static $supported_algs = [ + 'ES384' => ['openssl', 'SHA384'], + 'ES256' => ['openssl', 'SHA256'], + 'ES256K' => ['openssl', 'SHA256'], + 'HS256' => ['hash_hmac', 'SHA256'], + 'HS384' => ['hash_hmac', 'SHA384'], + 'HS512' => ['hash_hmac', 'SHA512'], + 'RS256' => ['openssl', 'SHA256'], + 'RS384' => ['openssl', 'SHA384'], + 'RS512' => ['openssl', 'SHA512'], + 'EdDSA' => ['sodium_crypto', 'EdDSA'], + ]; /** * Decodes a JWT string into a PHP object. * - * @param string $jwt The JWT - * @param Key|array|mixed $keyOrKeyArray The Key or array of Key objects. - * If the algorithm used is asymmetric, this is the public key - * Each Key object contains an algorithm and matching key. - * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', - * 'HS512', 'RS256', 'RS384', and 'RS512' - * @param array $allowed_algs [DEPRECATED] List of supported verification algorithms. Only - * should be used for backwards compatibility. + * @param string $jwt The JWT + * @param Key|ArrayAccess|array $keyOrKeyArray The Key or associative array of key IDs + * (kid) to Key objects. + * If the algorithm used is asymmetric, this is + * the public key. + * Each Key object contains an algorithm and + * matching key. + * Supported algorithms are 'ES384','ES256', + * 'HS256', 'HS384', 'HS512', 'RS256', 'RS384' + * and 'RS512'. * - * @return object The JWT's payload as a PHP object + * @return stdClass The JWT's payload as a PHP object * - * @throws InvalidArgumentException Provided JWT was empty + * @throws InvalidArgumentException Provided key/key-array was empty or malformed + * @throws DomainException Provided JWT is malformed * @throws UnexpectedValueException Provided JWT was invalid * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf' @@ -80,27 +92,37 @@ class JWT * @uses jsonDecode * @uses urlsafeB64Decode */ - public static function decode($jwt, $keyOrKeyArray, array $allowed_algs = array()) - { + public static function decode( + string $jwt, + $keyOrKeyArray + ): stdClass { + // Validate JWT $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; if (empty($keyOrKeyArray)) { throw new InvalidArgumentException('Key may not be empty'); } $tks = \explode('.', $jwt); - if (\count($tks) != 3) { + if (\count($tks) !== 3) { throw new UnexpectedValueException('Wrong number of segments'); } list($headb64, $bodyb64, $cryptob64) = $tks; - if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) { + $headerRaw = static::urlsafeB64Decode($headb64); + if (null === ($header = static::jsonDecode($headerRaw))) { throw new UnexpectedValueException('Invalid header encoding'); } - if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) { + $payloadRaw = static::urlsafeB64Decode($bodyb64); + if (null === ($payload = static::jsonDecode($payloadRaw))) { throw new UnexpectedValueException('Invalid claims encoding'); } - if (false === ($sig = static::urlsafeB64Decode($cryptob64))) { - throw new UnexpectedValueException('Invalid signature encoding'); + if (\is_array($payload)) { + // prevent PHP Fatal Error in edge-cases when payload is empty array + $payload = (object) $payload; } + if (!$payload instanceof stdClass) { + throw new UnexpectedValueException('Payload must be a JSON object'); + } + $sig = static::urlsafeB64Decode($cryptob64); if (empty($header->alg)) { throw new UnexpectedValueException('Empty algorithm'); } @@ -108,31 +130,18 @@ class JWT throw new UnexpectedValueException('Algorithm not supported'); } - list($keyMaterial, $algorithm) = self::getKeyMaterialAndAlgorithm( - $keyOrKeyArray, - empty($header->kid) ? null : $header->kid - ); + $key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null); - if (empty($algorithm)) { - // Use deprecated "allowed_algs" to determine if the algorithm is supported. - // This opens up the possibility of an attack in some implementations. - // @see https://github.com/firebase/php-jwt/issues/351 - if (!\in_array($header->alg, $allowed_algs)) { - throw new UnexpectedValueException('Algorithm not allowed'); - } - } else { - // Check the algorithm - if (!self::constantTimeEquals($algorithm, $header->alg)) { - // See issue #351 - throw new UnexpectedValueException('Incorrect key for this algorithm'); - } + // Check the algorithm + if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) { + // See issue #351 + throw new UnexpectedValueException('Incorrect key for this algorithm'); } - if ($header->alg === 'ES256' || $header->alg === 'ES384') { - // OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures + if (\in_array($header->alg, ['ES256', 'ES256K', 'ES384'], true)) { + // OpenSSL expects an ASN.1 DER sequence for ES256/ES256K/ES384 signatures $sig = self::signatureToDER($sig); } - - if (!static::verify("$headb64.$bodyb64", $sig, $keyMaterial, $header->alg)) { + if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) { throw new SignatureInvalidException('Signature verification failed'); } @@ -162,34 +171,37 @@ class JWT } /** - * Converts and signs a PHP object or array into a JWT string. + * Converts and signs a PHP array into a JWT string. * - * @param object|array $payload PHP object or array - * @param string|resource $key The secret key. - * If the algorithm used is asymmetric, this is the private key - * @param string $alg The signing algorithm. - * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', - * 'HS512', 'RS256', 'RS384', and 'RS512' - * @param mixed $keyId - * @param array $head An array with header elements to attach + * @param array $payload PHP array + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256', + * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' + * @param string $keyId + * @param array $head An array with header elements to attach * * @return string A signed JWT * * @uses jsonEncode * @uses urlsafeB64Encode */ - public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null) - { - $header = array('typ' => 'JWT', 'alg' => $alg); + public static function encode( + array $payload, + $key, + string $alg, + string $keyId = null, + array $head = null + ): string { + $header = ['typ' => 'JWT', 'alg' => $alg]; if ($keyId !== null) { $header['kid'] = $keyId; } if (isset($head) && \is_array($head)) { $header = \array_merge($head, $header); } - $segments = array(); - $segments[] = static::urlsafeB64Encode(static::jsonEncode($header)); - $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload)); + $segments = []; + $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header)); + $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload)); $signing_input = \implode('.', $segments); $signature = static::sign($signing_input, $key, $alg); @@ -201,67 +213,84 @@ class JWT /** * Sign a string with a given key and algorithm. * - * @param string $msg The message to sign - * @param string|resource $key The secret key - * @param string $alg The signing algorithm. - * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', - * 'HS512', 'RS256', 'RS384', and 'RS512' + * @param string $msg The message to sign + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string $alg Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256', + * 'HS384', 'HS512', 'RS256', 'RS384', and 'RS512' * * @return string An encrypted message * * @throws DomainException Unsupported algorithm or bad key was specified */ - public static function sign($msg, $key, $alg = 'HS256') - { + public static function sign( + string $msg, + $key, + string $alg + ): string { if (empty(static::$supported_algs[$alg])) { throw new DomainException('Algorithm not supported'); } list($function, $algorithm) = static::$supported_algs[$alg]; switch ($function) { case 'hash_hmac': + if (!\is_string($key)) { + throw new InvalidArgumentException('key must be a string when using hmac'); + } return \hash_hmac($algorithm, $msg, $key, true); case 'openssl': $signature = ''; - $success = \openssl_sign($msg, $signature, $key, $algorithm); + $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line if (!$success) { - throw new DomainException("OpenSSL unable to sign data"); + throw new DomainException('OpenSSL unable to sign data'); } - if ($alg === 'ES256') { + if ($alg === 'ES256' || $alg === 'ES256K') { $signature = self::signatureFromDER($signature, 256); } elseif ($alg === 'ES384') { $signature = self::signatureFromDER($signature, 384); } return $signature; case 'sodium_crypto': - if (!function_exists('sodium_crypto_sign_detached')) { + if (!\function_exists('sodium_crypto_sign_detached')) { throw new DomainException('libsodium is not available'); } + if (!\is_string($key)) { + throw new InvalidArgumentException('key must be a string when using EdDSA'); + } try { // The last non-empty line is used as the key. $lines = array_filter(explode("\n", $key)); - $key = base64_decode(end($lines)); + $key = base64_decode((string) end($lines)); + if (\strlen($key) === 0) { + throw new DomainException('Key cannot be empty string'); + } return sodium_crypto_sign_detached($msg, $key); } catch (Exception $e) { throw new DomainException($e->getMessage(), 0, $e); } } + + throw new DomainException('Algorithm not supported'); } /** * Verify a signature with the message, key and method. Not all methods * are symmetric, so we must have a separate verify and sign method. * - * @param string $msg The original message (header and body) - * @param string $signature The original signature - * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key - * @param string $alg The algorithm + * @param string $msg The original message (header and body) + * @param string $signature The original signature + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial For HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey + * @param string $alg The algorithm * * @return bool * * @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure */ - private static function verify($msg, $signature, $key, $alg) - { + private static function verify( + string $msg, + string $signature, + $keyMaterial, + string $alg + ): bool { if (empty(static::$supported_algs[$alg])) { throw new DomainException('Algorithm not supported'); } @@ -269,10 +298,11 @@ class JWT list($function, $algorithm) = static::$supported_algs[$alg]; switch ($function) { case 'openssl': - $success = \openssl_verify($msg, $signature, $key, $algorithm); + $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line if ($success === 1) { return true; - } elseif ($success === 0) { + } + if ($success === 0) { return false; } // returns 1 on success, 0 on failure, -1 on error. @@ -280,21 +310,33 @@ class JWT 'OpenSSL error: ' . \openssl_error_string() ); case 'sodium_crypto': - if (!function_exists('sodium_crypto_sign_verify_detached')) { - throw new DomainException('libsodium is not available'); - } - try { - // The last non-empty line is used as the key. - $lines = array_filter(explode("\n", $key)); - $key = base64_decode(end($lines)); - return sodium_crypto_sign_verify_detached($signature, $msg, $key); - } catch (Exception $e) { - throw new DomainException($e->getMessage(), 0, $e); - } + if (!\function_exists('sodium_crypto_sign_verify_detached')) { + throw new DomainException('libsodium is not available'); + } + if (!\is_string($keyMaterial)) { + throw new InvalidArgumentException('key must be a string when using EdDSA'); + } + try { + // The last non-empty line is used as the key. + $lines = array_filter(explode("\n", $keyMaterial)); + $key = base64_decode((string) end($lines)); + if (\strlen($key) === 0) { + throw new DomainException('Key cannot be empty string'); + } + if (\strlen($signature) === 0) { + throw new DomainException('Signature cannot be empty string'); + } + return sodium_crypto_sign_verify_detached($signature, $msg, $key); + } catch (Exception $e) { + throw new DomainException($e->getMessage(), 0, $e); + } case 'hash_hmac': default: - $hash = \hash_hmac($algorithm, $msg, $key, true); - return self::constantTimeEquals($signature, $hash); + if (!\is_string($keyMaterial)) { + throw new InvalidArgumentException('key must be a string when using hmac'); + } + $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true); + return self::constantTimeEquals($hash, $signature); } } @@ -303,30 +345,16 @@ class JWT * * @param string $input JSON string * - * @return object Object representation of JSON string + * @return mixed The decoded JSON string * * @throws DomainException Provided string was invalid JSON */ - public static function jsonDecode($input) + public static function jsonDecode(string $input) { - if (\version_compare(PHP_VERSION, '5.4.0', '>=') && !(\defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) { - /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you - * to specify that large ints (like Steam Transaction IDs) should be treated as - * strings, rather than the PHP default behaviour of converting them to floats. - */ - $obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING); - } else { - /** Not all servers will support that, however, so for older versions we must - * manually detect large ints in the JSON string and quote them (thus converting - *them to strings) before decoding, hence the preg_replace() call. - */ - $max_int_length = \strlen((string) PHP_INT_MAX) - 1; - $json_without_bigints = \preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input); - $obj = \json_decode($json_without_bigints); - } + $obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING); if ($errno = \json_last_error()) { - static::handleJsonError($errno); + self::handleJsonError($errno); } elseif ($obj === null && $input !== 'null') { throw new DomainException('Null result with non-null input'); } @@ -334,22 +362,30 @@ class JWT } /** - * Encode a PHP object into a JSON string. + * Encode a PHP array into a JSON string. * - * @param object|array $input A PHP object or array + * @param array $input A PHP array * - * @return string JSON representation of the PHP object or array + * @return string JSON representation of the PHP array * * @throws DomainException Provided object could not be encoded to valid JSON */ - public static function jsonEncode($input) + public static function jsonEncode(array $input): string { - $json = \json_encode($input); + if (PHP_VERSION_ID >= 50400) { + $json = \json_encode($input, \JSON_UNESCAPED_SLASHES); + } else { + // PHP 5.3 only + $json = \json_encode($input); + } if ($errno = \json_last_error()) { - static::handleJsonError($errno); - } elseif ($json === 'null' && $input !== null) { + self::handleJsonError($errno); + } elseif ($json === 'null') { throw new DomainException('Null result with non-null input'); } + if ($json === false) { + throw new DomainException('Provided object could not be encoded to valid JSON'); + } return $json; } @@ -359,8 +395,10 @@ class JWT * @param string $input A Base64 encoded string * * @return string A decoded string + * + * @throws InvalidArgumentException invalid base64 characters */ - public static function urlsafeB64Decode($input) + public static function urlsafeB64Decode(string $input): string { $remainder = \strlen($input) % 4; if ($remainder) { @@ -377,7 +415,7 @@ class JWT * * @return string The base64 encode of what you passed in */ - public static function urlsafeB64Encode($input) + public static function urlsafeB64Encode(string $input): string { return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); } @@ -386,67 +424,54 @@ class JWT /** * Determine if an algorithm has been provided for each Key * - * @param Key|array|mixed $keyOrKeyArray - * @param string|null $kid + * @param Key|ArrayAccess|array $keyOrKeyArray + * @param string|null $kid * * @throws UnexpectedValueException * - * @return array containing the keyMaterial and algorithm + * @return Key */ - private static function getKeyMaterialAndAlgorithm($keyOrKeyArray, $kid = null) - { - if ( - is_string($keyOrKeyArray) - || is_resource($keyOrKeyArray) - || $keyOrKeyArray instanceof OpenSSLAsymmetricKey - ) { - return array($keyOrKeyArray, null); - } - + private static function getKey( + $keyOrKeyArray, + ?string $kid + ): Key { if ($keyOrKeyArray instanceof Key) { - return array($keyOrKeyArray->getKeyMaterial(), $keyOrKeyArray->getAlgorithm()); + return $keyOrKeyArray; } - if (is_array($keyOrKeyArray) || $keyOrKeyArray instanceof ArrayAccess) { - if (!isset($kid)) { - throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); - } - if (!isset($keyOrKeyArray[$kid])) { - throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); - } - - $key = $keyOrKeyArray[$kid]; - - if ($key instanceof Key) { - return array($key->getKeyMaterial(), $key->getAlgorithm()); - } - - return array($key, null); + if (empty($kid) && $kid !== '0') { + throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); } - throw new UnexpectedValueException( - '$keyOrKeyArray must be a string|resource key, an array of string|resource keys, ' - . 'an instance of Firebase\JWT\Key key or an array of Firebase\JWT\Key keys' - ); + if ($keyOrKeyArray instanceof CachedKeySet) { + // Skip "isset" check, as this will automatically refresh if not set + return $keyOrKeyArray[$kid]; + } + + if (!isset($keyOrKeyArray[$kid])) { + throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); + } + + return $keyOrKeyArray[$kid]; } /** - * @param string $left - * @param string $right + * @param string $left The string of known length to compare against + * @param string $right The user-supplied string * @return bool */ - public static function constantTimeEquals($left, $right) + public static function constantTimeEquals(string $left, string $right): bool { if (\function_exists('hash_equals')) { return \hash_equals($left, $right); } - $len = \min(static::safeStrlen($left), static::safeStrlen($right)); + $len = \min(self::safeStrlen($left), self::safeStrlen($right)); $status = 0; for ($i = 0; $i < $len; $i++) { $status |= (\ord($left[$i]) ^ \ord($right[$i])); } - $status |= (static::safeStrlen($left) ^ static::safeStrlen($right)); + $status |= (self::safeStrlen($left) ^ self::safeStrlen($right)); return ($status === 0); } @@ -456,17 +481,19 @@ class JWT * * @param int $errno An error number from json_last_error() * + * @throws DomainException + * * @return void */ - private static function handleJsonError($errno) + private static function handleJsonError(int $errno): void { - $messages = array( + $messages = [ JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3 - ); + ]; throw new DomainException( isset($messages[$errno]) ? $messages[$errno] @@ -481,7 +508,7 @@ class JWT * * @return int */ - private static function safeStrlen($str) + private static function safeStrlen(string $str): int { if (\function_exists('mb_strlen')) { return \mb_strlen($str, '8bit'); @@ -495,10 +522,11 @@ class JWT * @param string $sig The ECDSA signature to convert * @return string The encoded DER object */ - private static function signatureToDER($sig) + private static function signatureToDER(string $sig): string { // Separate the signature into r-value and s-value - list($r, $s) = \str_split($sig, (int) (\strlen($sig) / 2)); + $length = max(1, (int) (\strlen($sig) / 2)); + list($r, $s) = \str_split($sig, $length); // Trim leading zeros $r = \ltrim($r, "\x00"); @@ -525,9 +553,10 @@ class JWT * * @param int $type DER tag * @param string $value the value to encode + * * @return string the encoded object */ - private static function encodeDER($type, $value) + private static function encodeDER(int $type, string $value): string { $tag_header = 0; if ($type === self::ASN1_SEQUENCE) { @@ -548,9 +577,10 @@ class JWT * * @param string $der binary signature in DER format * @param int $keySize the number of bits in the key + * * @return string the signature */ - private static function signatureFromDER($der, $keySize) + private static function signatureFromDER(string $der, int $keySize): string { // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE list($offset, $_) = self::readDER($der); @@ -575,9 +605,10 @@ class JWT * @param string $der the binary data in DER format * @param int $offset the offset of the data stream containing the object * to decode - * @return array [$offset, $data] the new offset and the decoded object + * + * @return array{int, string|null} the new offset and the decoded object */ - private static function readDER($der, $offset = 0) + private static function readDER(string $der, int $offset = 0): array { $pos = $offset; $size = \strlen($der); @@ -595,7 +626,7 @@ class JWT } // Value - if ($type == self::ASN1_BIT_STRING) { + if ($type === self::ASN1_BIT_STRING) { $pos++; // Skip the first contents octet (padding indicator) $data = \substr($der, $pos, $len - 1); $pos += $len - 1; @@ -606,6 +637,6 @@ class JWT $data = null; } - return array($pos, $data); + return [$pos, $data]; } } diff --git a/data/web/inc/lib/vendor/firebase/php-jwt/src/Key.php b/data/web/inc/lib/vendor/firebase/php-jwt/src/Key.php index f1ede6f27..00cf7f2ed 100644 --- a/data/web/inc/lib/vendor/firebase/php-jwt/src/Key.php +++ b/data/web/inc/lib/vendor/firebase/php-jwt/src/Key.php @@ -4,37 +4,42 @@ namespace Firebase\JWT; use InvalidArgumentException; use OpenSSLAsymmetricKey; +use OpenSSLCertificate; +use TypeError; class Key { - /** @var string $algorithm */ + /** @var string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate */ + private $keyMaterial; + /** @var string */ private $algorithm; - /** @var string|resource|OpenSSLAsymmetricKey $keyMaterial */ - private $keyMaterial; - /** - * @param string|resource|OpenSSLAsymmetricKey $keyMaterial + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial * @param string $algorithm */ - public function __construct($keyMaterial, $algorithm) - { + public function __construct( + $keyMaterial, + string $algorithm + ) { if ( - !is_string($keyMaterial) - && !is_resource($keyMaterial) + !\is_string($keyMaterial) && !$keyMaterial instanceof OpenSSLAsymmetricKey + && !$keyMaterial instanceof OpenSSLCertificate + && !\is_resource($keyMaterial) ) { - throw new InvalidArgumentException('Type error: $keyMaterial must be a string, resource, or OpenSSLAsymmetricKey'); + throw new TypeError('Key material must be a string, resource, or OpenSSLAsymmetricKey'); } if (empty($keyMaterial)) { - throw new InvalidArgumentException('Type error: $keyMaterial must not be empty'); + throw new InvalidArgumentException('Key material must not be empty'); } - if (!is_string($algorithm)|| empty($keyMaterial)) { - throw new InvalidArgumentException('Type error: $algorithm must be a string'); + if (empty($algorithm)) { + throw new InvalidArgumentException('Algorithm must not be empty'); } + // TODO: Remove in PHP 8.0 in favor of class constructor property promotion $this->keyMaterial = $keyMaterial; $this->algorithm = $algorithm; } @@ -44,13 +49,13 @@ class Key * * @return string */ - public function getAlgorithm() + public function getAlgorithm(): string { return $this->algorithm; } /** - * @return string|resource|OpenSSLAsymmetricKey + * @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate */ public function getKeyMaterial() { diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/.travis.yml b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/.travis.yml index 87831a56c..c5f3825a3 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/.travis.yml +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/.travis.yml @@ -3,9 +3,12 @@ language: php sudo: false php: - - 5.6 - - 7.0 - - 7.1 + - 7.2 + - 7.3 + - 7.4 + - 8.0 + - 8.1 + - 8.2 matrix: include: diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/README.md b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/README.md index e050a6d0a..d0b1acaf3 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/README.md +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/README.md @@ -36,6 +36,7 @@ $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([ 'encryptionAlgorithm' => 'RS256', // optional 'encryptionKeyPath' => '../key.pem' // optional 'encryptionKey' => 'contents_of_key_or_certificate' // optional + 'version' => '20.0.1', // optional ]); if (!isset($_GET['code'])) { diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/composer.json b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/composer.json index 958f8c3af..b6a54477e 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/composer.json +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/composer.json @@ -18,13 +18,14 @@ "keycloak" ], "require": { + "php": "~7.2 || ~8.0", "league/oauth2-client": "^2.0", - "firebase/php-jwt": "~4.0|~5.0" + "firebase/php-jwt": "^4.0 || ^5.0 || ^6.0" }, "require-dev": { - "phpunit/phpunit": "~4.0", - "mockery/mockery": "~0.9", - "squizlabs/php_codesniffer": "~2.0" + "phpunit/phpunit": "~9.6.4", + "mockery/mockery": "~1.5.0", + "squizlabs/php_codesniffer": "~3.7.0" }, "autoload": { "psr-4": { @@ -40,5 +41,11 @@ "branch-alias": { "dev-master": "1.0.x-dev" } + }, + "scripts": { + "test": [ + "@putenv XDEBUG_MODE=coverage", + "phpunit --colors=always" + ] } -} +} \ No newline at end of file diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/phpunit.xml.dist b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/phpunit.xml.dist index 04d164dc2..b915f933b 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/phpunit.xml.dist +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/phpunit.xml.dist @@ -1,38 +1,33 @@ - - - - + + + src + + + vendor + src/autoload.php + + + + + + ./test/ - - - ./ - - ./examples - ./vendor - ./test - - - diff --git a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/test/src/Provider/KeycloakTest.php b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/test/src/Provider/KeycloakTest.php index 01f6b62dd..a5c1da14f 100644 --- a/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/test/src/Provider/KeycloakTest.php +++ b/data/web/inc/lib/vendor/stevenmaguire/oauth2-keycloak/test/src/Provider/KeycloakTest.php @@ -23,18 +23,22 @@ namespace Stevenmaguire\OAuth2\Client\Provider namespace Stevenmaguire\OAuth2\Client\Test\Provider { + use League\OAuth2\Client\Provider\Exception\IdentityProviderException; use League\OAuth2\Client\Tool\QueryBuilderTrait; use Mockery as m; + use PHPUnit\Framework\TestCase; + use Stevenmaguire\OAuth2\Client\Provider\Exception\EncryptionConfigurationException; + use Stevenmaguire\OAuth2\Client\Provider\Keycloak; - class KeycloakTest extends \PHPUnit_Framework_TestCase + class KeycloakTest extends TestCase { use QueryBuilderTrait; protected $provider; - protected function setUp() + protected function setUp(): void { - $this->provider = new \Stevenmaguire\OAuth2\Client\Provider\Keycloak([ + $this->provider = new Keycloak([ 'authServerUrl' => 'http://mock.url/auth', 'realm' => 'mock_realm', 'clientId' => 'mock_client_id', @@ -43,7 +47,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider ]); } - public function tearDown() + public function tearDown(): void { m::close(); parent::tearDown(); @@ -67,7 +71,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider public function testEncryptionAlgorithm() { $algorithm = uniqid(); - $provider = new \Stevenmaguire\OAuth2\Client\Provider\Keycloak([ + $provider = new Keycloak([ 'encryptionAlgorithm' => $algorithm, ]); @@ -82,7 +86,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider public function testEncryptionKey() { $key = uniqid(); - $provider = new \Stevenmaguire\OAuth2\Client\Provider\Keycloak([ + $provider = new Keycloak([ 'encryptionKey' => $key, ]); @@ -101,7 +105,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $key = uniqid(); $mockFileGetContents = $key; - $provider = new \Stevenmaguire\OAuth2\Client\Provider\Keycloak([ + $provider = new Keycloak([ 'encryptionKeyPath' => $path, ]); @@ -118,12 +122,14 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider public function testEncryptionKeyPathFails() { + $this->markTestIncomplete('Need to assess the test to see what is required to be checked.'); + global $mockFileGetContents; $path = uniqid(); $key = uniqid(); $mockFileGetContents = new \Exception(); - $provider = new \Stevenmaguire\OAuth2\Client\Provider\Keycloak([ + $provider = new Keycloak([ 'encryptionKeyPath' => $path, ]); @@ -137,7 +143,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $query = ['scope' => implode($scopeSeparator, $options['scope'])]; $url = $this->provider->getAuthorizationUrl($options); $encodedScope = $this->buildQueryString($query); - $this->assertContains($encodedScope, $url); + $this->assertStringContainsString($encodedScope, $url); } public function testGetAuthorizationUrl() @@ -169,11 +175,15 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider public function testGetAccessToken() { $response = m::mock('Psr\Http\Message\ResponseInterface'); - $response->shouldReceive('getBody')->andReturn('{"access_token":"mock_access_token", "scope":"email", "token_type":"bearer"}'); - $response->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); + $response->shouldReceive('getBody') + ->andReturn('{"access_token":"mock_access_token", "scope":"email", "token_type":"bearer"}'); + $response->shouldReceive('getHeader') + ->andReturn(['content-type' => 'json']); $client = m::mock('GuzzleHttp\ClientInterface'); - $client->shouldReceive('send')->times(1)->andReturn($response); + $client->shouldReceive('send') + ->times(1) + ->andReturn($response); $this->provider->setHttpClient($client); $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']); @@ -186,18 +196,24 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider public function testUserData() { - $userId = rand(1000,9999); + $userId = rand(1000, 9999); $name = uniqid(); $nickname = uniqid(); $email = uniqid(); $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $postResponse->shouldReceive('getBody')->andReturn('access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}'); - $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/x-www-form-urlencoded']); + $postResponse->shouldReceive('getBody') + ->andReturn( + 'access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}' + ); + $postResponse->shouldReceive('getHeader') + ->andReturn(['content-type' => 'application/x-www-form-urlencoded']); $userResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $userResponse->shouldReceive('getBody')->andReturn('{"sub": '.$userId.', "name": "'.$name.'", "email": "'.$email.'"}'); - $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); + $userResponse->shouldReceive('getBody') + ->andReturn('{"sub": '.$userId.', "name": "'.$name.'", "email": "'.$email.'"}'); + $userResponse->shouldReceive('getHeader') + ->andReturn(['content-type' => 'json']); $client = m::mock('GuzzleHttp\ClientInterface'); $client->shouldReceive('send') @@ -218,7 +234,7 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider public function testUserDataWithEncryption() { - $userId = rand(1000,9999); + $userId = rand(1000, 9999); $name = uniqid(); $nickname = uniqid(); $email = uniqid(); @@ -227,21 +243,31 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $key = uniqid(); $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $postResponse->shouldReceive('getBody')->andReturn('access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}'); - $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/x-www-form-urlencoded']); - $postResponse->shouldReceive('getStatusCode')->andReturn(200); + $postResponse->shouldReceive('getBody') + ->andReturn( + 'access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}' + ); + $postResponse->shouldReceive('getHeader') + ->andReturn(['content-type' => 'application/x-www-form-urlencoded']); + $postResponse->shouldReceive('getStatusCode') + ->andReturn(200); $userResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $userResponse->shouldReceive('getBody')->andReturn($jwt); - $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/jwt']); - $userResponse->shouldReceive('getStatusCode')->andReturn(200); + $userResponse->shouldReceive('getBody') + ->andReturn($jwt); + $userResponse->shouldReceive('getHeader') + ->andReturn(['content-type' => 'application/jwt']); + $userResponse->shouldReceive('getStatusCode') + ->andReturn(200); $decoder = \Mockery::mock('overload:Firebase\JWT\JWT'); - $decoder->shouldReceive('decode')->with($jwt, $key, [$algorithm])->andReturn([ - 'sub' => $userId, - 'email' => $email, - 'name' => $name, - ]); + $decoder->shouldReceive('decode') + ->with($jwt, $key, [$algorithm]) + ->andReturn([ + 'sub' => $userId, + 'email' => $email, + 'name' => $name, + ]); $client = m::mock('GuzzleHttp\ClientInterface'); $client->shouldReceive('send') @@ -262,20 +288,27 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $this->assertEquals($email, $user->toArray()['email']); } - /** - * @expectedException Stevenmaguire\OAuth2\Client\Provider\Exception\EncryptionConfigurationException - */ public function testUserDataFailsWhenEncryptionEncounteredAndNotConfigured() { + $this->expectException(EncryptionConfigurationException::class); + $postResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $postResponse->shouldReceive('getBody')->andReturn('access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}'); - $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/x-www-form-urlencoded']); - $postResponse->shouldReceive('getStatusCode')->andReturn(200); + $postResponse->shouldReceive('getBody') + ->andReturn( + 'access_token=mock_access_token&expires=3600&refresh_token=mock_refresh_token&otherKey={1234}' + ); + $postResponse->shouldReceive('getHeader') + ->andReturn(['content-type' => 'application/x-www-form-urlencoded']); + $postResponse->shouldReceive('getStatusCode') + ->andReturn(200); $userResponse = m::mock('Psr\Http\Message\ResponseInterface'); - $userResponse->shouldReceive('getBody')->andReturn(uniqid()); - $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'application/jwt']); - $userResponse->shouldReceive('getStatusCode')->andReturn(200); + $userResponse->shouldReceive('getBody') + ->andReturn(uniqid()); + $userResponse->shouldReceive('getHeader') + ->andReturn(['content-type' => 'application/jwt']); + $userResponse->shouldReceive('getStatusCode') + ->andReturn(200); $client = m::mock('GuzzleHttp\ClientInterface'); $client->shouldReceive('send') @@ -287,17 +320,20 @@ namespace Stevenmaguire\OAuth2\Client\Test\Provider $user = $this->provider->getResourceOwner($token); } - /** - * @expectedException League\OAuth2\Client\Provider\Exception\IdentityProviderException - */ public function testErrorResponse() { + $this->expectException(IdentityProviderException::class); + $response = m::mock('Psr\Http\Message\ResponseInterface'); - $response->shouldReceive('getBody')->andReturn('{"error": "invalid_grant", "error_description": "Code not found"}'); - $response->shouldReceive('getHeader')->andReturn(['content-type' => 'json']); + $response->shouldReceive('getBody') + ->andReturn('{"error": "invalid_grant", "error_description": "Code not found"}'); + $response->shouldReceive('getHeader') + ->andReturn(['content-type' => 'json']); $client = m::mock('GuzzleHttp\ClientInterface'); - $client->shouldReceive('send')->times(1)->andReturn($response); + $client->shouldReceive('send') + ->times(1) + ->andReturn($response); $this->provider->setHttpClient($client); $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);