Skip to content

Commit 1721255

Browse files
committed
Change generic Exceptions in DPop class to specific ones.
1 parent 269b2e2 commit 1721255

3 files changed

Lines changed: 44 additions & 34 deletions

File tree

src/Exceptions.inc.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Pdsinterop\Solid\Auth\Exception;
44

5+
use Throwable;
6+
57
abstract class Exception extends \Exception implements \JsonSerializable
68
{
79
final public function jsonSerialize(): array
@@ -19,3 +21,7 @@ final public function jsonSerialize(): array
1921
}
2022

2123
class LogicException extends Exception {}
24+
25+
class AuthorizationHeaderException extends Exception {}
26+
27+
class InvalidTokenException extends Exception {}

src/Utils/DPop.php

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
1616
use Lcobucci\JWT\Validation\Constraint\SignedWith;
1717
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
18+
use Pdsinterop\Solid\Auth\Exception\AuthorizationHeaderException;
19+
use Pdsinterop\Solid\Auth\Exception\InvalidTokenException;
1820
use Psr\Http\Message\ServerRequestInterface;
1921

2022
/**
@@ -47,22 +49,22 @@ public function getWebId($request) {
4749
$serverParams = $request->getServerParams();
4850

4951
if (isset($serverParams['HTTP_AUTHORIZATION']) === false) {
50-
throw new Exception("Authorization Header missing");
52+
throw new AuthorizationHeaderException("Authorization Header missing");
5153
}
5254

5355
if (str_contains($serverParams['HTTP_AUTHORIZATION'], ' ') === false) {
54-
throw new Exception("Authorization Header does not contain parameters");
56+
throw new AuthorizationHeaderException("Authorization Header does not contain parameters");
5557
}
5658

5759
[$authScheme, $jwt] = explode(" ", $serverParams['HTTP_AUTHORIZATION'], 2);
5860
$authScheme = strtolower($authScheme);
5961

6062
if ($authScheme !== "dpop") {
61-
throw new Exception('Only "dpop" authorization scheme is supported');
63+
throw new AuthorizationHeaderException('Only "dpop" authorization scheme is supported');
6264
}
6365

6466
if (isset($serverParams['HTTP_DPOP']) === false) {
65-
throw new Exception("Missing DPoP token");
67+
throw new AuthorizationHeaderException("Missing DPoP token");
6668
}
6769

6870
$dpop = $serverParams['HTTP_DPOP'];
@@ -71,13 +73,13 @@ public function getWebId($request) {
7173
try {
7274
$dpopKey = $this->getDpopKey($dpop, $request);
7375
} catch (InvalidTokenStructure $e) {
74-
throw new Exception("Invalid JWT token: {$e->getMessage()}", 0, $e);
76+
throw new InvalidTokenException("Invalid JWT token: {$e->getMessage()}", 0, $e);
7577
}
7678

7779
try {
7880
$this->validateJwtDpop($jwt, $dpopKey);
7981
} catch (RequiredConstraintsViolated $e) {
80-
throw new Exception("Invalid token: {$e->getMessage()}", 0, $e);
82+
throw new InvalidTokenException($e->getMessage(), 0, $e);
8183
}
8284

8385
if ($jwt) {
@@ -109,7 +111,7 @@ public function getDpopKey($dpop, $request) {
109111
$jwk = $dpop->headers()->get("jwk");
110112

111113
if (isset($jwk['kid']) === false) {
112-
throw new Exception('Key ID is missing from JWK header');
114+
throw new InvalidTokenException('Key ID is missing from JWK header');
113115
}
114116

115117
return $jwk['kid'];
@@ -121,15 +123,15 @@ private function validateJwtDpop($jwt, $dpopKey) {
121123
$cnf = $jwt->claims()->get("cnf");
122124

123125
if ($cnf === null) {
124-
throw new Exception('JWT Confirmation claim (cnf) is missing');
126+
throw new InvalidTokenException('JWT Confirmation claim (cnf) is missing');
125127
}
126128

127129
if (isset($cnf['jkt']) === false) {
128-
throw new Exception('JWT Confirmation claim (cnf) is missing Thumbprint (jkt)');
130+
throw new InvalidTokenException('JWT Confirmation claim (cnf) is missing Thumbprint (jkt)');
129131
}
130132

131133
if ($cnf['jkt'] !== $dpopKey) {
132-
throw new Exception('JWT Confirmation claim (cnf) provided Thumbprint (jkt) does not match Key ID from JWK header');
134+
throw new InvalidTokenException('JWT Confirmation claim (cnf) provided Thumbprint (jkt) does not match Key ID from JWK header');
133135
}
134136

135137
//@FIXME: add check for "ath" claim in DPoP token, per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop#section-7
@@ -180,31 +182,31 @@ public function validateDpop($dpop, $request) {
180182
// 2. all required claims are contained in the JWT,
181183
$htm = $dpop->claims()->get("htm"); // http method
182184
if (!$htm) {
183-
throw new Exception("missing htm");
185+
throw new InvalidTokenException("missing htm");
184186
}
185187
$htu = $dpop->claims()->get("htu"); // http uri
186188
if (!$htu) {
187-
throw new Exception("missing htu");
189+
throw new InvalidTokenException("missing htu");
188190
}
189191
$typ = $dpop->headers()->get("typ");
190192
if (!$typ) {
191-
throw new Exception("missing typ");
193+
throw new InvalidTokenException("missing typ");
192194
}
193195
$alg = $dpop->headers()->get("alg");
194196
if (!$alg) {
195-
throw new Exception("missing alg");
197+
throw new InvalidTokenException("missing alg");
196198
}
197199

198200
// 3. the "typ" field in the header has the value "dpop+jwt",
199201
if ($typ != "dpop+jwt") {
200-
throw new Exception("typ is not dpop+jwt");
202+
throw new InvalidTokenException("typ is not dpop+jwt");
201203
}
202204

203205
// 4. the algorithm in the header of the JWT indicates an asymmetric
204206
// digital signature algorithm, is not "none", is supported by the
205207
// application, and is deemed secure,
206208
if ($alg == "none") {
207-
throw new Exception("alg is none");
209+
throw new InvalidTokenException("alg is none");
208210
}
209211

210212
// 5. that the JWT is signed using the public key contained in the
@@ -221,7 +223,7 @@ public function validateDpop($dpop, $request) {
221223
$signer = Sha256::create();
222224
break;
223225
default:
224-
throw new Exception("unsupported algorithm");
226+
throw new InvalidTokenException("unsupported algorithm");
225227
break;
226228
}
227229
$key = InMemory::plainText($pem);
@@ -231,7 +233,7 @@ public function validateDpop($dpop, $request) {
231233
// 6. the "htm" claim matches the HTTP method value of the HTTP request
232234
// in which the JWT was received (case-insensitive),
233235
if (strtolower($htm) != strtolower($request->getMethod())) {
234-
throw new Exception("htm http method is invalid");
236+
throw new InvalidTokenException("htm http method is invalid");
235237
}
236238

237239
// 7. the "htu" claims matches the HTTP URI value for the HTTP request
@@ -243,7 +245,7 @@ public function validateDpop($dpop, $request) {
243245
//error_log("REQUESTED HTU $htu");
244246
//error_log("REQUESTED PATH $requestedPath");
245247
if ($htu != $requestedPath) {
246-
throw new Exception("htu does not match requested path");
248+
throw new InvalidTokenException("htu does not match requested path");
247249
}
248250

249251
// 8. the token was issued within an acceptable timeframe (see Section 9.1), and
@@ -258,11 +260,11 @@ public function validateDpop($dpop, $request) {
258260
// 9. that, within a reasonable consideration of accuracy and resource utilization, a JWT with the same "jti" value has not been received previously (see Section 9.1).
259261
$jti = $dpop->claims()->get("jti");
260262
if ($jti === null) {
261-
throw new Exception("jti is missing");
263+
throw new InvalidTokenException("jti is missing");
262264
}
263265
$isJtiValid = $this->jtiValidator->validate($jti, (string) $request->getUri());
264266
if (! $isJtiValid) {
265-
throw new Exception("jti is invalid");
267+
throw new InvalidTokenException("jti is invalid");
266268
}
267269

268270
// 10. that, if used with an access token, it also contains the 'ath' claim, with a hash of the access token
@@ -276,12 +278,12 @@ private function getSubjectFromJwt($jwt) {
276278
try {
277279
$jwt = $jwtConfig->parser()->parse($jwt);
278280
} catch(Exception $e) {
279-
throw new Exception("Invalid JWT token", 409, $e);
281+
throw new InvalidTokenException("Invalid JWT token", 409, $e);
280282
}
281283

282284
$sub = $jwt->claims()->get("sub");
283285
if ($sub === null) {
284-
throw new Exception('Missing "SUB"');
286+
throw new InvalidTokenException('Missing "SUB"');
285287
}
286288
return $sub;
287289
}

tests/unit/Utils/DPOPTest.php

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
77
use Pdsinterop\Solid\Auth\AbstractTestCase;
88
use Pdsinterop\Solid\Auth\Enum\Jwk\Parameter as JwkParameter;
9+
use Pdsinterop\Solid\Auth\Exception\AuthorizationHeaderException;
10+
use Pdsinterop\Solid\Auth\Exception\InvalidTokenException;
911

1012
/**
1113
* @coversDefaultClass \Pdsinterop\Solid\Auth\Utils\DPop
@@ -140,7 +142,7 @@ public function testValidateDpopWithWrongTyp(): void
140142
$mockJtiValidator = $this->createMockJtiValidator();
141143
$dpop = new DPop($mockJtiValidator);
142144

143-
$this->expectException(\Exception::class);
145+
$this->expectException(InvalidTokenException::class);
144146
$this->expectExceptionMessage('typ is not dpop+jwt');
145147

146148
$result = $dpop->validateDpop($token['token'], $this->serverRequest);
@@ -159,7 +161,7 @@ public function testValidateDpopWithAlgNone(): void
159161
$mockJtiValidator = $this->createMockJtiValidator();
160162
$dpop = new DPop($mockJtiValidator);
161163

162-
$this->expectException(\Exception::class);
164+
$this->expectException(InvalidTokenException::class);
163165
$this->expectExceptionMessage('alg is none');
164166
$result = $dpop->validateDpop($token['token'], $this->serverRequest);
165167
$this->assertTrue($result);
@@ -243,7 +245,7 @@ final public function testGetWebIdWithoutHttpAuthorizationHeader(): void
243245

244246
$request = new ServerRequest(array(),array(), $this->url);
245247

246-
$this->expectException(\Exception::class);
248+
$this->expectException(AuthorizationHeaderException::class);
247249
$this->expectExceptionMessage('Authorization Header missing');
248250

249251
$dpop->getWebId($request);
@@ -261,7 +263,7 @@ final public function testGetWebIdWithIncorrectAuthHeaderFormat(): void
261263

262264
$request = new ServerRequest(array('HTTP_AUTHORIZATION' => 'IncorrectAuthorizationFormat'),array(), $this->url);
263265

264-
$this->expectException(\Exception::class);
266+
$this->expectException(AuthorizationHeaderException::class);
265267
$this->expectExceptionMessage('Authorization Header does not contain parameters');
266268

267269
$dpop->getWebId($request);
@@ -279,7 +281,7 @@ final public function testGetWebIdWithInvalidJwt(): void
279281
$mockJtiValidator = $this->createMockJtiValidator();
280282
$dpop = new DPop($mockJtiValidator);
281283

282-
$this->expectException(\Exception::class);
284+
$this->expectException(InvalidTokenException::class);
283285
$this->expectExceptionMessage('Invalid JWT token');
284286

285287
$request = new ServerRequest(array(
@@ -300,7 +302,7 @@ final public function testGetWebIdWithoutDpop(): void
300302
$mockJtiValidator = $this->createMockJtiValidator();
301303
$dpop = new DPop($mockJtiValidator);
302304

303-
$this->expectException(\Exception::class);
305+
$this->expectException(AuthorizationHeaderException::class);
304306
$this->expectExceptionMessage('Only "dpop" authorization scheme is supported');
305307

306308
$request = new ServerRequest(array('HTTP_AUTHORIZATION' => "Basic YWxhZGRpbjpvcGVuc2VzYW1l"),array(), $this->url);
@@ -338,7 +340,7 @@ final public function testGetWebIdWithDpopWithoutKeyId(): void
338340
'HTTP_DPOP' => $token['token'],
339341
),array(), $this->url);
340342

341-
$this->expectException(\Exception::class);
343+
$this->expectException(InvalidTokenException::class);
342344
$this->expectExceptionMessage('Key ID is missing from JWK header');
343345

344346
$dpop->getWebId($request);
@@ -374,7 +376,7 @@ final public function testGetWebIdWithDpopWithoutConfirmationClaim(): void
374376
'HTTP_DPOP' => $token['token'],
375377
),array(), $this->url);
376378

377-
$this->expectException(\Exception::class);
379+
$this->expectException(InvalidTokenException::class);
378380
$this->expectExceptionMessage('JWT Confirmation claim (cnf) is missing');
379381

380382
$dpop->getWebId($request);
@@ -409,7 +411,7 @@ final public function testGetWebIdWithDpopWithoutThumbprint(): void
409411
'HTTP_DPOP' => $token['token'],
410412
),array(), $this->url);
411413

412-
$this->expectException(\Exception::class);
414+
$this->expectException(InvalidTokenException::class);
413415
$this->expectExceptionMessage('JWT Confirmation claim (cnf) is missing Thumbprint (jkt)');
414416

415417
$dpop->getWebId($request);
@@ -444,7 +446,7 @@ final public function testGetWebIdWithDpopWithMismatchingThumbprintAndKeyId(): v
444446
'HTTP_DPOP' => $token['token'],
445447
),array(), $this->url);
446448

447-
$this->expectException(\Exception::class);
449+
$this->expectException(InvalidTokenException::class);
448450
$this->expectExceptionMessage('JWT Confirmation claim (cnf) provided Thumbprint (jkt) does not match Key ID from JWK header');
449451

450452
$dpop->getWebId($request);
@@ -478,7 +480,7 @@ final public function testGetWebIdWithDpopWithoutSub(): void
478480
'HTTP_DPOP' => $token['token'],
479481
),array(), $this->url);
480482

481-
$this->expectException(\Exception::class);
483+
$this->expectException(InvalidTokenException::class);
482484
$this->expectExceptionMessage('Missing "SUB"');
483485

484486
$dpop->getWebId($request);

0 commit comments

Comments
 (0)