Completed
Push — master ( ff92f6...3ef1d3 )
by John
09:08
created

validationWillFailWhenPrincipleIsMissing()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 14
Ratio 100 %

Importance

Changes 0
Metric Value
dl 14
loc 14
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 0
1
<?php
2
/*
3
 * This file is part of the KleijnWeb\JwtBundle package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
namespace KleijnWeb\JwtBundle\Tests\Jwt;
9
10
use KleijnWeb\JwtBundle\Jwt\Exception\InvalidTimeException;
11
use KleijnWeb\JwtBundle\Jwt\Exception\MissingClaimsException;
12
use KleijnWeb\JwtBundle\Jwt\JwtKey;
13
use KleijnWeb\JwtBundle\Jwt\JwtToken;
14
use KleijnWeb\JwtBundle\Jwt\SecretLoader;
15
use KleijnWeb\JwtBundle\Jwt\SignatureValidator\HmacValidator;
16
use KleijnWeb\JwtBundle\Jwt\SignatureValidator\RsaValidator;
17
18
/**
19
 * @author John Kleijn <[email protected]>
20
 */
21
class JwtKeyTest extends \PHPUnit_Framework_TestCase
22
{
23
    /**
24
     * @test
25
     * @expectedException \InvalidArgumentException
26
     */
27
    public function constructionWillFailWhenSecretNorLoaderPassed()
28
    {
29
        new JwtKey([]);
30
    }
31
32
    /**
33
     * @test
34
     */
35
    public function canOmitSecretWhenPassingLoader()
36
    {
37
        $actual   = new JwtKey(['loader' => 'foo']);
38
        $refl     = new \ReflectionClass($actual);
39
        $property = $refl->getProperty('secret');
40
        $property->setAccessible(true);
41
        $this->assertNull($property->getValue($actual));
42
    }
43
44
    /**
45
     * @test
46
     * @expectedException \InvalidArgumentException
47
     */
48
    public function cannotPassBothSecretAndLoader()
49
    {
50
        new JwtKey(['loader' => 'foo', 'secret' => 'bar']);
51
    }
52
53
    /**
54
     * @test
55
     */
56
    public function serializingWillClearSecret()
57
    {
58
        $key      = new JwtKey(['secret' => 'Buy the book']);
59
        $actual   = unserialize(serialize($key));
60
        $refl     = new \ReflectionClass($actual);
61
        $property = $refl->getProperty('secret');
62
        $property->setAccessible(true);
63
        $this->assertNull($property->getValue($actual));
64
    }
65
66
    /**
67
     * @test
68
     */
69
    public function validateTokenWillCallVerifySignatureOnToken()
70
    {
71
        $secret = (string)rand();
72
        $key    = new JwtKey(['secret' => $secret]);
73
        $key->validateToken($this->createTokenMock($secret, $key));
74
    }
75
76
    /**
77
     * @test
78
     */
79
    public function willValidateIfAudienceIsConfiguredAndMatchedAny()
80
    {
81
        $key = new JwtKey(['secret' => 'Buy the book', 'audience' => ['author', 'reader']]);
82
        $key->validateClaims(['sub' => 'john', 'aud' => 'reader']);
83
    }
84
85
    /**
86
     * @test
87
     */
88
    public function canLoadSecretFromLoader()
89
    {
90
        $secret = (string)rand();
91
        $token  = $this->createTokenMock($secret);
92
93
        $loaderMock = $this->getMockBuilder(SecretLoader::class)->getMock();
94
        $loaderMock->expects($this->once())->method('load')->with($token)->willReturn($secret);
95
96
        $key = new JwtKey(['loader' => $loaderMock]);
97
98
        $key->validateToken($token);
99
    }
100
101
    /**
102
     * @test
103
     */
104
    public function willGetRsaSignatureValidatorWhenTypeIsNotSpecified()
105
    {
106
        $key    = new JwtKey(['secret' => 'Buy the book']);
107
        $actual = $key->getSignatureValidator();
108
        $this->assertInstanceOf(HmacValidator::class, $actual);
109
    }
110
111
    /**
112
     * @test
113
     */
114
    public function willGetRsaSignatureValidatorWhenTypeIsRsa()
115
    {
116
        $key    = new JwtKey(['secret' => 'Buy the book', 'type' => JwtKey::TYPE_RSA]);
117
        $actual = $key->getSignatureValidator();
118
        $this->assertInstanceOf(RsaValidator::class, $actual);
119
    }
120
121
    /**
122
     * @test
123
     */
124 View Code Duplication
    public function validationWillFailWhenPrincipleIsMissing()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
125
    {
126
        $claims = ['prn' => 'joe'];
127
128
        $key = new JwtKey(['secret' => 'Buy the book']);
129
        $key->validateClaims($claims);
130
131
        unset($claims['prn']);
132
133
        $this->expectException(MissingClaimsException::class);
134
135
        $key = new JwtKey(['secret' => 'Buy the book']);
136
        $key->validateClaims($claims);
137
    }
138
139
140
    /**
141
     * @test
142
     */
143 View Code Duplication
    public function validationWillFailWhenSubjectMissing()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
144
    {
145
        $claims = ['sub' => 'joe'];
146
147
        $key = new JwtKey(['secret' => 'Buy the book']);
148
        $key->validateClaims($claims);
149
150
        unset($claims['sub']);
151
152
        $this->expectException(MissingClaimsException::class);
153
154
        $key = new JwtKey(['secret' => 'Buy the book']);
155
        $key->validateClaims($claims);
156
    }
157
158
    /**
159
     * @test
160
     */
161 View Code Duplication
    public function validationWillFailWhenExpiredByExp()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
    {
163
        $this->expectException(InvalidTimeException::class);
164
        $this->expectExceptionMessage("Token is expired by 'exp'");
165
166
        $key = new JwtKey(['secret' => 'Buy the book']);
167
        $key->validateClaims(['sub' => 'john', 'exp' => time() - 2]);
168
    }
169
170
    /**
171
     * @test
172
     */
173
    public function validationWillNotFailWhenExpiredByExpButWithinLeeway()
174
    {
175
        $key = new JwtKey(['secret' => 'Buy the book', 'leeway' => 3]);
176
        $key->validateClaims(['sub' => 'john', 'exp' => time() - 2]);
177
    }
178
179
    /**
180
     * @test
181
     */
182
    public function validationWillFailWhenExpiredByIatAndMinIssueTime()
183
    {
184
        $this->expectException(InvalidTimeException::class);
185
        $this->expectExceptionMessage("Server deemed your token too old");
186
        $key = new JwtKey(['secret' => 'Buy the book', 'minIssueTime' => time() + 2, 'leeway' => 3]);
187
        $key->validateClaims(['sub' => 'john', 'iat' => time()]);
188
    }
189
190
    /**
191
     * @test
192
     */
193 View Code Duplication
    public function validationWillFailWhenNotValidYet()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194
    {
195
        $this->expectException(InvalidTimeException::class);
196
        $this->expectExceptionMessage("Token not valid yet");
197
198
        $key = new JwtKey(['secret' => 'Buy the book']);
199
        $key->validateClaims(['sub' => 'john', 'nbf' => time() + 2]);
200
    }
201
202
    /**
203
     * @test
204
     */
205
    public function validationWillNotFailWhenNotValidYetButWithinLeeway()
206
    {
207
        $key = new JwtKey(['secret' => 'Buy the book', 'leeway' => 3]);
208
        $key->validateClaims(['sub' => 'john', 'nbf' => time() + 2]);
209
    }
210
211
    /**
212
     * @test
213
     * @expectedException \InvalidArgumentException
214
     */
215
    public function validationWillFailWhenIssuerDoesNotMatch()
216
    {
217
        $key = new JwtKey(['secret' => 'Buy the book', 'issuer' => 'me']);
218
        $key->validateClaims(['sub' => 'john', 'iss' => 'you']);
219
    }
220
221
    /**
222
     * @test
223
     * @expectedException \InvalidArgumentException
224
     */
225
    public function validationWillFailWhenAudienceDoesNotMatch()
226
    {
227
        $key = new JwtKey(['secret' => 'Buy the book', 'audience' => 'me']);
228
        $key->validateClaims(['sub' => 'john', 'aud' => 'the neighbours']);
229
    }
230
231
232
    /**
233
     * @test
234
     * @expectedException \InvalidArgumentException
235
     */
236
    public function validationWillFailWhenIssuerIsConfiguredAndNotInClaims()
237
    {
238
        $key = new JwtKey(['secret' => 'Buy the book', 'issuer' => 'me']);
239
        $key->validateClaims(['sub' => 'john']);
240
    }
241
242
    /**
243
     * @test
244
     * @expectedException \InvalidArgumentException
245
     */
246
    public function validationWillFailWhenMinIssueTimeIsConfiguredAndIatNotInClaims()
247
    {
248
        $key = new JwtKey(['secret' => 'Buy the book', 'minIssueTime' => time()]);
249
        $key->validateClaims(['sub' => 'john']);
250
    }
251
252
    /**
253
     * @test
254
     * @expectedException \InvalidArgumentException
255
     */
256
    public function validationWillFailWhenAudienceIsConfiguredAndNotInClaims()
257
    {
258
        $key = new JwtKey(['secret' => 'Buy the book', 'audience' => time()]);
259
        $key->validateClaims(['sub' => 'john']);
260
    }
261
262
    /**
263
     * @test
264
     * @expectedException \InvalidArgumentException
265
     */
266
    public function validationWillFailWhenIgnoreOtherReservedAndArbitraryClaimsAreRequiredButNotInClaims()
267
    {
268
        $key = new JwtKey(
269
            ['secret' => 'Buy the book', 'require' => ['jti', 'typ', 'and now for something completely different']]
270
        );
271
        $key->validateClaims(['sub' => 'john']);
272
    }
273
274
    /**
275
     * @test
276
     * @expectedException \InvalidArgumentException
277
     */
278
    public function headerValidationWillFailWhenAlgoIsMissing()
279
    {
280
        $key = new JwtKey(['secret' => 'Buy the book']);
281
        $key->validateHeader(['typ' => 'JWT']);
282
    }
283
284
    /**
285
     * @test
286
     * @expectedException \InvalidArgumentException
287
     */
288
    public function headerValidationWillFailWhenTypeIsMissing()
289
    {
290
        $key = new JwtKey(['secret' => 'Buy the book']);
291
        $key->validateHeader(['alg' => JwtKey::TYPE_HMAC]);
292
    }
293
294
    /**
295
     * @test
296
     * @expectedException \InvalidArgumentException
297
     */
298
    public function headerValidationWillFailWhenKeyAlgoDoesNotMatchTokenAlgo()
299
    {
300
        $key = new JwtKey(['secret' => 'Buy the book', 'type' => JwtKey::TYPE_RSA]);
301
        $key->validateHeader(['typ' => 'JWT', 'alg' => JwtKey::TYPE_HMAC]);
302
    }
303
304
    /**
305
     * @test
306
     * @expectedException \InvalidArgumentException
307
     */
308
    public function headerValidationWillFailWhenTypeIsNotJwt()
309
    {
310
        $key = new JwtKey(['secret' => 'Buy the book']);
311
        $key->validateHeader(['typ' => 'Something']);
312
    }
313
314
    /**
315
     * @param string      $secret
316
     * @param JwtKey|null $key
317
     *
318
     * @return JwtToken
319
     */
320
    private function createTokenMock($secret, JwtKey $key = null)
321
    {
322
        /** @var JwtToken $token */
323
        $token = $tokenMock = $this->getMockBuilder(JwtToken::class)->disableOriginalConstructor()->getMock();
324
325
        $tokenMock->expects($this->once())
326
            ->method('validateSignature')
327
            ->with($secret, $key ? $key->getSignatureValidator() : $this->anything());
328
329
        $tokenMock->expects($this->once())
330
            ->method('getClaims')
331
            ->willReturn(['sub' => 'john']);
332
333
        $tokenMock->expects($this->once())
334
            ->method('getHeader')
335
            ->willReturn(['alg' => JwtKey::TYPE_HMAC, 'typ' => 'JWT']);
336
337
        return $token;
338
    }
339
}
340