jenkins-bot has submitted this change and it was merged. Change subject: Add firebase/php-jwt for ContentTranslation ......................................................................
Add firebase/php-jwt for ContentTranslation Used in https://gerrit.wikimedia.org/r/#/c/219194/ Bug: T97113 Bug: T106762 Change-Id: I58af86204981a9f038b93df40e5c6c2f51fe06ae --- M composer.json M composer.lock M composer/autoload_classmap.php M composer/installed.json A firebase/php-jwt/.gitignore A firebase/php-jwt/.travis.yml A firebase/php-jwt/Authentication/JWT.php A firebase/php-jwt/Exceptions/BeforeValidException.php A firebase/php-jwt/Exceptions/ExpiredException.php A firebase/php-jwt/Exceptions/SignatureInvalidException.php A firebase/php-jwt/LICENSE A firebase/php-jwt/README.md A firebase/php-jwt/composer.json A firebase/php-jwt/package.xml A firebase/php-jwt/phpunit.xml.dist A firebase/php-jwt/run-tests.sh A firebase/php-jwt/tests/JWTTest.php A firebase/php-jwt/tests/autoload.php.dist A firebase/php-jwt/tests/bootstrap.php 19 files changed, 1,001 insertions(+), 1 deletion(-) Approvals: Legoktm: Looks good to me, approved jenkins-bot: Verified diff --git a/composer.json b/composer.json index 723d42f..a5090d8 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "require": { "composer/semver": "0.1.0", "cssjanus/cssjanus": "1.1.1", + "firebase/php-jwt": "2.1.0", "kzykhys/pygments": "1.0", "leafo/lessphp": "0.5.0", "liuggio/statsd-php-client": "1.0.16", diff --git a/composer.lock b/composer.lock index 5623eb1..7dbbc98 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "7d6cf00035f6a21a54aa0f9ba8a9101e", + "hash": "4985b96fdac3d7b3c1327fdcb1bd491f", "packages": [ { "name": "composer/semver", @@ -103,6 +103,50 @@ "time": "2014-11-14 20:00:50" }, { + "name": "firebase/php-jwt", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "fb219727e199dd80a72d5274ebb5c8b24d58dd9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/fb219727e199dd80a72d5274ebb5c8b24d58dd9b", + "reference": "fb219727e199dd80a72d5274ebb5c8b24d58dd9b", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "Authentication/", + "Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "[email protected]", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "[email protected]", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "time": "2015-05-20 19:16:04" + }, + { "name": "kzykhys/pygments", "version": "v1.0.0", "source": { diff --git a/composer/autoload_classmap.php b/composer/autoload_classmap.php index 285464b..3d8de99 100644 --- a/composer/autoload_classmap.php +++ b/composer/autoload_classmap.php @@ -6,6 +6,7 @@ $baseDir = $vendorDir; return array( + 'BeforeValidException' => $vendorDir . '/firebase/php-jwt/Exceptions/BeforeValidException.php', 'CSSJanus' => $vendorDir . '/cssjanus/cssjanus/src/CSSJanus.php', 'CSSJanusTokenizer' => $vendorDir . '/cssjanus/cssjanus/src/CSSJanus.php', 'Cdb\\Exception' => $vendorDir . '/wikimedia/cdb/src/Exception.php', @@ -238,7 +239,9 @@ 'Elastica\\Type\\AbstractType' => $vendorDir . '/ruflin/elastica/lib/Elastica/Type/AbstractType.php', 'Elastica\\Type\\Mapping' => $vendorDir . '/ruflin/elastica/lib/Elastica/Type/Mapping.php', 'Elastica\\Util' => $vendorDir . '/ruflin/elastica/lib/Elastica/Util.php', + 'ExpiredException' => $vendorDir . '/firebase/php-jwt/Exceptions/ExpiredException.php', 'IPSet\\IPSet' => $vendorDir . '/wikimedia/ip-set/src/IPSet.php', + 'JWT' => $vendorDir . '/firebase/php-jwt/Authentication/JWT.php', 'KzykHys\\Pygments\\Pygments' => $vendorDir . '/kzykhys/pygments/src/KzykHys/Pygments/Pygments.php', 'LCRun3' => $vendorDir . '/zordius/lightncandy/src/lightncandy.php', 'LightnCandy' => $vendorDir . '/zordius/lightncandy/src/lightncandy.php', @@ -379,6 +382,7 @@ 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', 'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', 'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'SignatureInvalidException' => $vendorDir . '/firebase/php-jwt/Exceptions/SignatureInvalidException.php', 'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/process/Exception/ExceptionInterface.php', 'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/process/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Process\\Exception\\LogicException' => $vendorDir . '/symfony/process/Exception/LogicException.php', diff --git a/composer/installed.json b/composer/installed.json index 1386097..97f95f3 100644 --- a/composer/installed.json +++ b/composer/installed.json @@ -865,5 +865,51 @@ ], "description": "Provides library of common widgets, layouts, and windows.", "homepage": "https://www.mediawiki.org/wiki/OOjs_UI" + }, + { + "name": "firebase/php-jwt", + "version": "v2.1.0", + "version_normalized": "2.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "fb219727e199dd80a72d5274ebb5c8b24d58dd9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/fb219727e199dd80a72d5274ebb5c8b24d58dd9b", + "reference": "fb219727e199dd80a72d5274ebb5c8b24d58dd9b", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "time": "2015-05-20 19:16:04", + "type": "library", + "installation-source": "dist", + "autoload": { + "classmap": [ + "Authentication/", + "Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "[email protected]", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "[email protected]", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt" } ] diff --git a/firebase/php-jwt/.gitignore b/firebase/php-jwt/.gitignore new file mode 100644 index 0000000..96e2f2e --- /dev/null +++ b/firebase/php-jwt/.gitignore @@ -0,0 +1,3 @@ +vendor +phpunit.phar +phpunit.phar.asc diff --git a/firebase/php-jwt/.travis.yml b/firebase/php-jwt/.travis.yml new file mode 100644 index 0000000..0bd12c5 --- /dev/null +++ b/firebase/php-jwt/.travis.yml @@ -0,0 +1,13 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - hhvm + +before_script: + - wget -nc http://getcomposer.org/composer.phar + - php composer.phar install + +script: phpunit --configuration phpunit.xml.dist diff --git a/firebase/php-jwt/Authentication/JWT.php b/firebase/php-jwt/Authentication/JWT.php new file mode 100644 index 0000000..7d6665b --- /dev/null +++ b/firebase/php-jwt/Authentication/JWT.php @@ -0,0 +1,334 @@ +<?php + +/** + * JSON Web Token implementation, based on this spec: + * http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06 + * + * PHP version 5 + * + * @category Authentication + * @package Authentication_JWT + * @author Neuman Vong <[email protected]> + * @author Anant Narayanan <[email protected]> + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWT +{ + + /** + * When checking nbf, iat or expiration times, + * we want to provide some extra leeway time to + * account for clock skew. + */ + public static $leeway = 0; + + public static $supported_algs = array( + 'HS256' => array('hash_hmac', 'SHA256'), + 'HS512' => array('hash_hmac', 'SHA512'), + 'HS384' => array('hash_hmac', 'SHA384'), + 'RS256' => array('openssl', 'SHA256'), + ); + + /** + * Decodes a JWT string into a PHP object. + * + * @param string $jwt The JWT + * @param string|Array|null $key The secret key, or map of keys + * @param Array $allowed_algs List of supported verification algorithms + * + * @return object The JWT's payload as a PHP object + * + * @throws DomainException Algorithm was not provided + * @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' + * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat' + * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim + * + * @uses jsonDecode + * @uses urlsafeB64Decode + */ + public static function decode($jwt, $key = null, $allowed_algs = array()) + { + $tks = explode('.', $jwt); + if (count($tks) != 3) { + throw new UnexpectedValueException('Wrong number of segments'); + } + list($headb64, $bodyb64, $cryptob64) = $tks; + if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) { + throw new UnexpectedValueException('Invalid header encoding'); + } + if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64))) { + throw new UnexpectedValueException('Invalid claims encoding'); + } + $sig = JWT::urlsafeB64Decode($cryptob64); + if (isset($key)) { + if (empty($header->alg)) { + throw new DomainException('Empty algorithm'); + } + if (empty(self::$supported_algs[$header->alg])) { + throw new DomainException('Algorithm not supported'); + } + if (!is_array($allowed_algs) || !in_array($header->alg, $allowed_algs)) { + throw new DomainException('Algorithm not allowed'); + } + if (is_array($key) || $key instanceof \ArrayAccess) { + if (isset($header->kid)) { + $key = $key[$header->kid]; + } else { + throw new DomainException('"kid" empty, unable to lookup correct key'); + } + } + + // Check the signature + if (!JWT::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) { + throw new SignatureInvalidException('Signature verification failed'); + } + + // Check if the nbf if it is defined. This is the time that the + // token can actually be used. If it's not yet that time, abort. + if (isset($payload->nbf) && $payload->nbf > (time() + self::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf) + ); + } + + // Check that this token has been created before 'now'. This prevents + // using tokens that have been created for later use (and haven't + // correctly used the nbf claim). + if (isset($payload->iat) && $payload->iat > (time() + self::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat) + ); + } + + // Check if this token has expired. + if (isset($payload->exp) && (time() - self::$leeway) >= $payload->exp) { + throw new ExpiredException('Expired token'); + } + } + + return $payload; + } + + /** + * Converts and signs a PHP object or array into a JWT string. + * + * @param object|array $payload PHP object or array + * @param string $key The secret key + * @param string $alg The signing algorithm. Supported + * algorithms are 'HS256', 'HS384' and 'HS512' + * + * @return string A signed JWT + * @uses jsonEncode + * @uses urlsafeB64Encode + */ + public static function encode($payload, $key, $alg = 'HS256', $keyId = null) + { + $header = array('typ' => 'JWT', 'alg' => $alg); + if ($keyId !== null) { + $header['kid'] = $keyId; + } + $segments = array(); + $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header)); + $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload)); + $signing_input = implode('.', $segments); + + $signature = JWT::sign($signing_input, $key, $alg); + $segments[] = JWT::urlsafeB64Encode($signature); + + return implode('.', $segments); + } + + /** + * 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 'HS256', 'HS384', 'HS512' and 'RS256' + * + * @return string An encrypted message + * @throws DomainException Unsupported algorithm was specified + */ + public static function sign($msg, $key, $alg = 'HS256') + { + if (empty(self::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + list($function, $algorithm) = self::$supported_algs[$alg]; + switch($function) { + case 'hash_hmac': + return hash_hmac($algorithm, $msg, $key, true); + case 'openssl': + $signature = ''; + $success = openssl_sign($msg, $signature, $key, $algorithm); + if (!$success) { + throw new DomainException("OpenSSL unable to sign data"); + } else { + return $signature; + } + } + } + + /** + * Verify a signature with the mesage, key and method. Not all methods + * are symmetric, so we must have a separate verify and sign method. + * @param string $msg the original message + * @param string $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 + * @return bool + * @throws DomainException Invalid Algorithm or OpenSSL failure + */ + private static function verify($msg, $signature, $key, $alg) + { + if (empty(self::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + + list($function, $algorithm) = self::$supported_algs[$alg]; + switch($function) { + case 'openssl': + $success = openssl_verify($msg, $signature, $key, $algorithm); + if (!$success) { + throw new DomainException("OpenSSL unable to verify data: " . openssl_error_string()); + } else { + return $signature; + } + case 'hash_hmac': + default: + $hash = hash_hmac($algorithm, $msg, $key, true); + if (function_exists('hash_equals')) { + return hash_equals($signature, $hash); + } + $len = min(self::safeStrlen($signature), self::safeStrlen($hash)); + + $status = 0; + for ($i = 0; $i < $len; $i++) { + $status |= (ord($signature[$i]) ^ ord($hash[$i])); + } + $status |= (self::safeStrlen($signature) ^ self::safeStrlen($hash)); + + return ($status === 0); + } + } + + /** + * Decode a JSON string into a PHP object. + * + * @param string $input JSON string + * + * @return object Object representation of JSON string + * @throws DomainException Provided string was invalid JSON + */ + public static function jsonDecode($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); + } + + if (function_exists('json_last_error') && $errno = json_last_error()) { + JWT::handleJsonError($errno); + } elseif ($obj === null && $input !== 'null') { + throw new DomainException('Null result with non-null input'); + } + return $obj; + } + + /** + * Encode a PHP object into a JSON string. + * + * @param object|array $input A PHP object or array + * + * @return string JSON representation of the PHP object or array + * @throws DomainException Provided object could not be encoded to valid JSON + */ + public static function jsonEncode($input) + { + $json = json_encode($input); + if (function_exists('json_last_error') && $errno = json_last_error()) { + JWT::handleJsonError($errno); + } elseif ($json === 'null' && $input !== null) { + throw new DomainException('Null result with non-null input'); + } + return $json; + } + + /** + * Decode a string with URL-safe Base64. + * + * @param string $input A Base64 encoded string + * + * @return string A decoded string + */ + public static function urlsafeB64Decode($input) + { + $remainder = strlen($input) % 4; + if ($remainder) { + $padlen = 4 - $remainder; + $input .= str_repeat('=', $padlen); + } + return base64_decode(strtr($input, '-_', '+/')); + } + + /** + * Encode a string with URL-safe Base64. + * + * @param string $input The string you want encoded + * + * @return string The base64 encode of what you passed in + */ + public static function urlsafeB64Encode($input) + { + return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); + } + + /** + * Helper method to create a JSON error. + * + * @param int $errno An error number from json_last_error() + * + * @return void + */ + private static function handleJsonError($errno) + { + $messages = array( + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON' + ); + throw new DomainException( + isset($messages[$errno]) + ? $messages[$errno] + : 'Unknown JSON error: ' . $errno + ); + } + + /** + * Get the number of bytes in cryptographic strings. + * + * @param string + * @return int + */ + private static function safeStrlen($str) + { + if (function_exists('mb_strlen')) { + return mb_strlen($str, '8bit'); + } + return strlen($str); + } +} diff --git a/firebase/php-jwt/Exceptions/BeforeValidException.php b/firebase/php-jwt/Exceptions/BeforeValidException.php new file mode 100644 index 0000000..5a84975 --- /dev/null +++ b/firebase/php-jwt/Exceptions/BeforeValidException.php @@ -0,0 +1,6 @@ +<?php + +class BeforeValidException extends UnexpectedValueException +{ + +} diff --git a/firebase/php-jwt/Exceptions/ExpiredException.php b/firebase/php-jwt/Exceptions/ExpiredException.php new file mode 100644 index 0000000..bd80468 --- /dev/null +++ b/firebase/php-jwt/Exceptions/ExpiredException.php @@ -0,0 +1,6 @@ +<?php + +class ExpiredException extends UnexpectedValueException +{ + +} diff --git a/firebase/php-jwt/Exceptions/SignatureInvalidException.php b/firebase/php-jwt/Exceptions/SignatureInvalidException.php new file mode 100644 index 0000000..d122232 --- /dev/null +++ b/firebase/php-jwt/Exceptions/SignatureInvalidException.php @@ -0,0 +1,6 @@ +<?php + +class SignatureInvalidException extends UnexpectedValueException +{ + +} diff --git a/firebase/php-jwt/LICENSE b/firebase/php-jwt/LICENSE new file mode 100644 index 0000000..cb0c49b --- /dev/null +++ b/firebase/php-jwt/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2011, Neuman Vong + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Neuman Vong nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/firebase/php-jwt/README.md b/firebase/php-jwt/README.md new file mode 100644 index 0000000..00bad2e --- /dev/null +++ b/firebase/php-jwt/README.md @@ -0,0 +1,93 @@ +[](https://travis-ci.org/firebase/php-jwt) + +PHP-JWT +======= +A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should +conform to the [current spec](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06) + +Installation +------------ + +Use composer to manage your dependencies and download PHP-JWT: + +```bash +composer require firebase/php-jwt +``` + +Example +------- +```php +<?php + +$key = "example_key"; +$token = array( + "iss" => "http://example.org", + "aud" => "http://example.com", + "iat" => 1356999524, + "nbf" => 1357000000 +); + +/** + * IMPORTANT: + * You must specify supported algorithms for your application. See + * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 + * for a list of spec-compliant algorithms. + */ +$jwt = JWT::encode($token, $key); +$decoded = JWT::decode($jwt, $key, array('HS256')); + +print_r($decoded); + +/* + NOTE: This will now be an object instead of an associative array. To get + an associative array, you will need to cast it as such: +*/ + +$decoded_array = (array) $decoded; + +/** + * You can add a leeway to account for when there is a clock skew times between + * the signing and verifying servers. It is recommended that this leeway should + * not be bigger than a few minutes. + * + * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef + */ +JWT::$leeway = 60; // $leeway in seconds +$decoded = JWT::decode($jwt, $key, array('HS256')); + +?> +``` + +Changelog +--------- + +#### 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. + + +Tests +----- +Run the tests using phpunit: + +```bash +$ pear install PHPUnit +$ phpunit --configuration phpunit.xml.dist +PHPUnit 3.7.10 by Sebastian Bergmann. +..... +Time: 0 seconds, Memory: 2.50Mb +OK (5 tests, 5 assertions) +``` + +License +------- +[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause). diff --git a/firebase/php-jwt/composer.json b/firebase/php-jwt/composer.json new file mode 100644 index 0000000..95560af --- /dev/null +++ b/firebase/php-jwt/composer.json @@ -0,0 +1,25 @@ +{ + "name": "firebase/php-jwt", + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "authors": [ + { + "name": "Neuman Vong", + "email": "[email protected]", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "[email protected]", + "role": "Developer" + } + ], + "license": "BSD-3-Clause", + "require": { + "php": ">=5.2.0" + }, + "autoload": { + "classmap": ["Authentication/", "Exceptions/"] + }, + "minimum-stability": "dev" +} diff --git a/firebase/php-jwt/package.xml b/firebase/php-jwt/package.xml new file mode 100644 index 0000000..b40da26 --- /dev/null +++ b/firebase/php-jwt/package.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<package packagerversion="1.9.2" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 + http://pear.php.net/dtd/tasks-1.0.xsd + http://pear.php.net/dtd/package-2.0 + http://pear.php.net/dtd/package-2.0.xsd"> + <name>JWT</name> + <channel>pear.php.net</channel> + <summary>A JWT encoder/decoder.</summary> + <description>A JWT encoder/decoder library for PHP.</description> + <lead> + <name>Neuman Vong</name> + <user>lcfrs</user> + <email>[email protected]</email> + <active>yes</active> + </lead> + <lead> + <name>Firebase Operations</name> + <user>firebase</user> + <email>[email protected]</email> + <active>yes</active> + </lead> + <date>2015-05-20</date> + <version> + <release>2.1.0</release> + <api>2.1.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <license uri="http://opensource.org/licenses/BSD-3-Clause">BSD 3-Clause License</license> + <notes> +Initial release with basic support for JWT encoding, decoding and signature verification. + </notes> + <contents> + <dir baseinstalldir="/" name="/"> + <dir name="tests"> + <file name="JWTTest.php" role="test" /> + </dir> + <file name="Authentication/JWT.php" role="php" /> + </dir> + </contents> + <dependencies> + <required> + <php> + <min>5.1</min> + </php> + <pearinstaller> + <min>1.7.0</min> + </pearinstaller> + <extension> + <name>json</name> + </extension> + <extension> + <name>hash</name> + </extension> + </required> + </dependencies> + <phprelease /> + <changelog> + <release> + <version> + <release>0.1.0</release> + <api>0.1.0</api> + </version> + <stability> + <release>beta</release> + <api>beta</api> + </stability> + <date>2015-04-01</date> + <license uri="http://opensource.org/licenses/BSD-3-Clause">BSD 3-Clause License</license> + <notes> +Initial release with basic support for JWT encoding, decoding and signature verification. + </notes> + </release> + </changelog> +</package> diff --git a/firebase/php-jwt/phpunit.xml.dist b/firebase/php-jwt/phpunit.xml.dist new file mode 100644 index 0000000..9f85f5b --- /dev/null +++ b/firebase/php-jwt/phpunit.xml.dist @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<phpunit backupGlobals="false" + backupStaticAttributes="false" + colors="true" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + processIsolation="false" + stopOnFailure="false" + syntaxCheck="false" + bootstrap="tests/bootstrap.php" +> + <testsuites> + <testsuite name="PHP JSON Web Token Test Suite"> + <directory>./tests</directory> + </testsuite> + </testsuites> +</phpunit> diff --git a/firebase/php-jwt/run-tests.sh b/firebase/php-jwt/run-tests.sh new file mode 100755 index 0000000..d37c30f --- /dev/null +++ b/firebase/php-jwt/run-tests.sh @@ -0,0 +1,38 @@ + +#!/usr/bin/env bash +gpg --fingerprint D8406D0D82947747293778314AA394086372C20A +if [ $? -ne 0 ]; then + echo -e "\033[33mDownloading PGP Public Key...\033[0m" + gpg --recv-keys D8406D0D82947747293778314AA394086372C20A + # Sebastian Bergmann <[email protected]> + gpg --fingerprint D8406D0D82947747293778314AA394086372C20A + if [ $? -ne 0 ]; then + echo -e "\033[31mCould not download PGP public key for verification\033[0m" + exit + fi +fi + +# Let's grab the latest release and its signature +if [ ! -f phpunit.phar ]; then + wget https://phar.phpunit.de/phpunit.phar +fi +if [ ! -f phpunit.phar.asc ]; then + wget https://phar.phpunit.de/phpunit.phar.asc +fi + +# Verify before running +gpg --verify phpunit.phar.asc phpunit.phar +if [ $? -eq 0 ]; then + echo + echo -e "\033[33mBegin Unit Testing\033[0m" + # Run the testing suite + php --version + php phpunit.phar --configuration phpunit.xml.dist +else + echo + chmod -x phpunit.phar + mv phpunit.phar /tmp/bad-phpunit.phar + mv phpunit.phar.asc /tmp/bad-phpunit.phar.asc + echo -e "\033[31mSignature did not match! PHPUnit has been moved to /tmp/bad-phpunit.phar\033[0m" + exit 1 +fi diff --git a/firebase/php-jwt/tests/JWTTest.php b/firebase/php-jwt/tests/JWTTest.php new file mode 100644 index 0000000..0605e4c --- /dev/null +++ b/firebase/php-jwt/tests/JWTTest.php @@ -0,0 +1,231 @@ +<?php + +class JWTTest extends PHPUnit_Framework_TestCase +{ + public function testEncodeDecode() + { + $msg = JWT::encode('abc', 'my_key'); + $this->assertEquals(JWT::decode($msg, 'my_key', array('HS256')), 'abc'); + } + + public function testDecodeFromPython() + { + $msg = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.Iio6aHR0cDovL2FwcGxpY2F0aW9uL2NsaWNreT9ibGFoPTEuMjMmZi5vbz00NTYgQUMwMDAgMTIzIg.E_U8X2YpMT5K1cEiT_3-IvBYfrdIFIeVYeOqre_Z5Cg'; + $this->assertEquals( + JWT::decode($msg, 'my_key', array('HS256')), + '*:http://application/clicky?blah=1.23&f.oo=456 AC000 123' + ); + } + + public function testUrlSafeCharacters() + { + $encoded = JWT::encode('f?', 'a'); + $this->assertEquals('f?', JWT::decode($encoded, 'a', array('HS256'))); + } + + public function testMalformedUtf8StringsFail() + { + $this->setExpectedException('DomainException'); + JWT::encode(pack('c', 128), 'a'); + } + + public function testMalformedJsonThrowsException() + { + $this->setExpectedException('DomainException'); + JWT::jsonDecode('this is not valid JSON string'); + } + + public function testExpiredToken() + { + $this->setExpectedException('ExpiredException'); + $payload = array( + "message" => "abc", + "exp" => time() - 20); // time in the past + $encoded = JWT::encode($payload, 'my_key'); + JWT::decode($encoded, 'my_key', array('HS256')); + } + + public function testBeforeValidTokenWithNbf() + { + $this->setExpectedException('BeforeValidException'); + $payload = array( + "message" => "abc", + "nbf" => time() + 20); // time in the future + $encoded = JWT::encode($payload, 'my_key'); + JWT::decode($encoded, 'my_key', array('HS256')); + } + + public function testBeforeValidTokenWithIat() + { + $this->setExpectedException('BeforeValidException'); + $payload = array( + "message" => "abc", + "iat" => time() + 20); // time in the future + $encoded = JWT::encode($payload, 'my_key'); + JWT::decode($encoded, 'my_key', array('HS256')); + } + + public function testValidToken() + { + $payload = array( + "message" => "abc", + "exp" => time() + JWT::$leeway + 20); // time in the future + $encoded = JWT::encode($payload, 'my_key'); + $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $this->assertEquals($decoded->message, 'abc'); + } + + public function testValidTokenWithLeeway() + { + JWT::$leeway = 60; + $payload = array( + "message" => "abc", + "exp" => time() - 20); // time in the past + $encoded = JWT::encode($payload, 'my_key'); + $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $this->assertEquals($decoded->message, 'abc'); + JWT::$leeway = 0; + } + + public function testExpiredTokenWithLeeway() + { + JWT::$leeway = 60; + $payload = array( + "message" => "abc", + "exp" => time() - 70); // time far in the past + $this->setExpectedException('ExpiredException'); + $encoded = JWT::encode($payload, 'my_key'); + $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $this->assertEquals($decoded->message, 'abc'); + JWT::$leeway = 0; + } + + public function testValidTokenWithList() + { + $payload = array( + "message" => "abc", + "exp" => time() + 20); // time in the future + $encoded = JWT::encode($payload, 'my_key'); + $decoded = JWT::decode($encoded, 'my_key', array('HS256', 'HS512')); + $this->assertEquals($decoded->message, 'abc'); + } + + public function testValidTokenWithNbf() + { + $payload = array( + "message" => "abc", + "iat" => time(), + "exp" => time() + 20, // time in the future + "nbf" => time() - 20); + $encoded = JWT::encode($payload, 'my_key'); + $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $this->assertEquals($decoded->message, 'abc'); + } + + public function testValidTokenWithNbfLeeway() + { + JWT::$leeway = 60; + $payload = array( + "message" => "abc", + "nbf" => time() + 20); // not before in near (leeway) future + $encoded = JWT::encode($payload, 'my_key'); + $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $this->assertEquals($decoded->message, 'abc'); + JWT::$leeway = 0; + } + + public function testInvalidTokenWithNbfLeeway() + { + JWT::$leeway = 60; + $payload = array( + "message" => "abc", + "nbf" => time() + 65); // not before too far in future + $encoded = JWT::encode($payload, 'my_key'); + $this->setExpectedException('BeforeValidException'); + $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + JWT::$leeway = 0; + } + + public function testValidTokenWithIatLeeway() + { + JWT::$leeway = 60; + $payload = array( + "message" => "abc", + "iat" => time() + 20); // issued in near (leeway) future + $encoded = JWT::encode($payload, 'my_key'); + $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + $this->assertEquals($decoded->message, 'abc'); + JWT::$leeway = 0; + } + + public function testInvalidTokenWithIatLeeway() + { + JWT::$leeway = 60; + $payload = array( + "message" => "abc", + "iat" => time() + 65); // issued too far in future + $encoded = JWT::encode($payload, 'my_key'); + $this->setExpectedException('BeforeValidException'); + $decoded = JWT::decode($encoded, 'my_key', array('HS256')); + JWT::$leeway = 0; + } + + public function testInvalidToken() + { + $payload = array( + "message" => "abc", + "exp" => time() + 20); // time in the future + $encoded = JWT::encode($payload, 'my_key'); + $this->setExpectedException('SignatureInvalidException'); + $decoded = JWT::decode($encoded, 'my_key2', array('HS256')); + } + + public function testRSEncodeDecode() + { + $privKey = openssl_pkey_new(array('digest_alg' => 'sha256', + 'private_key_bits' => 1024, + 'private_key_type' => OPENSSL_KEYTYPE_RSA)); + $msg = JWT::encode('abc', $privKey, 'RS256'); + $pubKey = openssl_pkey_get_details($privKey); + $pubKey = $pubKey['key']; + $decoded = JWT::decode($msg, $pubKey, array('RS256')); + $this->assertEquals($decoded, 'abc'); + } + + public function testKIDChooser() + { + $keys = array('1' => 'my_key', '2' => 'my_key2'); + $msg = JWT::encode('abc', $keys['1'], 'HS256', '1'); + $decoded = JWT::decode($msg, $keys, array('HS256')); + $this->assertEquals($decoded, 'abc'); + } + + public function testArrayAccessKIDChooser() + { + $keys = new ArrayObject(array('1' => 'my_key', '2' => 'my_key2')); + $msg = JWT::encode('abc', $keys['1'], 'HS256', '1'); + $decoded = JWT::decode($msg, $keys, array('HS256')); + $this->assertEquals($decoded, 'abc'); + } + + public function testNoneAlgorithm() + { + $msg = JWT::encode('abc', 'my_key'); + $this->setExpectedException('DomainException'); + JWT::decode($msg, 'my_key', array('none')); + } + + public function testIncorrectAlgorithm() + { + $msg = JWT::encode('abc', 'my_key'); + $this->setExpectedException('DomainException'); + JWT::decode($msg, 'my_key', array('RS256')); + } + + public function testMissingAlgorithm() + { + $msg = JWT::encode('abc', 'my_key'); + $this->setExpectedException('DomainException'); + JWT::decode($msg, 'my_key'); + } +} diff --git a/firebase/php-jwt/tests/autoload.php.dist b/firebase/php-jwt/tests/autoload.php.dist new file mode 100644 index 0000000..2e4310a --- /dev/null +++ b/firebase/php-jwt/tests/autoload.php.dist @@ -0,0 +1,17 @@ +<?php + +// if the library is the project, try to use the composer's autoload for the tests +$composerAutoload = __DIR__ . '/../vendor/autoload.php'; + +if (is_file($composerAutoload)) { + include $composerAutoload; +} else { + die('Unable to find autoload.php file, please use composer to load dependencies: + +wget http://getcomposer.org/composer.phar +php composer.phar install + +Visit http://getcomposer.org/ for more information. + +'); +} diff --git a/firebase/php-jwt/tests/bootstrap.php b/firebase/php-jwt/tests/bootstrap.php new file mode 100644 index 0000000..326c216 --- /dev/null +++ b/firebase/php-jwt/tests/bootstrap.php @@ -0,0 +1,7 @@ +<?php + +if (file_exists($file = __DIR__ . '/autoload.php')) { + require_once $file; +} elseif (file_exists($file = __DIR__ . '/autoload.php.dist')) { + require_once $file; +} -- To view, visit https://gerrit.wikimedia.org/r/226616 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I58af86204981a9f038b93df40e5c6c2f51fe06ae Gerrit-PatchSet: 4 Gerrit-Project: mediawiki/vendor Gerrit-Branch: master Gerrit-Owner: Nikerabbit <[email protected]> Gerrit-Reviewer: CSteipp <[email protected]> Gerrit-Reviewer: Legoktm <[email protected]> Gerrit-Reviewer: Nikerabbit <[email protected]> Gerrit-Reviewer: Santhosh <[email protected]> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
