ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
LocalUserPasswordTest.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
23 use ILIAS\User\Tests\BaseTestCase as ilUserBaseTestCase;
24 use org\bovigo\vfs;
25 
26 class 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  {
35  return $this->testDirectory;
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 
213  public function testPasswordManagerVerifiesPassword(): void
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  $this->equalTo(self::PASSWORD)
262  );
263 
264  $encoder->expects($this->once())->method('getName')->willReturn('second_mockencoder');
265  $encoder->expects($this->once())->method('isPasswordValid')->with(
266  $this->equalTo(self::ENCODED_PASSWORD),
267  $this->equalTo(self::PASSWORD),
268  $this->isType('string')
269  )->willReturn(true);
270  $encoder->expects($this->never())->method('requiresReencoding')
271  ->with($this->equalTo(self::ENCODED_PASSWORD))
272  ->willReturn(false)
273  ;
274 
275  $factory_mock->expects($this->once())->method('getEncoderByName')->willReturn($encoder);
276 
277  $password_manager = new LocalUserPasswordManager([
278  'password_encoder' => 'mockencoder',
279  'encoder_factory' => $factory_mock,
280  'data_directory' => $this->getTestDirectoryUrl()
281  ]);
282 
283  $this->assertTrue($password_manager->verifyPassword($user_mock, self::PASSWORD));
284  }
285 
291  {
292  $user_mock = $this->getMockBuilder(ilObjUser::class)->disableOriginalConstructor()->getMock();
293  $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
294  $factory_mock = $this->getMockBuilder(LocalUserPasswordEncoderFactory::class)->disableOriginalConstructor()->getMock();
295 
296  $user_mock->expects($this->once())->method('getPasswordSalt')->willReturn('asuperrandomsalt');
297  $user_mock->expects($this->once())->method('getPasswordEncodingType')->willReturn('mockencoder');
298  $user_mock->expects($this->exactly(2))->method('getPasswd')->willReturn(self::ENCODED_PASSWORD);
299  $user_mock->expects($this->once())->method('resetPassword')->with(
300  $this->equalTo(self::PASSWORD),
301  $this->equalTo(self::PASSWORD)
302  );
303 
304  $encoder->expects($this->once())->method('getName')->willReturn('mockencoder');
305  $encoder->expects($this->once())->method('isPasswordValid')->with(
306  $this->equalTo(self::ENCODED_PASSWORD),
307  $this->equalTo(self::PASSWORD),
308  $this->isType('string')
309  )->willReturn(true);
310  $encoder->expects($this->once())->method('requiresReencoding')
311  ->with($this->equalTo(self::ENCODED_PASSWORD))
312  ->willReturn(true)
313  ;
314 
315  $factory_mock->expects($this->once())->method('getEncoderByName')->willReturn($encoder);
316 
317  $password_manager = new LocalUserPasswordManager([
318  'password_encoder' => 'mockencoder',
319  'encoder_factory' => $factory_mock,
320  'data_directory' => $this->getTestDirectoryUrl()
321  ]);
322 
323  $this->assertTrue($password_manager->verifyPassword($user_mock, self::PASSWORD));
324  }
325 
331  {
332  $user_mock = $this->getMockBuilder(ilObjUser::class)->disableOriginalConstructor()->getMock();
333  $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
334  $factory_mock = $this->getMockBuilder(LocalUserPasswordEncoderFactory::class)->disableOriginalConstructor()->getMock();
335 
336  $user_mock->expects($this->once())->method('getPasswordSalt')->willReturn('asuperrandomsalt');
337  $user_mock->expects($this->once())->method('getPasswordEncodingType')->willReturn('second_mockencoder');
338  $user_mock->expects($this->once())->method('getPasswd')->willReturn(self::ENCODED_PASSWORD);
339  $user_mock->expects($this->never())->method('resetPassword');
340 
341  $encoder->expects($this->once())->method('getName')->willReturn('second_mockencoder');
342  $encoder->expects($this->never())->method('requiresReencoding');
343  $encoder->expects($this->once())->method('isPasswordValid')
344  ->with(
345  $this->equalTo(self::ENCODED_PASSWORD),
346  $this->equalTo(self::PASSWORD),
347  $this->isType('string')
348  )->willReturn(false)
349  ;
350 
351  $factory_mock->expects($this->once())->method('getEncoderByName')->willReturn($encoder);
352 
353  $password_manager = new LocalUserPasswordManager([
354  'password_encoder' => 'mockencoder',
355  'encoder_factory' => $factory_mock,
356  'data_directory' => $this->getTestDirectoryUrl()
357  ]);
358 
359  $this->assertFalse($password_manager->verifyPassword($user_mock, self::PASSWORD));
360  }
361 
365  public function testFactoryCanBeCreated(): void
366  {
367  $factory = new LocalUserPasswordEncoderFactory([
368  'data_directory' => $this->getTestDirectoryUrl()
369  ]);
370  $this->assertInstanceOf(LocalUserPasswordEncoderFactory::class, $factory);
371  }
372 
379  {
380  $factory = new LocalUserPasswordEncoderFactory([
381  'default_password_encoder' => 'md5',
382  'data_directory' => $this->getTestDirectoryUrl()
383  ]);
384  $this->assertEquals('md5', $factory->getDefaultEncoder());
385 
386  $encoder = $this->createMock(ilPasswordEncoder::class);
387  $encoder->expects($this->atLeastOnce())->method('getName')->willReturn('mockencoder');
388  $encoder->expects($this->atLeastOnce())->method('isSupportedByRuntime')->willReturn(true);
389 
390  $second_mockencoder = $this->createMock(ilPasswordEncoder::class);
391  $second_mockencoder->expects($this->atLeastOnce())->method('getName')->willReturn('second_mockencoder');
392  $second_mockencoder->expects($this->atLeastOnce())->method('isSupportedByRuntime')->willReturn(true);
393 
394  $factory->setSupportedEncoders([$encoder, $second_mockencoder]);
395  $this->assertCount(2, $factory->getSupportedEncoders());
396  $this->assertCount(2, $factory->getSupportedEncoderNames());
397  $this->assertCount(
398  0,
399  array_diff(['mockencoder', 'second_mockencoder'], $factory->getSupportedEncoderNames())
400  );
401  $this->assertCount(
402  0,
403  array_diff($factory->getSupportedEncoderNames(), ['mockencoder', 'second_mockencoder'])
404  );
405  }
406 
412  {
413  $this->expectException(ilUserException::class);
414  $factory = new LocalUserPasswordEncoderFactory([
415  'data_directory' => $this->getTestDirectoryUrl()
416  ]);
417  $factory->setSupportedEncoders(['phpunit']);
418  }
419 
425  {
426  $this->expectException(ilUserException::class);
427  $factory = new LocalUserPasswordEncoderFactory([
428  'data_directory' => $this->getTestDirectoryUrl()
429  ]);
430  $factory->getEncoderByName('phpunit');
431  }
432 
438  {
439  $this->expectException(ilUserException::class);
440  $factory = new LocalUserPasswordEncoderFactory([
441  'default_password_encoder' => 'phpunit',
442  'data_directory' => $this->getTestDirectoryUrl()
443  ]);
444  $factory->getEncoderByName('phpunit');
445  }
446 
453  {
454  $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
455  $encoder->expects($this->atLeastOnce())->method('getName')->willReturn('mockencoder');
456  $encoder->expects($this->atLeastOnce())->method('isSupportedByRuntime')->willReturn(true);
457 
458  $factory = new LocalUserPasswordEncoderFactory([
459  'default_password_encoder' => $encoder->getName(),
460  'data_directory' => $this->getTestDirectoryUrl()
461  ]);
462  $factory->setSupportedEncoders([$encoder]);
463  $this->assertEquals($encoder, $factory->getEncoderByName('phpunit'));
464  }
465 
472  {
473  $encoder = $this->getMockBuilder(ilBasePasswordEncoder::class)->disableOriginalConstructor()->getMock();
474  $encoder->expects($this->atLeastOnce())->method('getName')->willReturn('mockencoder');
475  $encoder->expects($this->atLeastOnce())->method('isSupportedByRuntime')->willReturn(true);
476 
477  $factory = new LocalUserPasswordEncoderFactory([
478  'default_password_encoder' => $encoder->getName(),
479  'data_directory' => $this->getTestDirectoryUrl()
480  ]);
481  $factory->setSupportedEncoders([$encoder]);
482  $this->assertEquals($encoder, $factory->getEncoderByName('mockencoder'));
483  }
484 }
testExceptionIsRaisedIfPasswordManagerIsCreatedWithoutFactory()
testPasswordManagerMigratesPasswordOnVerificationWithVariantEncoders()
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
vfs vfsStreamDirectory $testDirectory
testFactoryReturnsTheDefaultEncoderIfAnUnsupportedEncoderIsRequestedAndASupportedDefaultEncoderWasSpecifiedInFallbackMode()
setTestDirectory(vfs\vfsStreamDirectory $testDirectory)
testFactoryRaisesAnExceptionIfAnUnsupportedEncoderIsRequestedAndNoDefaultEncoderWasSpecifiedInFallbackMode()
const PASSWD_CRYPTED
setTestDirectoryUrl(string $testDirectoryUrl)
testExceptionIsRaisedIfPasswordManagerIsCreatedWithoutValidFactory()
testPasswordManagerNeverMigratesPasswordOnFailedVerificationWithVariantEncoders()
testExceptionIsRaisedIfPasswordManagerIsCreatedWithoutEncoderInformation()
testFactoryRaisesAnExceptionIfAnUnsupportedEncoderWasInjected()
testFactoryRaisesAnExceptionIfAnUnsupportedEncoderIsRequestedAndTheDefaultEncoderDoesNotMatchOneOfTheSupportedEncodersInFallbackMode()