Skip to content

Commit b2e5d09

Browse files
authored
Merge branch 'main' into patch-1
2 parents 18e5474 + e9690f5 commit b2e5d09

File tree

8 files changed

+141
-13
lines changed

8 files changed

+141
-13
lines changed

.github/workflows/tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
php: [ "7.4", "8.0", "8.1", "8.2" ]
13+
php: [ "7.4", "8.0", "8.1", "8.2", "8.3" ]
1414
name: PHP ${{matrix.php }} Unit Test
1515
steps:
1616
- uses: actions/checkout@v2
@@ -35,7 +35,7 @@ jobs:
3535
- name: Setup PHP
3636
uses: shivammathur/setup-php@v2
3737
with:
38-
php-version: "8.0"
38+
php-version: "8.2"
3939
- name: Run Script
4040
run: |
4141
composer global require friendsofphp/php-cs-fixer
@@ -49,7 +49,7 @@ jobs:
4949
- name: Install PHP
5050
uses: shivammathur/setup-php@v2
5151
with:
52-
php-version: '8.0'
52+
php-version: '8.2'
5353
- name: Run Script
5454
run: |
5555
composer install

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## [6.10.0](https://github.com/firebase/php-jwt/compare/v6.9.0...v6.10.0) (2023-11-28)
4+
5+
6+
### Features
7+
8+
* allow typ header override ([#546](https://github.com/firebase/php-jwt/issues/546)) ([79cb30b](https://github.com/firebase/php-jwt/commit/79cb30b729a22931b2fbd6b53f20629a83031ba9))
9+
10+
## [6.9.0](https://github.com/firebase/php-jwt/compare/v6.8.1...v6.9.0) (2023-10-04)
11+
12+
13+
### Features
14+
15+
* add payload to jwt exception ([#521](https://github.com/firebase/php-jwt/issues/521)) ([175edf9](https://github.com/firebase/php-jwt/commit/175edf958bb61922ec135b2333acf5622f2238a2))
16+
317
## [6.8.1](https://github.com/firebase/php-jwt/compare/v6.8.0...v6.8.1) (2023-07-14)
418

519

src/BeforeValidException.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
namespace Firebase\JWT;
44

5-
class BeforeValidException extends \UnexpectedValueException
5+
class BeforeValidException extends \UnexpectedValueException implements JWTExceptionWithPayloadInterface
66
{
7+
private object $payload;
8+
9+
public function setPayload(object $payload): void
10+
{
11+
$this->payload = $payload;
12+
}
13+
14+
public function getPayload(): object
15+
{
16+
return $this->payload;
17+
}
718
}

src/CachedKeySet.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ private function rateLimitExceeded(): bool
213213

214214
$cacheItem = $this->cache->getItem($this->rateLimitCacheKey);
215215
if (!$cacheItem->isHit()) {
216-
$cacheItem->expiresAfter(1); // # of calls are cached each minute
216+
$cacheItem->expiresAfter(60); // # of calls are cached each minute
217217
}
218218

219219
$callsPerMinute = (int) $cacheItem->get();

src/ExpiredException.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
namespace Firebase\JWT;
44

5-
class ExpiredException extends \UnexpectedValueException
5+
class ExpiredException extends \UnexpectedValueException implements JWTExceptionWithPayloadInterface
66
{
7+
private object $payload;
8+
9+
public function setPayload(object $payload): void
10+
{
11+
$this->payload = $payload;
12+
}
13+
14+
public function getPayload(): object
15+
{
16+
return $this->payload;
17+
}
718
}

src/JWT.php

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,23 +153,29 @@ public static function decode(
153153
// Check the nbf if it is defined. This is the time that the
154154
// token can actually be used. If it's not yet that time, abort.
155155
if (isset($payload->nbf) && floor($payload->nbf) > ($timestamp + static::$leeway)) {
156-
throw new BeforeValidException(
156+
$ex = new BeforeValidException(
157157
'Cannot handle token with nbf prior to ' . \date(DateTime::ATOM, (int) $payload->nbf)
158158
);
159+
$ex->setPayload($payload);
160+
throw $ex;
159161
}
160162

161163
// Check that this token has been created before 'now'. This prevents
162164
// using tokens that have been created for later use (and haven't
163165
// correctly used the nbf claim).
164166
if (!isset($payload->nbf) && isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) {
165-
throw new BeforeValidException(
167+
$ex = new BeforeValidException(
166168
'Cannot handle token with iat prior to ' . \date(DateTime::ATOM, (int) $payload->iat)
167169
);
170+
$ex->setPayload($payload);
171+
throw $ex;
168172
}
169173

170174
// Check if this token has expired.
171175
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
172-
throw new ExpiredException('Expired token');
176+
$ex = new ExpiredException('Expired token');
177+
$ex->setPayload($payload);
178+
throw $ex;
173179
}
174180

175181
return $payload;
@@ -197,13 +203,14 @@ public static function encode(
197203
string $keyId = null,
198204
array $head = null
199205
): string {
200-
$header = ['typ' => 'JWT', 'alg' => $alg];
206+
$header = ['typ' => 'JWT'];
207+
if (isset($head) && \is_array($head)) {
208+
$header = \array_merge($header, $head);
209+
}
210+
$header['alg'] = $alg;
201211
if ($keyId !== null) {
202212
$header['kid'] = $keyId;
203213
}
204-
if (isset($head) && \is_array($head)) {
205-
$header = \array_merge($head, $header);
206-
}
207214
$segments = [];
208215
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header));
209216
$segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload));
@@ -244,6 +251,9 @@ public static function sign(
244251
return \hash_hmac($algorithm, $msg, $key, true);
245252
case 'openssl':
246253
$signature = '';
254+
if (!\is_resource($key) && !openssl_pkey_get_private($key)) {
255+
throw new DomainException('OpenSSL unable to validate key');
256+
}
247257
$success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line
248258
if (!$success) {
249259
throw new DomainException('OpenSSL unable to sign data');
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
namespace Firebase\JWT;
3+
4+
interface JWTExceptionWithPayloadInterface
5+
{
6+
/**
7+
* Get the payload that caused this exception.
8+
*
9+
* @return object
10+
*/
11+
public function getPayload(): object;
12+
13+
/**
14+
* Get the payload that caused this exception.
15+
*
16+
* @param object $payload
17+
* @return void
18+
*/
19+
public function setPayload(object $payload): void;
20+
}

tests/JWTTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public function testMalformedUtf8StringsFail()
2626
JWT::encode(['message' => pack('c', 128)], 'a', 'HS256');
2727
}
2828

29+
public function testInvalidKeyOpensslSignFail()
30+
{
31+
$this->expectException(DomainException::class);
32+
JWT::sign('message', 'invalid key', 'openssl');
33+
}
34+
2935
public function testMalformedJsonThrowsException()
3036
{
3137
$this->expectException(DomainException::class);
@@ -107,6 +113,40 @@ public function testExpiredTokenWithLeeway()
107113
$this->assertSame($decoded->message, 'abc');
108114
}
109115

116+
public function testExpiredExceptionPayload()
117+
{
118+
$this->expectException(ExpiredException::class);
119+
$payload = [
120+
'message' => 'abc',
121+
'exp' => time() - 100, // time in the past
122+
];
123+
$encoded = JWT::encode($payload, 'my_key', 'HS256');
124+
try {
125+
JWT::decode($encoded, new Key('my_key', 'HS256'));
126+
} catch (ExpiredException $e) {
127+
$exceptionPayload = (array) $e->getPayload();
128+
$this->assertEquals($exceptionPayload, $payload);
129+
throw $e;
130+
}
131+
}
132+
133+
public function testBeforeValidExceptionPayload()
134+
{
135+
$this->expectException(BeforeValidException::class);
136+
$payload = [
137+
'message' => 'abc',
138+
'iat' => time() + 100, // time in the future
139+
];
140+
$encoded = JWT::encode($payload, 'my_key', 'HS256');
141+
try {
142+
JWT::decode($encoded, new Key('my_key', 'HS256'));
143+
} catch (BeforeValidException $e) {
144+
$exceptionPayload = (array) $e->getPayload();
145+
$this->assertEquals($exceptionPayload, $payload);
146+
throw $e;
147+
}
148+
}
149+
110150
public function testValidTokenWithNbf()
111151
{
112152
$payload = [
@@ -484,4 +524,26 @@ public function testGetHeaders()
484524
$this->assertEquals($headers->typ, 'JWT');
485525
$this->assertEquals($headers->alg, 'HS256');
486526
}
527+
528+
public function testAdditionalHeaderOverrides()
529+
{
530+
$msg = JWT::encode(
531+
['message' => 'abc'],
532+
'my_key',
533+
'HS256',
534+
'my_key_id',
535+
[
536+
'cty' => 'test-eit;v=1',
537+
'typ' => 'JOSE', // override type header
538+
'kid' => 'not_my_key_id', // should not override $key param
539+
'alg' => 'BAD', // should not override $alg param
540+
]
541+
);
542+
$headers = new stdClass();
543+
JWT::decode($msg, new Key('my_key', 'HS256'), $headers);
544+
$this->assertEquals('test-eit;v=1', $headers->cty, 'additional field works');
545+
$this->assertEquals('JOSE', $headers->typ, 'typ override works');
546+
$this->assertEquals('my_key_id', $headers->kid, 'key param not overridden');
547+
$this->assertEquals('HS256', $headers->alg, 'alg param not overridden');
548+
}
487549
}

0 commit comments

Comments
 (0)