]> BookStack Code Mirror - bookstack/blob - tests/Unit/OidcIdTokenTest.php
Applied StyleCI changes, added php/larastan to attribution
[bookstack] / tests / Unit / OidcIdTokenTest.php
1 <?php
2
3 namespace Tests\Unit;
4
5 use BookStack\Auth\Access\Oidc\OidcIdToken;
6 use BookStack\Auth\Access\Oidc\OidcInvalidTokenException;
7 use Tests\Helpers\OidcJwtHelper;
8 use Tests\TestCase;
9
10 class OidcIdTokenTest extends TestCase
11 {
12     public function test_valid_token_passes_validation()
13     {
14         $token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), [
15             OidcJwtHelper::publicJwkKeyArray(),
16         ]);
17
18         $this->assertTrue($token->validate('xxyyzz.aaa.bbccdd.123'));
19     }
20
21     public function test_get_claim_returns_value_if_existing()
22     {
23         $token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), []);
24         $this->assertEquals('bscott@example.com', $token->getClaim('email'));
25     }
26
27     public function test_get_claim_returns_null_if_not_existing()
28     {
29         $token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), []);
30         $this->assertEquals(null, $token->getClaim('emails'));
31     }
32
33     public function test_get_all_claims_returns_all_payload_claims()
34     {
35         $defaultPayload = OidcJwtHelper::defaultPayload();
36         $token = new OidcIdToken(OidcJwtHelper::idToken($defaultPayload), OidcJwtHelper::defaultIssuer(), []);
37         $this->assertEquals($defaultPayload, $token->getAllClaims());
38     }
39
40     public function test_token_structure_error_cases()
41     {
42         $idToken = OidcJwtHelper::idToken();
43         $idTokenExploded = explode('.', $idToken);
44
45         $messagesAndTokenValues = [
46             ['Could not parse out a valid header within the provided token', ''],
47             ['Could not parse out a valid header within the provided token', 'cat'],
48             ['Could not parse out a valid payload within the provided token', $idTokenExploded[0]],
49             ['Could not parse out a valid payload within the provided token', $idTokenExploded[0] . '.' . 'dog'],
50             ['Could not parse out a valid signature within the provided token', $idTokenExploded[0] . '.' . $idTokenExploded[1]],
51             ['Could not parse out a valid signature within the provided token', $idTokenExploded[0] . '.' . $idTokenExploded[1] . '.' . '@$%'],
52         ];
53
54         foreach ($messagesAndTokenValues as [$message, $tokenValue]) {
55             $token = new OidcIdToken($tokenValue, OidcJwtHelper::defaultIssuer(), []);
56             $err = null;
57
58             try {
59                 $token->validate('abc');
60             } catch (\Exception $exception) {
61                 $err = $exception;
62             }
63
64             $this->assertInstanceOf(OidcInvalidTokenException::class, $err, $message);
65             $this->assertEquals($message, $err->getMessage());
66         }
67     }
68
69     public function test_error_thrown_if_token_signature_not_validated_from_no_keys()
70     {
71         $token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), []);
72         $this->expectException(OidcInvalidTokenException::class);
73         $this->expectExceptionMessage('Token signature could not be validated using the provided keys');
74         $token->validate('abc');
75     }
76
77     public function test_error_thrown_if_token_signature_not_validated_from_non_matching_key()
78     {
79         $token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), [
80             array_merge(OidcJwtHelper::publicJwkKeyArray(), [
81                 'n' => 'iqK-1QkICMf_cusNLpeNnN-bhT0-9WLBvzgwKLALRbrevhdi5ttrLHIQshaSL0DklzfyG2HWRmAnJ9Q7sweEjuRiiqRcSUZbYu8cIv2hLWYu7K_NH67D2WUjl0EnoHEuiVLsZhQe1CmdyLdx087j5nWkd64K49kXRSdxFQUlj8W3NeK3CjMEUdRQ3H4RZzJ4b7uuMiFA29S2ZhMNG20NPbkUVsFL-jiwTd10KSsPT8yBYipI9O7mWsUWt_8KZs1y_vpM_k3SyYihnWpssdzDm1uOZ8U3mzFr1xsLAO718GNUSXk6npSDzLl59HEqa6zs4O9awO2qnSHvcmyELNk31w',
82             ]),
83         ]);
84         $this->expectException(OidcInvalidTokenException::class);
85         $this->expectExceptionMessage('Token signature could not be validated using the provided keys');
86         $token->validate('abc');
87     }
88
89     public function test_error_thrown_if_invalid_key_provided()
90     {
91         $token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), ['url://example.com']);
92         $this->expectException(OidcInvalidTokenException::class);
93         $this->expectExceptionMessage('Unexpected type of key value provided');
94         $token->validate('abc');
95     }
96
97     public function test_error_thrown_if_token_algorithm_is_not_rs256()
98     {
99         $token = new OidcIdToken(OidcJwtHelper::idToken([], ['alg' => 'HS256']), OidcJwtHelper::defaultIssuer(), []);
100         $this->expectException(OidcInvalidTokenException::class);
101         $this->expectExceptionMessage('Only RS256 signature validation is supported. Token reports using HS256');
102         $token->validate('abc');
103     }
104
105     public function test_token_claim_error_cases()
106     {
107         /** @var array<array{0: string: 1: array}> $claimOverridesByErrorMessage */
108         $claimOverridesByErrorMessage = [
109             // 1. iss claim present
110             ['Missing or non-matching token issuer value', ['iss' => null]],
111             // 1. iss claim matches provided issuer
112             ['Missing or non-matching token issuer value', ['iss' => 'https://auth.example.co.uk']],
113             // 2. aud claim present
114             ['Missing token audience value', ['aud' => null]],
115             // 2. aud claim validates all values against those expected (Only expect single)
116             ['Token audience value has 2 values, Expected 1', ['aud' => ['abc', 'def']]],
117             // 2. aud claim matches client id
118             ['Token audience value did not match the expected client_id', ['aud' => 'xxyyzz.aaa.bbccdd.456']],
119             // 4. azp claim matches client id if present
120             ['Token authorized party exists but does not match the expected client_id', ['azp' => 'xxyyzz.aaa.bbccdd.456']],
121             // 5. exp claim present
122             ['Missing token expiration time value', ['exp' => null]],
123             // 5. exp claim not expired
124             ['Token has expired', ['exp' => time() - 360]],
125             // 6. iat claim present
126             ['Missing token issued at time value', ['iat' => null]],
127             // 6. iat claim too far in the future
128             ['Token issue at time is not recent or is invalid', ['iat' => time() + 600]],
129             // 6. iat claim too far in the past
130             ['Token issue at time is not recent or is invalid', ['iat' => time() - 172800]],
131
132             // Custom: sub is present
133             ['Missing token subject value', ['sub' => null]],
134         ];
135
136         foreach ($claimOverridesByErrorMessage as [$message, $overrides]) {
137             $token = new OidcIdToken(OidcJwtHelper::idToken($overrides), OidcJwtHelper::defaultIssuer(), [
138                 OidcJwtHelper::publicJwkKeyArray(),
139             ]);
140
141             $err = null;
142
143             try {
144                 $token->validate('xxyyzz.aaa.bbccdd.123');
145             } catch (\Exception $exception) {
146                 $err = $exception;
147             }
148
149             $this->assertInstanceOf(OidcInvalidTokenException::class, $err, $message);
150             $this->assertEquals($message, $err->getMessage());
151         }
152     }
153
154     public function test_keys_can_be_a_local_file_reference_to_pem_key()
155     {
156         $file = tmpfile();
157         $testFilePath = 'file://' . stream_get_meta_data($file)['uri'];
158         file_put_contents($testFilePath, OidcJwtHelper::publicPemKey());
159         $token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), [
160             $testFilePath,
161         ]);
162
163         $this->assertTrue($token->validate('xxyyzz.aaa.bbccdd.123'));
164         unlink($testFilePath);
165     }
166 }
Morty Proxy This is a proxified and sanitized view of the page, visit original site.