diff options
author | Chris Raynor <chris@firebase.com> | 2014-11-14 14:32:03 -0800 |
---|---|---|
committer | Chris Raynor <chris@firebase.com> | 2014-11-14 14:32:03 -0800 |
commit | c20a3cb3faf81ec9716449b6c07c27b14f52bc75 (patch) | |
tree | 7c2e8f613cd50c57740cc5a317df3254fcdf8bd4 | |
parent | 0cc1ae69c2040c6afd4c0bfc30fae727ccfb016f (diff) | |
parent | 301e6bc0f183c71e16748310d1a96cfa94ddc3fb (diff) | |
download | php-jwt-c20a3cb3faf81ec9716449b6c07c27b14f52bc75.zip php-jwt-c20a3cb3faf81ec9716449b6c07c27b14f52bc75.tar.gz php-jwt-c20a3cb3faf81ec9716449b6c07c27b14f52bc75.tar.bz2 |
Merge pull request #23 from firebase/integration
This closes #22 and #21
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | Authentication/JWT.php | 58 | ||||
-rw-r--r-- | Exceptions/BeforeValidException.php | 6 | ||||
-rw-r--r-- | Exceptions/ExpiredException.php | 6 | ||||
-rw-r--r-- | Exceptions/SignatureInvalidException.php | 6 | ||||
-rw-r--r-- | composer.json | 2 | ||||
-rw-r--r-- | tests/JWTTest.php | 161 | ||||
-rw-r--r-- | tests/autoload.php.dist | 2 |
9 files changed, 152 insertions, 92 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22d0d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/.travis.yml b/.travis.yml index 1ecd967..d5d716e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,5 +8,5 @@ php: before_script: - wget -nc http://getcomposer.org/composer.phar - php composer.phar install - + script: phpunit --configuration phpunit.xml.dist diff --git a/Authentication/JWT.php b/Authentication/JWT.php index 90c1ac7..7e64b4b 100644 --- a/Authentication/JWT.php +++ b/Authentication/JWT.php @@ -15,7 +15,7 @@ */ class JWT { - static $methods = array( + public static $methods = array( 'HS256' => array('hash_hmac', 'SHA256'), 'HS512' => array('hash_hmac', 'SHA512'), 'HS384' => array('hash_hmac', 'SHA384'), @@ -32,7 +32,7 @@ class JWT * @return object The JWT's payload as a PHP object * @throws UnexpectedValueException Provided JWT was invalid * @throws DomainException Algorithm was not provided - * + * * @uses jsonDecode * @uses urlsafeB64Decode */ @@ -44,10 +44,10 @@ class JWT } list($headb64, $bodyb64, $cryptob64) = $tks; if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) { - throw new UnexpectedValueException('Invalid segment encoding'); + throw new UnexpectedValueException('Invalid header encoding'); } if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64))) { - throw new UnexpectedValueException('Invalid segment encoding'); + throw new UnexpectedValueException('Invalid claims encoding'); } $sig = JWT::urlsafeB64Decode($cryptob64); if ($verify) { @@ -55,20 +55,31 @@ class JWT throw new DomainException('Empty algorithm'); } if (is_array($key)) { - if(isset($header->kid)) { + 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 UnexpectedValueException('Signature verification failed'); + throw new SignatureInvalidException('Signature verification failed'); } + // Check token expiry time if defined. - if (isset($payload->exp) && time() >= $payload->exp){ - throw new UnexpectedValueException('Expired Token'); + if (isset($payload->exp) && time() >= $payload->exp) { + throw new ExpiredException('Expired token'); + } + + // Check if the nbf if it is defined. + if (isset($payload->nbf) && $payload->nbf > time()) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf) + ); } } + return $payload; } @@ -87,7 +98,7 @@ class JWT public static function encode($payload, $key, $algo = 'HS256', $keyId = null) { $header = array('typ' => 'JWT', 'alg' => $algo); - if($keyId !== null) { + if ($keyId !== null) { $header['kid'] = $keyId; } $segments = array(); @@ -124,7 +135,7 @@ class JWT case 'openssl': $signature = ''; $success = openssl_sign($msg, $signature, $key, $algo); - if(!$success) { + if (!$success) { throw new DomainException("OpenSSL unable to sign data"); } else { return $signature; @@ -142,7 +153,8 @@ class JWT * @return bool * @throws DomainException Invalid Algorithm or OpenSSL failure */ - public static function verify($msg, $signature, $key, $method = 'HS256') { + public static function verify($msg, $signature, $key, $method = 'HS256') + { if (empty(self::$methods[$method])) { throw new DomainException('Algorithm not supported'); } @@ -150,7 +162,7 @@ class JWT switch($function) { case 'openssl': $success = openssl_verify($msg, $signature, $key, $algo); - if(!$success) { + if (!$success) { throw new DomainException("OpenSSL unable to verify data: " . openssl_error_string()); } else { return $signature; @@ -181,13 +193,15 @@ class JWT 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. + /** 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. + /** 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); @@ -195,8 +209,8 @@ class JWT } if (function_exists('json_last_error') && $errno = json_last_error()) { - JWT::_handleJsonError($errno); - } else if ($obj === null && $input !== 'null') { + JWT::handleJsonError($errno); + } elseif ($obj === null && $input !== 'null') { throw new DomainException('Null result with non-null input'); } return $obj; @@ -214,8 +228,8 @@ class JWT { $json = json_encode($input); if (function_exists('json_last_error') && $errno = json_last_error()) { - JWT::_handleJsonError($errno); - } else if ($json === 'null' && $input !== null) { + JWT::handleJsonError($errno); + } elseif ($json === 'null' && $input !== null) { throw new DomainException('Null result with non-null input'); } return $json; @@ -257,7 +271,7 @@ class JWT * * @return void */ - private static function _handleJsonError($errno) + private static function handleJsonError($errno) { $messages = array( JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', @@ -270,6 +284,4 @@ class JWT : 'Unknown JSON error: ' . $errno ); } - } - diff --git a/Exceptions/BeforeValidException.php b/Exceptions/BeforeValidException.php new file mode 100644 index 0000000..5a84975 --- /dev/null +++ b/Exceptions/BeforeValidException.php @@ -0,0 +1,6 @@ +<?php + +class BeforeValidException extends UnexpectedValueException +{ + +} diff --git a/Exceptions/ExpiredException.php b/Exceptions/ExpiredException.php new file mode 100644 index 0000000..bd80468 --- /dev/null +++ b/Exceptions/ExpiredException.php @@ -0,0 +1,6 @@ +<?php + +class ExpiredException extends UnexpectedValueException +{ + +} diff --git a/Exceptions/SignatureInvalidException.php b/Exceptions/SignatureInvalidException.php new file mode 100644 index 0000000..d122232 --- /dev/null +++ b/Exceptions/SignatureInvalidException.php @@ -0,0 +1,6 @@ +<?php + +class SignatureInvalidException extends UnexpectedValueException +{ + +} diff --git a/composer.json b/composer.json index 292edbf..15b246c 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": ">=5.2.0" }, "autoload": { - "classmap": ["Authentication/"] + "classmap": ["Authentication/", "Exceptions/"] }, "target-dir": "Firebase/PHP-JWT", "minimum-stability": "dev" diff --git a/tests/JWTTest.php b/tests/JWTTest.php index 2149862..5a76ed4 100644 --- a/tests/JWTTest.php +++ b/tests/JWTTest.php @@ -1,79 +1,108 @@ <?php -class JWTTest extends PHPUnit_Framework_TestCase { - function testEncodeDecode() { - $msg = JWT::encode('abc', 'my_key'); - $this->assertEquals(JWT::decode($msg, 'my_key'), 'abc'); - } +class JWTTest extends PHPUnit_Framework_TestCase +{ + public function testEncodeDecode() + { + $msg = JWT::encode('abc', 'my_key'); + $this->assertEquals(JWT::decode($msg, 'my_key'), 'abc'); + } - function testDecodeFromPython() { - $msg = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.Iio6aHR0cDovL2FwcGxpY2F0aW9uL2NsaWNreT9ibGFoPTEuMjMmZi5vbz00NTYgQUMwMDAgMTIzIg.E_U8X2YpMT5K1cEiT_3-IvBYfrdIFIeVYeOqre_Z5Cg'; - $this->assertEquals( - JWT::decode($msg, 'my_key'), - '*:http://application/clicky?blah=1.23&f.oo=456 AC000 123' - ); - } + public function testDecodeFromPython() + { + $msg = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.Iio6aHR0cDovL2FwcGxpY2F0aW9uL2NsaWNreT9ibGFoPTEuMjMmZi5vbz00NTYgQUMwMDAgMTIzIg.E_U8X2YpMT5K1cEiT_3-IvBYfrdIFIeVYeOqre_Z5Cg'; + $this->assertEquals( + JWT::decode($msg, 'my_key'), + '*:http://application/clicky?blah=1.23&f.oo=456 AC000 123' + ); + } - function testUrlSafeCharacters() { - $encoded = JWT::encode('f?', 'a'); - $this->assertEquals('f?', JWT::decode($encoded, 'a')); - } + public function testUrlSafeCharacters() + { + $encoded = JWT::encode('f?', 'a'); + $this->assertEquals('f?', JWT::decode($encoded, 'a')); + } - function testMalformedUtf8StringsFail() { - $this->setExpectedException('DomainException'); - JWT::encode(pack('c', 128), 'a'); - } + public function testMalformedUtf8StringsFail() + { + $this->setExpectedException('DomainException'); + JWT::encode(pack('c', 128), 'a'); + } - function testMalformedJsonThrowsException() { - $this->setExpectedException('DomainException'); - JWT::jsonDecode('this is not valid JSON string'); - } + public function testMalformedJsonThrowsException() + { + $this->setExpectedException('DomainException'); + JWT::jsonDecode('this is not valid JSON string'); + } - function testExpiredToken() { - $this->setExpectedException('UnexpectedValueException'); - $payload = array( - "message" => "abc", - "exp" => time() - 20); // time in the past - $encoded = JWT::encode($payload, 'my_key'); - JWT::decode($encoded); - } + 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'); + } - function testValidToken() { - $payload = array( - "message" => "abc", - "exp" => time() + 20); // time in the future - $encoded = JWT::encode($payload, 'my_key'); - $decoded = JWT::decode($encoded, 'my_key'); - $this->assertEquals($decoded->message, 'abc'); - } + public function testBeforeValidToken() + { + $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'); + } - function testInvalidToken() { - $payload = array( - "message" => "abc", - "exp" => time() + 20); // time in the future - $encoded = JWT::encode($payload, 'my_key'); - $this->setExpectedException('UnexpectedValueException'); - $decoded = JWT::decode($encoded, 'my_key2'); - } + public function testValidToken() + { + $payload = array( + "message" => "abc", + "exp" => time() + 20); // time in the future + $encoded = JWT::encode($payload, 'my_key'); + $decoded = JWT::decode($encoded, 'my_key'); + $this->assertEquals($decoded->message, 'abc'); + } - 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, true); - $this->assertEquals($decoded, 'abc'); - } + public function testValidTokenWithNbf() + { + $payload = array( + "message" => "abc", + "exp" => time() + 20, // time in the future + "nbf" => time() - 20); + $encoded = JWT::encode($payload, 'my_key'); + $decoded = JWT::decode($encoded, 'my_key'); + $this->assertEquals($decoded->message, 'abc'); + } - function testKIDChooser() { - $keys = array('1' => 'my_key', '2' => 'my_key2'); - $msg = JWT::encode('abc', $keys['1'], 'HS256', '1'); - $decoded = JWT::decode($msg, $keys, true); - $this->assertEquals($decoded, 'abc'); - } + 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'); + } -} + 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, true); + $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, true); + $this->assertEquals($decoded, 'abc'); + } +} diff --git a/tests/autoload.php.dist b/tests/autoload.php.dist index 4533624..2e4310a 100644 --- a/tests/autoload.php.dist +++ b/tests/autoload.php.dist @@ -14,4 +14,4 @@ php composer.phar install Visit http://getcomposer.org/ for more information. '); -}
\ No newline at end of file +} |