ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
LocalUserPasswordTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
23use ILIAS\User\Tests\BaseTestCase as ilUserBaseTestCase;
24use org\bovigo\vfs;
25
26class LocalUserPasswordTest extends ilUserBaseTestCase
27{
28 private const PASSWORD = 'password';
29 private const ENCODED_PASSWORD = 'encoded';
30 protected vfs\vfsStreamDirectory $testDirectory;
31 protected string $testDirectoryUrl;
32
33 public function getTestDirectory(): vfs\vfsStreamDirectory
34 {
36 }
37
38 public function setTestDirectory(vfs\vfsStreamDirectory $testDirectory): void
39 {
40 $this->testDirectory = $testDirectory;
41 }
42
43 public function getTestDirectoryUrl(): string
44 {
46 }
47
48 public function setTestDirectoryUrl(string $testDirectoryUrl): void
49 {
50 $this->testDirectoryUrl = $testDirectoryUrl;
51 }
52
53 protected function setUp(): void
54 {
55 vfs\vfsStream::setup();
56 $this->setTestDirectory(vfs\vfsStream::newDirectory('tests')->at(vfs\vfsStreamWrapper::getRoot()));
57 $this->setTestDirectoryUrl(vfs\vfsStream::url('root/tests'));
58
59 parent::setUp();
60 }
61
66 {
67 $this->expectException(ilUserException::class);
68 new LocalUserPasswordManager(['data_directory' => $this->getTestDirectoryUrl()]);
69 }
70
75 {
76 $this->expectException(ilUserException::class);
78 'password_encoder' => 'md5',
79 'data_directory' => $this->getTestDirectoryUrl()
80 ]);
81 }
82
87 {
88 $this->expectException(TypeError::class);
89 $this->expectExceptionMessageMatches('/' . preg_quote(LocalUserPasswordManager::class, '/') . '/');
90
92 'password_encoder' => 'md5',
93 'encoder_factory' => 'test',
94 'data_directory' => $this->getTestDirectoryUrl()
95 ]);
96 }
97
102 public function testInstanceCanBeCreated(): void
103 {
104 $call_count = 0;
105 $factory_mock = $this->getMockBuilder(LocalUserPasswordEncoderFactory::class)->disableOriginalConstructor()->getMock();
106 $factory_mock
107 ->expects($this->exactly(2))
108 ->method('getSupportedEncoderNames')
109 ->willReturnCallback(function () use (&$call_count) {
110 $call_count++;
111 if ($call_count === 1) {
112 return [
113 'mockencoder',
114 'second_mockencoder'
115 ];
116 }
117 return [
118 'mockencoder'
119 ];
120 });
121
122 $password_manager = new LocalUserPasswordManager([
123 'password_encoder' => 'md5',
124 'encoder_factory' => $factory_mock,
125 'data_directory' => $this->getTestDirectoryUrl()
126 ]);
127 $this->assertInstanceOf(LocalUserPasswordManager::class, $password_manager);
128 $this->assertEquals('md5', $password_manager->getEncoderName());
129 $this->assertEquals($factory_mock, $password_manager->getEncoderFactory());
130
131 $this->assertTrue($password_manager->isEncodingTypeSupported('second_mockencoder'));
132 $this->assertFalse($password_manager->isEncodingTypeSupported('second_mockencoder'));
133 }
134
140 {
141 $user_mock = $this->getMockBuilder(ilObjUser::class)->disableOriginalConstructor()->getMock();
142 $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
143 $factory_mock = $this->getMockBuilder(LocalUserPasswordEncoderFactory::class)->disableOriginalConstructor()->getMock();
144
145 $user_mock->expects($this->once())->method('setPasswordSalt')->with($this->isType('string'));
146 $user_mock->expects($this->once())->method('getPasswordSalt')->willReturn('asuperrandomsalt');
147 $user_mock->expects($this->once())->method('setPasswordEncodingType')->with($this->equalTo('mockencoder'));
148 $user_mock->expects($this->once())->method('setPasswd')->with(
149 $this->equalTo(self::ENCODED_PASSWORD),
150 $this->equalTo(ilObjUser::PASSWD_CRYPTED)
151 );
152
153 $encoder->expects($this->once())->method('getName')->willReturn('mockencoder');
154 $encoder->expects($this->once())->method('requiresSalt')->willReturn(true);
155 $encoder->expects($this->once())->method('encodePassword')
156 ->with(
157 $this->equalTo(self::PASSWORD),
158 $this->isType('string')
159 )->willReturn(self::ENCODED_PASSWORD)
160 ;
161
162 $factory_mock->expects($this->once())->method('getEncoderByName')->willReturn($encoder);
163
164 $password_manager = new LocalUserPasswordManager([
165 'password_encoder' => 'mockencoder',
166 'encoder_factory' => $factory_mock,
167 'data_directory' => $this->getTestDirectoryUrl()
168 ]);
169
170 $password_manager->encodePassword($user_mock, self::PASSWORD);
171 }
172
178 {
179 $user_mock = $this->getMockBuilder(ilObjUser::class)->disableOriginalConstructor()->getMock();
180 $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
181 $factory_mock = $this->getMockBuilder(LocalUserPasswordEncoderFactory::class)->disableOriginalConstructor()->getMock();
182
183 $user_mock->expects($this->once())->method('setPasswordSalt')->with($this->equalTo(null));
184 $user_mock->expects($this->once())->method('getPasswordSalt')->willReturn(null);
185 $user_mock->expects($this->once())->method('setPasswordEncodingType')->with($this->equalTo('mockencoder'));
186 $user_mock->expects($this->once())->method('setPasswd')->with(
187 $this->equalTo(self::ENCODED_PASSWORD),
188 $this->equalTo(ilObjUser::PASSWD_CRYPTED)
189 );
190
191 $encoder->expects($this->once())->method('getName')->willReturn('mockencoder');
192 $encoder->expects($this->once())->method('requiresSalt')->willReturn(false);
193 $encoder->expects($this->once())->method('encodePassword')->with(
194 $this->equalTo(self::PASSWORD),
195 $this->equalTo(null)
196 )->willReturn(self::ENCODED_PASSWORD);
197
198 $factory_mock->expects($this->once())->method('getEncoderByName')->willReturn($encoder);
199
200 $password_manager = new LocalUserPasswordManager([
201 'password_encoder' => 'mockencoder',
202 'encoder_factory' => $factory_mock,
203 'data_directory' => $this->getTestDirectoryUrl()
204 ]);
205
206 $password_manager->encodePassword($user_mock, self::PASSWORD);
207 }
208
214 {
215 $user_mock = $this->getMockBuilder(ilObjUser::class)->disableOriginalConstructor()->getMock();
216 $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
217 $factory_mock = $this->getMockBuilder(LocalUserPasswordEncoderFactory::class)->disableOriginalConstructor()->getMock();
218
219 $user_mock->expects($this->atLeast(1))->method('getPasswordSalt')->willReturn('asuperrandomsalt');
220 $user_mock->expects($this->atLeast(1))->method('getPasswordEncodingType')->willReturn('mockencoder');
221 $user_mock->expects($this->atLeast(1))->method('getPasswd')->willReturn(self::ENCODED_PASSWORD);
222 $user_mock->expects($this->never())->method('resetPassword');
223
224 $encoder->expects($this->once())->method('getName')->willReturn('mockencoder');
225 $encoder->expects($this->once())->method('isPasswordValid')->with(
226 $this->equalTo(self::ENCODED_PASSWORD),
227 $this->equalTo(self::PASSWORD),
228 $this->isType('string')
229 )->willReturn(true);
230 $encoder->expects($this->once())->method('requiresReencoding')
231 ->with($this->equalTo(self::ENCODED_PASSWORD))
232 ->willReturn(false)
233 ;
234
235 $factory_mock->expects($this->once())->method('getEncoderByName')->willReturn($encoder);
236
237 $password_manager = new LocalUserPasswordManager([
238 'password_encoder' => 'mockencoder',
239 'encoder_factory' => $factory_mock,
240 'data_directory' => $this->getTestDirectoryUrl()
241 ]);
242
243 $this->assertTrue($password_manager->verifyPassword($user_mock, self::PASSWORD));
244 }
245
251 {
252 $user_mock = $this->getMockBuilder(ilObjUser::class)->disableOriginalConstructor()->getMock();
253 $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
254 $factory_mock = $this->getMockBuilder(LocalUserPasswordEncoderFactory::class)->disableOriginalConstructor()->getMock();
255
256 $user_mock->expects($this->once())->method('getPasswordSalt')->willReturn('asuperrandomsalt');
257 $user_mock->expects($this->once())->method('getPasswordEncodingType')->willReturn('second_mockencoder');
258 $user_mock->expects($this->once())->method('getPasswd')->willReturn(self::ENCODED_PASSWORD);
259 $user_mock->expects($this->once())->method('resetPassword')->with(
260 $this->equalTo(self::PASSWORD)
261 );
262
263 $encoder->expects($this->once())->method('getName')->willReturn('second_mockencoder');
264 $encoder->expects($this->once())->method('isPasswordValid')->with(
265 $this->equalTo(self::ENCODED_PASSWORD),
266 $this->equalTo(self::PASSWORD),
267 $this->isType('string')
268 )->willReturn(true);
269 $encoder->expects($this->never())->method('requiresReencoding')
270 ->with($this->equalTo(self::ENCODED_PASSWORD))
271 ->willReturn(false)
272 ;
273
274 $factory_mock->expects($this->once())->method('getEncoderByName')->willReturn($encoder);
275
276 $password_manager = new LocalUserPasswordManager([
277 'password_encoder' => 'mockencoder',
278 'encoder_factory' => $factory_mock,
279 'data_directory' => $this->getTestDirectoryUrl()
280 ]);
281
282 $this->assertTrue($password_manager->verifyPassword($user_mock, self::PASSWORD));
283 }
284
290 {
291 $user_mock = $this->getMockBuilder(ilObjUser::class)->disableOriginalConstructor()->getMock();
292 $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
293 $factory_mock = $this->getMockBuilder(LocalUserPasswordEncoderFactory::class)->disableOriginalConstructor()->getMock();
294
295 $user_mock->expects($this->once())->method('getPasswordSalt')->willReturn('asuperrandomsalt');
296 $user_mock->expects($this->once())->method('getPasswordEncodingType')->willReturn('mockencoder');
297 $user_mock->expects($this->exactly(2))->method('getPasswd')->willReturn(self::ENCODED_PASSWORD);
298 $user_mock->expects($this->once())->method('resetPassword')->with(
299 $this->equalTo(self::PASSWORD)
300 );
301
302 $encoder->expects($this->once())->method('getName')->willReturn('mockencoder');
303 $encoder->expects($this->once())->method('isPasswordValid')->with(
304 $this->equalTo(self::ENCODED_PASSWORD),
305 $this->equalTo(self::PASSWORD),
306 $this->isType('string')
307 )->willReturn(true);
308 $encoder->expects($this->once())->method('requiresReencoding')
309 ->with($this->equalTo(self::ENCODED_PASSWORD))
310 ->willReturn(true)
311 ;
312
313 $factory_mock->expects($this->once())->method('getEncoderByName')->willReturn($encoder);
314
315 $password_manager = new LocalUserPasswordManager([
316 'password_encoder' => 'mockencoder',
317 'encoder_factory' => $factory_mock,
318 'data_directory' => $this->getTestDirectoryUrl()
319 ]);
320
321 $this->assertTrue($password_manager->verifyPassword($user_mock, self::PASSWORD));
322 }
323
329 {
330 $user_mock = $this->getMockBuilder(ilObjUser::class)->disableOriginalConstructor()->getMock();
331 $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
332 $factory_mock = $this->getMockBuilder(LocalUserPasswordEncoderFactory::class)->disableOriginalConstructor()->getMock();
333
334 $user_mock->expects($this->once())->method('getPasswordSalt')->willReturn('asuperrandomsalt');
335 $user_mock->expects($this->once())->method('getPasswordEncodingType')->willReturn('second_mockencoder');
336 $user_mock->expects($this->once())->method('getPasswd')->willReturn(self::ENCODED_PASSWORD);
337 $user_mock->expects($this->never())->method('resetPassword');
338
339 $encoder->expects($this->once())->method('getName')->willReturn('second_mockencoder');
340 $encoder->expects($this->never())->method('requiresReencoding');
341 $encoder->expects($this->once())->method('isPasswordValid')
342 ->with(
343 $this->equalTo(self::ENCODED_PASSWORD),
344 $this->equalTo(self::PASSWORD),
345 $this->isType('string')
346 )->willReturn(false)
347 ;
348
349 $factory_mock->expects($this->once())->method('getEncoderByName')->willReturn($encoder);
350
351 $password_manager = new LocalUserPasswordManager([
352 'password_encoder' => 'mockencoder',
353 'encoder_factory' => $factory_mock,
354 'data_directory' => $this->getTestDirectoryUrl()
355 ]);
356
357 $this->assertFalse($password_manager->verifyPassword($user_mock, self::PASSWORD));
358 }
359
363 public function testFactoryCanBeCreated(): void
364 {
365 $factory = new LocalUserPasswordEncoderFactory([
366 'data_directory' => $this->getTestDirectoryUrl()
367 ]);
368 $this->assertInstanceOf(LocalUserPasswordEncoderFactory::class, $factory);
369 }
370
377 {
378 $factory = new LocalUserPasswordEncoderFactory([
379 'default_password_encoder' => 'md5',
380 'data_directory' => $this->getTestDirectoryUrl()
381 ]);
382 $this->assertEquals('md5', $factory->getDefaultEncoder());
383
384 $encoder = $this->createMock(ilPasswordEncoder::class);
385 $encoder->expects($this->atLeastOnce())->method('getName')->willReturn('mockencoder');
386 $encoder->expects($this->atLeastOnce())->method('isSupportedByRuntime')->willReturn(true);
387
388 $second_mockencoder = $this->createMock(ilPasswordEncoder::class);
389 $second_mockencoder->expects($this->atLeastOnce())->method('getName')->willReturn('second_mockencoder');
390 $second_mockencoder->expects($this->atLeastOnce())->method('isSupportedByRuntime')->willReturn(true);
391
392 $factory->setSupportedEncoders([$encoder, $second_mockencoder]);
393 $this->assertCount(2, $factory->getSupportedEncoders());
394 $this->assertCount(2, $factory->getSupportedEncoderNames());
395 $this->assertCount(
396 0,
397 array_diff(['mockencoder', 'second_mockencoder'], $factory->getSupportedEncoderNames())
398 );
399 $this->assertCount(
400 0,
401 array_diff($factory->getSupportedEncoderNames(), ['mockencoder', 'second_mockencoder'])
402 );
403 }
404
410 {
411 $this->expectException(ilUserException::class);
412 $factory = new LocalUserPasswordEncoderFactory([
413 'data_directory' => $this->getTestDirectoryUrl()
414 ]);
415 $factory->setSupportedEncoders(['phpunit']);
416 }
417
423 {
424 $this->expectException(ilUserException::class);
425 $factory = new LocalUserPasswordEncoderFactory([
426 'data_directory' => $this->getTestDirectoryUrl()
427 ]);
428 $factory->getEncoderByName('phpunit');
429 }
430
436 {
437 $this->expectException(ilUserException::class);
438 $factory = new LocalUserPasswordEncoderFactory([
439 'default_password_encoder' => 'phpunit',
440 'data_directory' => $this->getTestDirectoryUrl()
441 ]);
442 $factory->getEncoderByName('phpunit');
443 }
444
451 {
452 $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
453 $encoder->expects($this->atLeastOnce())->method('getName')->willReturn('mockencoder');
454 $encoder->expects($this->atLeastOnce())->method('isSupportedByRuntime')->willReturn(true);
455
456 $factory = new LocalUserPasswordEncoderFactory([
457 'default_password_encoder' => $encoder->getName(),
458 'data_directory' => $this->getTestDirectoryUrl()
459 ]);
460 $factory->setSupportedEncoders([$encoder]);
461 $this->assertEquals($encoder, $factory->getEncoderByName('phpunit'));
462 }
463
470 {
471 $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
472 $encoder->expects($this->atLeastOnce())->method('getName')->willReturn('mockencoder');
473 $encoder->expects($this->atLeastOnce())->method('isSupportedByRuntime')->willReturn(true);
474
475 $factory = new LocalUserPasswordEncoderFactory([
476 'default_password_encoder' => $encoder->getName(),
477 'data_directory' => $this->getTestDirectoryUrl()
478 ]);
479 $factory->setSupportedEncoders([$encoder]);
480 $this->assertEquals($encoder, $factory->getEncoderByName('mockencoder'));
481 }
482}
testPasswordManagerMigratesPasswordOnVerificationWithVariantEncoders()
setTestDirectoryUrl(string $testDirectoryUrl)
testFactoryRaisesAnExceptionIfAnUnsupportedEncoderIsRequestedAndTheDefaultEncoderDoesNotMatchOneOfTheSupportedEncodersInFallbackMode()
testExceptionIsRaisedIfPasswordManagerIsCreatedWithoutValidFactory()
vfs vfsStreamDirectory $testDirectory
testPasswordManagerNeverMigratesPasswordOnFailedVerificationWithVariantEncoders()
testFactoryReturnsTheDefaultEncoderIfAnUnsupportedEncoderIsRequestedAndASupportedDefaultEncoderWasSpecifiedInFallbackMode()
testFactoryRaisesAnExceptionIfAnUnsupportedEncoderWasInjected()
testExceptionIsRaisedIfPasswordManagerIsCreatedWithoutFactory()
testExceptionIsRaisedIfPasswordManagerIsCreatedWithoutEncoderInformation()
setTestDirectory(vfs\vfsStreamDirectory $testDirectory)
testFactoryRaisesAnExceptionIfAnUnsupportedEncoderIsRequestedAndNoDefaultEncoderWasSpecifiedInFallbackMode()
const PASSWD_CRYPTED