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\Authenticator; |
9
|
|
|
|
10
|
|
|
use KleijnWeb\JwtBundle\Authenticator\Authenticator; |
11
|
|
|
use KleijnWeb\JwtBundle\Authenticator\JwtKey; |
12
|
|
|
use KleijnWeb\JwtBundle\Authenticator\JwtToken; |
13
|
|
|
use KleijnWeb\JwtBundle\User\JwtUserProvider; |
14
|
|
|
use KleijnWeb\JwtBundle\User\UnsafeGroupsUserInterface; |
15
|
|
|
use Symfony\Component\HttpFoundation\Request; |
16
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; |
17
|
|
|
use Symfony\Component\Security\Core\User\User; |
18
|
|
|
use Symfony\Component\Security\Core\User\UserProviderInterface; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @author John Kleijn <[email protected]> |
22
|
|
|
*/ |
23
|
|
|
class AuthenticatorTest extends \PHPUnit_Framework_TestCase |
24
|
|
|
{ |
25
|
|
|
// @codingStandardsIgnoreStart |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Created using jwt.io |
29
|
|
|
*/ |
30
|
|
|
const TEST_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleU9uZSJ9.eyJwcm4iOiJqb2huIiwiaXNzIjoiaHR0cDovL2FwaS5zZXJ2ZXIxLmNvbS9vYXV0aDIvdG9rZW4ifQ._jXjAWMzwwG1v5N3ZOEUoLGSINtmwLsvQdfYkYAcWiY'; |
31
|
|
|
|
32
|
|
|
const JKEY_CLASS = 'KleijnWeb\JwtBundle\Authenticator\JwtKey'; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var array |
36
|
|
|
*/ |
37
|
|
|
private static $keyConfig = [ |
38
|
|
|
'keyOne' => |
39
|
|
|
[ |
40
|
|
|
'issuer' => 'http://api.server1.com/oauth2/token', |
41
|
|
|
'secret' => 'A Pre-Shared Key', |
42
|
|
|
'type' => 'HS256', |
43
|
|
|
], |
44
|
|
|
'keyTwo' => |
45
|
|
|
[ |
46
|
|
|
'issuer' => 'http://api.server2.com/oauth2/token', |
47
|
|
|
'type' => 'RS256', |
48
|
|
|
'secret' => 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0F', |
49
|
|
|
], |
50
|
|
|
]; |
51
|
|
|
|
52
|
|
|
// @codingStandardsIgnoreEnd |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var JwtKey[] |
56
|
|
|
*/ |
57
|
|
|
private $keys = []; |
58
|
|
|
|
59
|
|
|
protected function setUp() |
60
|
|
|
{ |
61
|
|
|
foreach (self::$keyConfig as $keyId => $config) { |
62
|
|
|
$config['kid'] = $keyId; |
63
|
|
|
$this->keys[$keyId] = new JwtKey($config); |
64
|
|
|
} |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @test |
69
|
|
|
*/ |
70
|
|
|
public function getGetKeysUsingIndexesInConfig() |
71
|
|
|
{ |
72
|
|
|
$authenticator = new Authenticator($this->keys); |
73
|
|
|
|
74
|
|
|
$this->assertInstanceOf(self::JKEY_CLASS, $authenticator->getKeyById('keyOne')); |
75
|
|
|
$this->assertInstanceOf(self::JKEY_CLASS, $authenticator->getKeyById('keyTwo')); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @test |
80
|
|
|
*/ |
81
|
|
|
public function willGetSingleKeyWhenKeyIdIsNull() |
82
|
|
|
{ |
83
|
|
|
$config = $this->keys; |
84
|
|
|
unset($config['keyTwo']); |
85
|
|
|
|
86
|
|
|
$authenticator = new Authenticator($config); |
87
|
|
|
|
88
|
|
|
$this->assertInstanceOf(self::JKEY_CLASS, $authenticator->getKeyById(null)); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @test |
93
|
|
|
* @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationException |
94
|
|
|
*/ |
95
|
|
|
public function willFailWhenTryingToGetKeyWithoutIdWhenThereAreMoreThanOne() |
96
|
|
|
{ |
97
|
|
|
$authenticator = new Authenticator($this->keys); |
98
|
|
|
|
99
|
|
|
$this->assertInstanceOf(self::JKEY_CLASS, $authenticator->getKeyById(null)); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @test |
104
|
|
|
* @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationException |
105
|
|
|
*/ |
106
|
|
|
public function willFailWhenTryingToGetUnknownKey() |
107
|
|
|
{ |
108
|
|
|
$authenticator = new Authenticator($this->keys); |
109
|
|
|
|
110
|
|
|
$this->assertInstanceOf(self::JKEY_CLASS, $authenticator->getKeyById('blah')); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* @test |
115
|
|
|
*/ |
116
|
|
|
public function authenticateTokenWillSetUserFetchedFromUserProviderOnToken() |
117
|
|
|
{ |
118
|
|
|
$jwtToken = $this->createToken(['sub' => 'john']); |
119
|
|
|
$authenticator = new Authenticator($this->keys); |
120
|
|
|
$anonToken = new PreAuthenticatedToken('foo', $jwtToken, 'myprovider'); |
121
|
|
|
|
122
|
|
|
$userProvider = $this->getMockBuilder( |
123
|
|
|
'Symfony\Component\Security\Core\User\UserProviderInterface' |
124
|
|
|
)->getMockForAbstractClass(); |
125
|
|
|
|
126
|
|
|
$userProvider->expects($this->once()) |
127
|
|
|
->method('loadUserByUsername') |
128
|
|
|
->with('john') |
129
|
|
|
->willReturn(new User('john', 'hi there')); |
130
|
|
|
|
131
|
|
|
$authenticator->authenticateToken($anonToken, $userProvider, 'myprovider'); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* @test |
136
|
|
|
* @expectedException \UnexpectedValueException |
137
|
|
|
*/ |
138
|
|
View Code Duplication |
public function authenticateTokenWillFailIfCredentialsAreNotJwtToken() |
|
|
|
|
139
|
|
|
{ |
140
|
|
|
$authenticator = new Authenticator($this->keys); |
141
|
|
|
$anonToken = new PreAuthenticatedToken('foo', ['sub' => 'john'], 'myprovider'); |
142
|
|
|
|
143
|
|
|
$userProvider = $this->getMockBuilder( |
144
|
|
|
'Symfony\Component\Security\Core\User\UserProviderInterface' |
145
|
|
|
)->getMockForAbstractClass(); |
146
|
|
|
|
147
|
|
|
$authenticator->authenticateToken($anonToken, $userProvider, 'myprovider'); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* @test |
152
|
|
|
*/ |
153
|
|
View Code Duplication |
public function supportsPreAuthToken() |
|
|
|
|
154
|
|
|
{ |
155
|
|
|
$authenticator = new Authenticator($this->keys); |
156
|
|
|
|
157
|
|
|
$securityToken = new PreAuthenticatedToken('foo', 'bar', 'myprovider'); |
158
|
|
|
$actual = $authenticator->supportsToken($securityToken, 'myprovider'); |
159
|
|
|
$this->assertTrue($actual); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @test |
164
|
|
|
* @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException |
165
|
|
|
*/ |
166
|
|
|
public function willFailWhenApiKeyNotFoundInHeader() |
167
|
|
|
{ |
168
|
|
|
$authenticator = new Authenticator($this->keys); |
169
|
|
|
$request = new Request(); |
170
|
|
|
$authenticator->createToken($request, 'myprovider'); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* @test |
175
|
|
|
*/ |
176
|
|
|
public function canGetAnonTokenWithClaims() |
177
|
|
|
{ |
178
|
|
|
$authenticator = new Authenticator($this->keys); |
179
|
|
|
$request = new Request(); |
180
|
|
|
$request->headers->set('Authorization', 'Bearer ' . self::TEST_TOKEN); |
181
|
|
|
$token = $authenticator->createToken($request, 'myprovider'); |
182
|
|
|
|
183
|
|
|
$expected = new JwtToken(self::TEST_TOKEN); |
184
|
|
|
$this->assertEquals($expected, $token->getCredentials()); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @test |
189
|
|
|
*/ |
190
|
|
View Code Duplication |
public function willSetClaimsOnJwtUserProvider() |
|
|
|
|
191
|
|
|
{ |
192
|
|
|
$authenticator = new Authenticator($this->keys); |
193
|
|
|
$token = $this->createToken(['sub' => 'john', 'aud' => 'guests']); |
194
|
|
|
$anonToken = new PreAuthenticatedToken('foo', $token, 'myprovider'); |
195
|
|
|
|
196
|
|
|
$user = $this->getMockBuilder(UnsafeGroupsUserInterface::class)->getMockForAbstractClass(); |
197
|
|
|
|
198
|
|
|
$userProvider = $this->getMockBuilder(JwtUserProvider::class)->disableOriginalConstructor()->getMock(); |
199
|
|
|
|
200
|
|
|
$userProvider |
201
|
|
|
->expects($this->once()) |
202
|
|
|
->method('loadUserByUsername') |
203
|
|
|
->willReturn($user); |
204
|
|
|
|
205
|
|
|
$userProvider |
206
|
|
|
->expects($this->once()) |
207
|
|
|
->method('setClaimsUsingToken') |
208
|
|
|
->with($token); |
209
|
|
|
|
210
|
|
|
$user->expects($this->once()) |
211
|
|
|
->method('getRoles') |
212
|
|
|
->willReturn(['ROLE_GUESTS']); |
213
|
|
|
|
214
|
|
|
$authenticator->authenticateToken($anonToken, $userProvider, 'myprovider'); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* @deprecated |
219
|
|
|
* @test |
220
|
|
|
*/ |
221
|
|
View Code Duplication |
public function willAddRolesFromAudienceClaimsInToken() |
|
|
|
|
222
|
|
|
{ |
223
|
|
|
$authenticator = new Authenticator($this->keys); |
224
|
|
|
$token = $this->createToken(['sub' => 'john', 'aud' => 'guests']); |
225
|
|
|
$anonToken = new PreAuthenticatedToken('foo', $token, 'myprovider'); |
226
|
|
|
|
227
|
|
|
$user = $this->getMockBuilder(UnsafeGroupsUserInterface::class)->getMockForAbstractClass(); |
228
|
|
|
|
229
|
|
|
$userProvider = $this->getMockBuilder(UserProviderInterface::class)->getMockForAbstractClass(); |
230
|
|
|
|
231
|
|
|
$userProvider->expects($this->once()) |
232
|
|
|
->method('loadUserByUsername') |
233
|
|
|
->willReturn($user); |
234
|
|
|
|
235
|
|
|
$user->expects($this->once()) |
236
|
|
|
->method('addRole') |
237
|
|
|
->with('ROLE_GUESTS'); |
238
|
|
|
|
239
|
|
|
$user->expects($this->once()) |
240
|
|
|
->method('getRoles') |
241
|
|
|
->willReturn(['ROLE_GUESTS']); |
242
|
|
|
|
243
|
|
|
$authenticator->authenticateToken($anonToken, $userProvider, 'myprovider'); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* @deprecated |
248
|
|
|
* @test |
249
|
|
|
*/ |
250
|
|
|
public function willAddMultipleRolesFromAudienceClaimsInToken() |
251
|
|
|
{ |
252
|
|
|
$authenticator = new Authenticator($this->keys); |
253
|
|
|
$token = $this->createToken(['sub' => 'john', 'aud' => ['guests', 'users']]); |
254
|
|
|
$anonToken = new PreAuthenticatedToken('foo', $token, 'myprovider'); |
255
|
|
|
|
256
|
|
|
$user = $this->getMockBuilder(UnsafeGroupsUserInterface::class)->getMockForAbstractClass(); |
257
|
|
|
|
258
|
|
|
$userProvider = $this->getMockBuilder(UserProviderInterface::class)->getMockForAbstractClass(); |
259
|
|
|
|
260
|
|
|
$userProvider->expects($this->once()) |
261
|
|
|
->method('loadUserByUsername') |
262
|
|
|
->willReturn($user); |
263
|
|
|
|
264
|
|
|
$user->expects($this->exactly(2)) |
265
|
|
|
->method('addRole'); |
266
|
|
|
|
267
|
|
|
$user->expects($this->once()) |
268
|
|
|
->method('getRoles') |
269
|
|
|
->willReturn(['guests', 'users']); |
270
|
|
|
|
271
|
|
|
$authenticator->authenticateToken($anonToken, $userProvider, 'myprovider'); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* @param array $claims |
276
|
|
|
* |
277
|
|
|
* @return JwtToken |
278
|
|
|
*/ |
279
|
|
View Code Duplication |
private function createToken(array $claims) |
|
|
|
|
280
|
|
|
{ |
281
|
|
|
return new JwtToken([ |
282
|
|
|
'header' => [ |
283
|
|
|
'alg' => 'HS256', |
284
|
|
|
'typ' => 'JWT', |
285
|
|
|
'kid' => 'keyOne' |
286
|
|
|
], |
287
|
|
|
'claims' => $claims, |
288
|
|
|
'secret' => 'secret' |
289
|
|
|
]); |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
|
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.