Skip to content

Commit dd3e41c

Browse files
authored
Merge pull request #6 from talis/exception_targetting
1. Adds specific exception subtypes to allow us to differentiate different kinds of exception 2. Removes error codes, since they're all `1` 3. Updates tests to use `::class` for mocking.
2 parents ad54f77 + 097cf56 commit dd3e41c

13 files changed

+87
-50
lines changed

src/lti/LTI_JWT_Exception.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace IMSGlobal\LTI;
3+
4+
class LTI_JWT_Exception extends LTI_Exception {
5+
6+
}

src/lti/LTI_Message_Launch.php

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ protected function get_public_key() {
219219

220220
if (empty($public_key_set)) {
221221
// Failed to fetch public keyset from URL.
222-
throw new LTI_Exception('Failed to fetch public key', 1);
222+
throw new LTI_Public_Key_Exception('Failed to fetch public key');
223223
}
224224

225225
// Find key used to sign the JWT (matches the KID in the header)
@@ -234,7 +234,7 @@ protected function get_public_key() {
234234
}
235235

236236
// Could not find public key with a matching kid and alg.
237-
throw new LTI_Exception('Unable to find public key', 1);
237+
throw new LTI_Public_Key_Exception('Unable to find public key');
238238
}
239239

240240
private function cache_launch_data() {
@@ -247,7 +247,7 @@ private function validate_state() {
247247
$expectedState = $this->cookie->get_cookie('lti1p3_' . $this->request['state']);
248248
if (empty($expectedState) || $expectedState !== $this->request['state']) {
249249
// Error if state doesn't match
250-
throw new LTI_Exception('State not found', 1);
250+
throw new LTI_No_State_Found_Exception('State not found');
251251
}
252252
return $this;
253253
}
@@ -256,15 +256,15 @@ private function validate_jwt_format() {
256256
$jwt = $this->request['id_token'];
257257

258258
if (empty($jwt)) {
259-
throw new LTI_Exception('Missing id_token', 1);
259+
throw new LTI_JWT_Exception('Missing id_token');
260260
}
261261

262262
// Get parts of JWT.
263263
$jwt_parts = explode('.', $jwt);
264264

265265
if (count($jwt_parts) !== 3) {
266266
// Invalid number of parts in JWT.
267-
throw new LTI_Exception('Invalid id_token, JWT must contain 3 parts', 1);
267+
throw new LTI_JWT_Exception('Invalid id_token, JWT must contain 3 parts');
268268
}
269269

270270
// Decode JWT headers.
@@ -284,25 +284,25 @@ private function validate_nonce() {
284284

285285
private function validate_registration() {
286286
if (empty($this->jwt['body']['iss'])) {
287-
throw new LTI_Exception('Invalid issuer', 1);
287+
throw new LTI_Registration_Exception('Invalid issuer');
288288
}
289289

290290
$client_id = $this->get_client_id_from_jwt();
291291

292292
if (empty($client_id)) {
293-
throw new LTI_Exception('Invalid client id', 1);
293+
throw new LTI_Registration_Exception('Invalid client id');
294294
}
295295

296296
// Find registration.
297297
$this->registration = $this->db->find_registration_by_issuer($this->jwt['body']['iss'], $client_id);
298298

299299
if (empty($this->registration)) {
300-
throw new LTI_Exception('Registration not found.', 1);
300+
throw new LTI_Registration_Exception('Registration not found.');
301301
}
302302
// Check client id.
303303
if ( $client_id !== $this->registration->get_client_id()) {
304304
// Client not registered.
305-
throw new LTI_Exception('Client id not registered for this issuer', 1);
305+
throw new LTI_Registration_Exception('Client id not registered for this issuer');
306306
}
307307

308308
return $this;
@@ -317,15 +317,15 @@ private function validate_jwt_signature() {
317317
JWT::decode($this->request['id_token'], $public_key['key'], ['RS256']);
318318
} catch(\Exception $e) {
319319
// Error validating signature.
320-
throw new LTI_Exception('Invalid signature on id_token', 1);
320+
throw new LTI_JWT_Exception('Invalid signature on id_token');
321321
}
322322

323323
return $this;
324324
}
325325

326326
private function validate_deployment() {
327327
if (empty($this->jwt['body']['https://purl.imsglobal.org/spec/lti/claim/deployment_id'])) {
328-
throw new LTI_Exception('Invalid deployment');
328+
throw new LTI_Registration_Exception('Invalid deployment');
329329
}
330330

331331
// Find deployment.
@@ -336,7 +336,7 @@ private function validate_deployment() {
336336

337337
if (empty($deployment)) {
338338
// deployment not recognized.
339-
throw new LTI_Exception('Unable to find deployment', 1);
339+
throw new LTI_Registration_Exception('Unable to find deployment');
340340
}
341341

342342
return $this;
@@ -345,7 +345,7 @@ private function validate_deployment() {
345345
private function validate_message() {
346346
if (empty($this->jwt['body']['https://purl.imsglobal.org/spec/lti/claim/message_type'])) {
347347
// Unable to identify message type.
348-
throw new LTI_Exception('Invalid message type', 1);
348+
throw new LTI_Message_Validation_Exception('Invalid message type');
349349
}
350350

351351
// Do message type validation
@@ -370,18 +370,18 @@ private function validate_message() {
370370
if ($validator->can_validate($this->jwt['body'])) {
371371
if ($message_validator !== false) {
372372
// Can't have more than one validator apply at a time.
373-
throw new LTI_Exception('Validator conflict', 1);
373+
throw new LTI_Message_Validation_Exception('Validator conflict');
374374
}
375375
$message_validator = $validator;
376376
}
377377
}
378378

379379
if ($message_validator === false) {
380-
throw new LTI_Exception('Unrecognized message type.', 1);
380+
throw new LTI_Message_Validation_Exception('Unrecognized message type.');
381381
}
382382

383383
if (!$message_validator->validate($this->jwt['body'])) {
384-
throw new LTI_Exception('Message validation failed.', 1);
384+
throw new LTI_Message_Validation_Exception('Message validation failed.');
385385
}
386386

387387
return $this;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace IMSGlobal\LTI;
3+
4+
class LTI_Message_Validation_Exception extends LTI_Exception {
5+
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace IMSGlobal\LTI;
3+
4+
class LTI_No_State_Found_Exception extends LTI_Exception {
5+
6+
}

src/lti/LTI_Public_Key_Exception.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace IMSGlobal\LTI;
3+
4+
class LTI_Public_Key_Exception extends LTI_Exception {
5+
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace IMSGlobal\LTI;
3+
4+
class LTI_Registration_Exception extends LTI_Exception {
5+
6+
}

src/lti/message_validators/deep_link_message_validator.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,26 @@ public function can_validate($jwt_body) {
88

99
public function validate($jwt_body) {
1010
if (empty($jwt_body['sub'])) {
11-
throw new LTI_Exception('Must have a user (sub)');
11+
throw new LTI_Message_Validation_Exception('Must have a user (sub)');
1212
}
1313
if ($jwt_body['https://purl.imsglobal.org/spec/lti/claim/version'] !== '1.3.0') {
14-
throw new LTI_Exception('Incorrect version, expected 1.3.0');
14+
throw new LTI_Message_Validation_Exception('Incorrect version, expected 1.3.0');
1515
}
1616
if (!isset($jwt_body['https://purl.imsglobal.org/spec/lti/claim/roles'])) {
17-
throw new LTI_Exception('Missing Roles Claim');
17+
throw new LTI_Message_Validation_Exception('Missing Roles Claim');
1818
}
1919
if (empty($jwt_body['https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings'])) {
20-
throw new LTI_Exception('Missing Deep Linking Settings');
20+
throw new LTI_Message_Validation_Exception('Missing Deep Linking Settings');
2121
}
2222
$deep_link_settings = $jwt_body['https://purl.imsglobal.org/spec/lti-dl/claim/deep_linking_settings'];
2323
if (empty($deep_link_settings['deep_link_return_url'])) {
24-
throw new LTI_Exception('Missing Deep Linking Return URL');
24+
throw new LTI_Message_Validation_Exception('Missing Deep Linking Return URL');
2525
}
2626
if (empty($deep_link_settings['accept_types']) || !in_array('ltiResourceLink', $deep_link_settings['accept_types'])) {
27-
throw new LTI_Exception('Must support resource link placement types');
27+
throw new LTI_Message_Validation_Exception('Must support resource link placement types');
2828
}
2929
if (empty($deep_link_settings['accept_presentation_document_targets'])) {
30-
throw new LTI_Exception('Must support a presentation type');
30+
throw new LTI_Message_Validation_Exception('Must support a presentation type');
3131
}
3232

3333
return true;

src/lti/message_validators/resource_message_validator.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ public function can_validate($jwt_body) {
88

99
public function validate($jwt_body) {
1010
if (empty($jwt_body['sub'])) {
11-
throw new LTI_Exception('Must have a user (sub)');
11+
throw new LTI_Message_Validation_Exception('Must have a user (sub)');
1212
}
1313
if ($jwt_body['https://purl.imsglobal.org/spec/lti/claim/version'] !== '1.3.0') {
14-
throw new LTI_Exception('Incorrect version, expected 1.3.0');
14+
throw new LTI_Message_Validation_Exception('Incorrect version, expected 1.3.0');
1515
}
1616
if (!isset($jwt_body['https://purl.imsglobal.org/spec/lti/claim/roles'])) {
17-
throw new LTI_Exception('Missing Roles Claim');
17+
throw new LTI_Message_Validation_Exception('Missing Roles Claim');
1818
}
1919
if (empty($jwt_body['https://purl.imsglobal.org/spec/lti/claim/resource_link']['id'])) {
20-
throw new LTI_Exception('Missing Resource Link Id');
20+
throw new LTI_Message_Validation_Exception('Missing Resource Link Id');
2121
}
2222

2323
return true;

src/lti/message_validators/submission_review_message_validator.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@ public function can_validate($jwt_body) {
88

99
public function validate($jwt_body) {
1010
if (empty($jwt_body['sub'])) {
11-
throw new LTI_Exception('Must have a user (sub)');
11+
throw new LTI_Message_Validation_Exception('Must have a user (sub)');
1212
}
1313
if ($jwt_body['https://purl.imsglobal.org/spec/lti/claim/version'] !== '1.3.0') {
14-
throw new LTI_Exception('Incorrect version, expected 1.3.0');
14+
throw new LTI_Message_Validation_Exception('Incorrect version, expected 1.3.0');
1515
}
1616
if (!isset($jwt_body['https://purl.imsglobal.org/spec/lti/claim/roles'])) {
17-
throw new LTI_Exception('Missing Roles Claim');
17+
throw new LTI_Message_Validation_Exception('Missing Roles Claim');
1818
}
1919
if (empty($jwt_body['https://purl.imsglobal.org/spec/lti/claim/resource_link']['id'])) {
20-
throw new LTI_Exception('Missing Resource Link Id');
20+
throw new LTI_Message_Validation_Exception('Missing Resource Link Id');
2121
}
2222
if (empty($jwt_body['https://purl.imsglobal.org/spec/lti/claim/for_user'])) {
23-
throw new LTI_Exception('Missing For User');
23+
throw new LTI_Message_Validation_Exception('Missing For User');
2424
}
2525

2626
return true;

tests/unit/LTI_Message_Launch_Test.php

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
use Firebase\JWT\JWT;
66
use IMSGlobal\LTI\Cookie;
7+
use IMSGlobal\LTI\LTI_JWT_Exception;
78
use IMSGlobal\LTI\LTI_Message_Launch;
9+
use IMSGlobal\LTI\LTI_Message_Validation_Exception;
10+
use IMSGlobal\LTI\LTI_No_State_Found_Exception;
11+
use IMSGlobal\LTI\LTI_Registration_Exception;
812
use IMSGlobal\LTI\Tests\unit\helpers\DummyDatabase;
913
use IMSGlobal\LTI\Tests\unit\helpers\InMemoryCache;
1014

@@ -32,7 +36,7 @@ public function testNewInstance()
3236

3337
public function testValidateStateWithInvalidStateThrowsException()
3438
{
35-
$this->setExpectedException('\IMSGlobal\LTI\LTI_Exception', 'State not found');
39+
$this->setExpectedException(LTI_No_State_Found_Exception::class, 'State not found');
3640
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
3741
$cookie = $this->getMockBuilder(Cookie::class)
3842
->setMethods(['get_cookie'])
@@ -50,7 +54,7 @@ public function testValidateStateWithInvalidStateThrowsException()
5054
public function testValidateState()
5155
{
5256
$state = uniqid();
53-
$this->setExpectedException('\IMSGlobal\LTI\LTI_Exception', 'Missing id_token');
57+
$this->setExpectedException(LTI_JWT_Exception::class, 'Missing id_token');
5458
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
5559
$cookie = $this->getMockBuilder(Cookie::class)
5660
->setMethods(['get_cookie'])
@@ -68,7 +72,7 @@ public function testValidateJWTFormat()
6872
{
6973
$jwt = $this->encodeJWT($this->getValidJWTPayload());
7074
$state = uniqid();
71-
$this->setExpectedException('\Exception', 'Invalid signature on id_token');
75+
$this->setExpectedException(LTI_JWT_Exception::class, 'Invalid signature on id_token');
7276
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
7377
$cookie = $this->getMockBuilder(Cookie::class)
7478
->setMethods(['get_cookie'])
@@ -93,7 +97,7 @@ public function testValidateJWTFormatNotThreeParts()
9397
{
9498
$jwt = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhYWEiLCJhdWQiOiIxMjM0NSJ9';
9599
$state = uniqid();
96-
$this->setExpectedException('\IMSGlobal\LTI\LTI_Exception', 'Invalid id_token, JWT must contain 3 parts');
100+
$this->setExpectedException(LTI_JWT_Exception::class, 'Invalid id_token, JWT must contain 3 parts');
97101
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
98102
$cookie = $this->getMockBuilder(Cookie::class)
99103
->setMethods(['get_cookie'])
@@ -112,7 +116,7 @@ public function testValidateRegistration()
112116
$badKey = openssl_pkey_new();
113117
$jwt = $this->encodeJWT($this->getValidJWTPayload(), $badKey);
114118
$state = uniqid();
115-
$this->setExpectedException('\IMSGlobal\LTI\LTI_Exception', 'Invalid signature on id_token');
119+
$this->setExpectedException(LTI_JWT_Exception::class, 'Invalid signature on id_token');
116120
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
117121
$cookie = $this->getMockBuilder(Cookie::class)
118122
->setMethods(['get_cookie'])
@@ -139,7 +143,7 @@ public function testValidateRegistrationNotFound()
139143
$payload['aud'] = '67890';
140144
$jwt = $this->encodeJWT($payload);
141145
$state = uniqid();
142-
$this->setExpectedException('\Exception', 'Registration not found.');
146+
$this->setExpectedException(LTI_Registration_Exception::class, 'Registration not found.');
143147
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
144148
$cookie = $this->getMockBuilder(Cookie::class)
145149
->setMethods(['get_cookie'])
@@ -174,7 +178,7 @@ public function testValidateRegistrationMismatchClientId()
174178

175179
$jwt = $this->encodeJWT($payload);
176180
$state = uniqid();
177-
$this->setExpectedException('\IMSGlobal\LTI\LTI_Exception', 'Registration not found');
181+
$this->setExpectedException(LTI_Registration_Exception::class, 'Registration not found');
178182
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
179183
$cookie = $this->getMockBuilder(Cookie::class)
180184
->setMethods(['get_cookie'])
@@ -199,7 +203,7 @@ public function testValidateRegistrationMissingClientId()
199203

200204
$jwt = $this->encodeJWT($payload);
201205
$state = uniqid();
202-
$this->setExpectedException('\IMSGlobal\LTI\LTI_Exception', 'Invalid client id');
206+
$this->setExpectedException(LTI_Registration_Exception::class, 'Invalid client id');
203207
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
204208
$cookie = $this->getMockBuilder(Cookie::class)
205209
->setMethods(['get_cookie'])
@@ -223,7 +227,7 @@ public function testValidateJwtSignature()
223227
$payload = $this->getValidJWTPayload();
224228
$jwtWithInvalidSignature = $this->encodeJWT($payload, $badKey);
225229
$state = uniqid();
226-
$this->setExpectedException('\IMSGlobal\LTI\LTI_Exception', 'Invalid signature on id_token');
230+
$this->setExpectedException(LTI_JWT_Exception::class, 'Invalid signature on id_token');
227231
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
228232
$cookie = $this->getMockBuilder(Cookie::class)
229233
->setMethods(['get_cookie'])
@@ -307,7 +311,7 @@ public function testValidateMessageInvalidMessage()
307311
$payload['is_valid'] = false;
308312
$jwt = $this->encodeJWT($payload);
309313
$state = uniqid();
310-
$this->setExpectedException('\IMSGlobal\LTI\LTI_Exception', 'Message validation failed');
314+
$this->setExpectedException(LTI_Message_Validation_Exception::class, 'Message validation failed');
311315
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
312316
$cookie = $this->getMockBuilder(Cookie::class)
313317
->setMethods(['get_cookie'])
@@ -345,7 +349,7 @@ public function testValidateMessageMatchesMultipleValidators()
345349

346350
$jwt = $this->encodeJWT($payload);
347351
$state = uniqid();
348-
$this->setExpectedException('\IMSGlobal\LTI\LTI_Exception', 'Validator conflict');
352+
$this->setExpectedException(LTI_Message_Validation_Exception::class, 'Validator conflict');
349353
/** @var Cookie|\PHPUnit_Framework_MockObject_MockObject $cookie */
350354
$cookie = $this->getMockBuilder(Cookie::class)
351355
->setMethods(['get_cookie'])

0 commit comments

Comments
 (0)