Skip to content

Commit c20a3cb

Browse files
author
Chris Raynor
committed
Merge pull request firebase#23 from firebase/integration
This closes firebase#22 and firebase#21
2 parents 0cc1ae6 + 301e6bc commit c20a3cb

File tree

9 files changed

+152
-92
lines changed

9 files changed

+152
-92
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vendor

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ php:
88
before_script:
99
- wget -nc http://getcomposer.org/composer.phar
1010
- php composer.phar install
11-
11+
1212
script: phpunit --configuration phpunit.xml.dist

Authentication/JWT.php

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
class JWT
1717
{
18-
static $methods = array(
18+
public static $methods = array(
1919
'HS256' => array('hash_hmac', 'SHA256'),
2020
'HS512' => array('hash_hmac', 'SHA512'),
2121
'HS384' => array('hash_hmac', 'SHA384'),
@@ -32,7 +32,7 @@ class JWT
3232
* @return object The JWT's payload as a PHP object
3333
* @throws UnexpectedValueException Provided JWT was invalid
3434
* @throws DomainException Algorithm was not provided
35-
*
35+
*
3636
* @uses jsonDecode
3737
* @uses urlsafeB64Decode
3838
*/
@@ -44,31 +44,42 @@ public static function decode($jwt, $key = null, $verify = true)
4444
}
4545
list($headb64, $bodyb64, $cryptob64) = $tks;
4646
if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) {
47-
throw new UnexpectedValueException('Invalid segment encoding');
47+
throw new UnexpectedValueException('Invalid header encoding');
4848
}
4949
if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64))) {
50-
throw new UnexpectedValueException('Invalid segment encoding');
50+
throw new UnexpectedValueException('Invalid claims encoding');
5151
}
5252
$sig = JWT::urlsafeB64Decode($cryptob64);
5353
if ($verify) {
5454
if (empty($header->alg)) {
5555
throw new DomainException('Empty algorithm');
5656
}
5757
if (is_array($key)) {
58-
if(isset($header->kid)) {
58+
if (isset($header->kid)) {
5959
$key = $key[$header->kid];
6060
} else {
6161
throw new DomainException('"kid" empty, unable to lookup correct key');
6262
}
6363
}
64+
65+
// Check the signature
6466
if (!JWT::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
65-
throw new UnexpectedValueException('Signature verification failed');
67+
throw new SignatureInvalidException('Signature verification failed');
6668
}
69+
6770
// Check token expiry time if defined.
68-
if (isset($payload->exp) && time() >= $payload->exp){
69-
throw new UnexpectedValueException('Expired Token');
71+
if (isset($payload->exp) && time() >= $payload->exp) {
72+
throw new ExpiredException('Expired token');
73+
}
74+
75+
// Check if the nbf if it is defined.
76+
if (isset($payload->nbf) && $payload->nbf > time()) {
77+
throw new BeforeValidException(
78+
'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
79+
);
7080
}
7181
}
82+
7283
return $payload;
7384
}
7485

@@ -87,7 +98,7 @@ public static function decode($jwt, $key = null, $verify = true)
8798
public static function encode($payload, $key, $algo = 'HS256', $keyId = null)
8899
{
89100
$header = array('typ' => 'JWT', 'alg' => $algo);
90-
if($keyId !== null) {
101+
if ($keyId !== null) {
91102
$header['kid'] = $keyId;
92103
}
93104
$segments = array();
@@ -124,7 +135,7 @@ public static function sign($msg, $key, $method = 'HS256')
124135
case 'openssl':
125136
$signature = '';
126137
$success = openssl_sign($msg, $signature, $key, $algo);
127-
if(!$success) {
138+
if (!$success) {
128139
throw new DomainException("OpenSSL unable to sign data");
129140
} else {
130141
return $signature;
@@ -142,15 +153,16 @@ public static function sign($msg, $key, $method = 'HS256')
142153
* @return bool
143154
* @throws DomainException Invalid Algorithm or OpenSSL failure
144155
*/
145-
public static function verify($msg, $signature, $key, $method = 'HS256') {
156+
public static function verify($msg, $signature, $key, $method = 'HS256')
157+
{
146158
if (empty(self::$methods[$method])) {
147159
throw new DomainException('Algorithm not supported');
148160
}
149161
list($function, $algo) = self::$methods[$method];
150162
switch($function) {
151163
case 'openssl':
152164
$success = openssl_verify($msg, $signature, $key, $algo);
153-
if(!$success) {
165+
if (!$success) {
154166
throw new DomainException("OpenSSL unable to verify data: " . openssl_error_string());
155167
} else {
156168
return $signature;
@@ -181,22 +193,24 @@ public static function verify($msg, $signature, $key, $method = 'HS256') {
181193
public static function jsonDecode($input)
182194
{
183195
if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
184-
/* In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you to specify that large ints (like Steam
185-
* Transaction IDs) should be treated as strings, rather than the PHP default behaviour of converting them to floats.
196+
/** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
197+
* to specify that large ints (like Steam Transaction IDs) should be treated as
198+
* strings, rather than the PHP default behaviour of converting them to floats.
186199
*/
187200
$obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
188201
} else {
189-
/* Not all servers will support that, however, so for older versions we must manually detect large ints in the JSON
190-
* string and quote them (thus converting them to strings) before decoding, hence the preg_replace() call.
202+
/** Not all servers will support that, however, so for older versions we must
203+
* manually detect large ints in the JSON string and quote them (thus converting
204+
*them to strings) before decoding, hence the preg_replace() call.
191205
*/
192206
$max_int_length = strlen((string) PHP_INT_MAX) - 1;
193207
$json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
194208
$obj = json_decode($json_without_bigints);
195209
}
196210

197211
if (function_exists('json_last_error') && $errno = json_last_error()) {
198-
JWT::_handleJsonError($errno);
199-
} else if ($obj === null && $input !== 'null') {
212+
JWT::handleJsonError($errno);
213+
} elseif ($obj === null && $input !== 'null') {
200214
throw new DomainException('Null result with non-null input');
201215
}
202216
return $obj;
@@ -214,8 +228,8 @@ public static function jsonEncode($input)
214228
{
215229
$json = json_encode($input);
216230
if (function_exists('json_last_error') && $errno = json_last_error()) {
217-
JWT::_handleJsonError($errno);
218-
} else if ($json === 'null' && $input !== null) {
231+
JWT::handleJsonError($errno);
232+
} elseif ($json === 'null' && $input !== null) {
219233
throw new DomainException('Null result with non-null input');
220234
}
221235
return $json;
@@ -257,7 +271,7 @@ public static function urlsafeB64Encode($input)
257271
*
258272
* @return void
259273
*/
260-
private static function _handleJsonError($errno)
274+
private static function handleJsonError($errno)
261275
{
262276
$messages = array(
263277
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
@@ -270,6 +284,4 @@ private static function _handleJsonError($errno)
270284
: 'Unknown JSON error: ' . $errno
271285
);
272286
}
273-
274287
}
275-

Exceptions/BeforeValidException.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
class BeforeValidException extends UnexpectedValueException
4+
{
5+
6+
}

Exceptions/ExpiredException.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
class ExpiredException extends UnexpectedValueException
4+
{
5+
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
class SignatureInvalidException extends UnexpectedValueException
4+
{
5+
6+
}

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"php": ">=5.2.0"
2020
},
2121
"autoload": {
22-
"classmap": ["Authentication/"]
22+
"classmap": ["Authentication/", "Exceptions/"]
2323
},
2424
"target-dir": "Firebase/PHP-JWT",
2525
"minimum-stability": "dev"

tests/JWTTest.php

Lines changed: 95 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,108 @@
11
<?php
22

3-
class JWTTest extends PHPUnit_Framework_TestCase {
4-
function testEncodeDecode() {
5-
$msg = JWT::encode('abc', 'my_key');
6-
$this->assertEquals(JWT::decode($msg, 'my_key'), 'abc');
7-
}
3+
class JWTTest extends PHPUnit_Framework_TestCase
4+
{
5+
public function testEncodeDecode()
6+
{
7+
$msg = JWT::encode('abc', 'my_key');
8+
$this->assertEquals(JWT::decode($msg, 'my_key'), 'abc');
9+
}
810

9-
function testDecodeFromPython() {
10-
$msg = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.Iio6aHR0cDovL2FwcGxpY2F0aW9uL2NsaWNreT9ibGFoPTEuMjMmZi5vbz00NTYgQUMwMDAgMTIzIg.E_U8X2YpMT5K1cEiT_3-IvBYfrdIFIeVYeOqre_Z5Cg';
11-
$this->assertEquals(
12-
JWT::decode($msg, 'my_key'),
13-
'*:http://application/clicky?blah=1.23&f.oo=456 AC000 123'
14-
);
15-
}
11+
public function testDecodeFromPython()
12+
{
13+
$msg = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.Iio6aHR0cDovL2FwcGxpY2F0aW9uL2NsaWNreT9ibGFoPTEuMjMmZi5vbz00NTYgQUMwMDAgMTIzIg.E_U8X2YpMT5K1cEiT_3-IvBYfrdIFIeVYeOqre_Z5Cg';
14+
$this->assertEquals(
15+
JWT::decode($msg, 'my_key'),
16+
'*:http://application/clicky?blah=1.23&f.oo=456 AC000 123'
17+
);
18+
}
1619

17-
function testUrlSafeCharacters() {
18-
$encoded = JWT::encode('f?', 'a');
19-
$this->assertEquals('f?', JWT::decode($encoded, 'a'));
20-
}
20+
public function testUrlSafeCharacters()
21+
{
22+
$encoded = JWT::encode('f?', 'a');
23+
$this->assertEquals('f?', JWT::decode($encoded, 'a'));
24+
}
2125

22-
function testMalformedUtf8StringsFail() {
23-
$this->setExpectedException('DomainException');
24-
JWT::encode(pack('c', 128), 'a');
25-
}
26+
public function testMalformedUtf8StringsFail()
27+
{
28+
$this->setExpectedException('DomainException');
29+
JWT::encode(pack('c', 128), 'a');
30+
}
2631

27-
function testMalformedJsonThrowsException() {
28-
$this->setExpectedException('DomainException');
29-
JWT::jsonDecode('this is not valid JSON string');
30-
}
32+
public function testMalformedJsonThrowsException()
33+
{
34+
$this->setExpectedException('DomainException');
35+
JWT::jsonDecode('this is not valid JSON string');
36+
}
3137

32-
function testExpiredToken() {
33-
$this->setExpectedException('UnexpectedValueException');
34-
$payload = array(
35-
"message" => "abc",
36-
"exp" => time() - 20); // time in the past
37-
$encoded = JWT::encode($payload, 'my_key');
38-
JWT::decode($encoded);
39-
}
38+
public function testExpiredToken()
39+
{
40+
$this->setExpectedException('ExpiredException');
41+
$payload = array(
42+
"message" => "abc",
43+
"exp" => time() - 20); // time in the past
44+
$encoded = JWT::encode($payload, 'my_key');
45+
JWT::decode($encoded, 'my_key');
46+
}
4047

41-
function testValidToken() {
42-
$payload = array(
43-
"message" => "abc",
44-
"exp" => time() + 20); // time in the future
45-
$encoded = JWT::encode($payload, 'my_key');
46-
$decoded = JWT::decode($encoded, 'my_key');
47-
$this->assertEquals($decoded->message, 'abc');
48-
}
48+
public function testBeforeValidToken()
49+
{
50+
$this->setExpectedException('BeforeValidException');
51+
$payload = array(
52+
"message" => "abc",
53+
"nbf" => time() + 20); // time in the future
54+
$encoded = JWT::encode($payload, 'my_key');
55+
JWT::decode($encoded, 'my_key');
56+
}
4957

50-
function testInvalidToken() {
51-
$payload = array(
52-
"message" => "abc",
53-
"exp" => time() + 20); // time in the future
54-
$encoded = JWT::encode($payload, 'my_key');
55-
$this->setExpectedException('UnexpectedValueException');
56-
$decoded = JWT::decode($encoded, 'my_key2');
57-
}
58+
public function testValidToken()
59+
{
60+
$payload = array(
61+
"message" => "abc",
62+
"exp" => time() + 20); // time in the future
63+
$encoded = JWT::encode($payload, 'my_key');
64+
$decoded = JWT::decode($encoded, 'my_key');
65+
$this->assertEquals($decoded->message, 'abc');
66+
}
5867

59-
function testRSEncodeDecode() {
60-
$privKey = openssl_pkey_new(array('digest_alg' => 'sha256',
61-
'private_key_bits' => 1024,
62-
'private_key_type' => OPENSSL_KEYTYPE_RSA));
63-
$msg = JWT::encode('abc', $privKey, 'RS256');
64-
$pubKey = openssl_pkey_get_details($privKey);
65-
$pubKey = $pubKey['key'];
66-
$decoded = JWT::decode($msg, $pubKey, true);
67-
$this->assertEquals($decoded, 'abc');
68-
}
68+
public function testValidTokenWithNbf()
69+
{
70+
$payload = array(
71+
"message" => "abc",
72+
"exp" => time() + 20, // time in the future
73+
"nbf" => time() - 20);
74+
$encoded = JWT::encode($payload, 'my_key');
75+
$decoded = JWT::decode($encoded, 'my_key');
76+
$this->assertEquals($decoded->message, 'abc');
77+
}
6978

70-
function testKIDChooser() {
71-
$keys = array('1' => 'my_key', '2' => 'my_key2');
72-
$msg = JWT::encode('abc', $keys['1'], 'HS256', '1');
73-
$decoded = JWT::decode($msg, $keys, true);
74-
$this->assertEquals($decoded, 'abc');
75-
}
79+
public function testInvalidToken()
80+
{
81+
$payload = array(
82+
"message" => "abc",
83+
"exp" => time() + 20); // time in the future
84+
$encoded = JWT::encode($payload, 'my_key');
85+
$this->setExpectedException('SignatureInvalidException');
86+
$decoded = JWT::decode($encoded, 'my_key2');
87+
}
7688

77-
}
89+
public function testRSEncodeDecode()
90+
{
91+
$privKey = openssl_pkey_new(array('digest_alg' => 'sha256',
92+
'private_key_bits' => 1024,
93+
'private_key_type' => OPENSSL_KEYTYPE_RSA));
94+
$msg = JWT::encode('abc', $privKey, 'RS256');
95+
$pubKey = openssl_pkey_get_details($privKey);
96+
$pubKey = $pubKey['key'];
97+
$decoded = JWT::decode($msg, $pubKey, true);
98+
$this->assertEquals($decoded, 'abc');
99+
}
78100

79-
?>
101+
public function testKIDChooser()
102+
{
103+
$keys = array('1' => 'my_key', '2' => 'my_key2');
104+
$msg = JWT::encode('abc', $keys['1'], 'HS256', '1');
105+
$decoded = JWT::decode($msg, $keys, true);
106+
$this->assertEquals($decoded, 'abc');
107+
}
108+
}

tests/autoload.php.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ php composer.phar install
1414
Visit http://getcomposer.org/ for more information.
1515

1616
');
17-
}
17+
}

0 commit comments

Comments
 (0)