ILIAS  release_8 Revision v8.24
ilBcryptPasswordEncoderTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21use org\bovigo\vfs;
22
29{
31 private const VALID_COSTS = '08';
32
34 private const PASSWORD = 'password';
35
37 private const WRONG_PASSWORD = 'wrong_password';
38
40 private const CLIENT_SALT = 'homer!12345_/';
41
43 private const PASSWORD_SALT = 'salt';
44
45 protected vfs\vfsStreamDirectory $testDirectory;
46 protected string $testDirectoryUrl;
47
48 public function getTestDirectory(): vfs\vfsStreamDirectory
49 {
51 }
52
53 public function setTestDirectory(vfs\vfsStreamDirectory $testDirectory): void
54 {
55 $this->testDirectory = $testDirectory;
56 }
57
58 public function getTestDirectoryUrl(): string
59 {
61 }
62
63 public function setTestDirectoryUrl(string $testDirectoryUrl): void
64 {
65 $this->testDirectoryUrl = $testDirectoryUrl;
66 }
67
68 private function isVsfStreamInstalled(): bool
69 {
70 return class_exists('org\bovigo\vfs\vfsStreamWrapper');
71 }
72
73 private function skipIfvfsStreamNotSupported(): void
74 {
75 if (!$this->isVsfStreamInstalled()) {
76 $this->markTestSkipped('Skipped test, vfsStream (https://github.com/bovigo/vfsStream) required');
77 } else {
78 vfs\vfsStream::setup();
79 $this->setTestDirectory(vfs\vfsStream::newDirectory('tests')->at(vfs\vfsStreamWrapper::getRoot()));
80 $this->setTestDirectoryUrl(vfs\vfsStream::url('root/tests'));
81 }
82 }
83
87 public function costsProvider(): array
88 {
89 $data = [];
90 for ($i = 4; $i <= 31; ++$i) {
91 $data[sprintf('Costs: %s', $i)] = [(string) $i];
92 }
93
94 return $data;
95 }
96
98 {
99 return new ilBcryptPasswordEncoder([
100 'data_directory' => $this->getTestDirectoryUrl()
101 ]);
102 }
103
105 {
107
108 $security_flaw_ignoring_encoder = new ilBcryptPasswordEncoder([
109 'ignore_security_flaw' => true,
110 'data_directory' => $this->getTestDirectoryUrl()
111 ]);
112 $this->assertTrue($security_flaw_ignoring_encoder->isSecurityFlawIgnored());
113
114 $security_flaw_respecting_encoder = new ilBcryptPasswordEncoder([
115 'ignore_security_flaw' => false,
116 'data_directory' => $this->getTestDirectoryUrl()
117 ]);
118 $this->assertFalse($security_flaw_respecting_encoder->isSecurityFlawIgnored());
119
120 $encoder = new ilBcryptPasswordEncoder([
121 'cost' => self::VALID_COSTS,
122 'data_directory' => $this->getTestDirectoryUrl()
123 ]);
124 $this->assertInstanceOf(ilBcryptPasswordEncoder::class, $encoder);
125 $this->assertSame(self::VALID_COSTS, $encoder->getCosts());
126 $this->assertFalse($encoder->isSecurityFlawIgnored());
127 $encoder->setClientSalt(self::CLIENT_SALT);
128
129 return $encoder;
130 }
131
137 {
138 $expected = '04';
139
140 $encoder->setCosts($expected);
141 $this->assertSame($expected, $encoder->getCosts());
142 }
143
149 {
150 $this->expectException(ilPasswordException::class);
151 $encoder->setCosts('32');
152 }
153
159 {
160 $this->expectException(ilPasswordException::class);
161 $encoder->setCosts('3');
162 }
163
170 public function testCostsCanBeSetInRange(string $costs, ilBcryptPasswordEncoder $encoder): void
171 {
172 $encoder->setCosts($costs);
173 }
174
182 $encoder->setCosts(self::VALID_COSTS);
183 $encoded_password = $encoder->encodePassword(self::PASSWORD, self::PASSWORD_SALT);
184 $this->assertTrue($encoder->isPasswordValid($encoded_password, self::PASSWORD, self::PASSWORD_SALT));
185 $this->assertFalse($encoder->isPasswordValid($encoded_password, self::WRONG_PASSWORD, self::PASSWORD_SALT));
186
187 return $encoder;
188 }
189
196 ): void {
197 $this->expectException(ilPasswordException::class);
198 $encoder->setCosts(self::VALID_COSTS);
199 $encoder->encodePassword(str_repeat('a', 5000), self::PASSWORD_SALT);
200 }
201
208 ): void {
209 $encoder->setCosts(self::VALID_COSTS);
210 $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), self::PASSWORD_SALT));
211 }
212
216 public function testEncoderReliesOnSalts(ilBcryptPasswordEncoder $encoder): void
217 {
218 $this->assertTrue($encoder->requiresSalt());
219 }
220
225 {
226 $this->assertFalse($encoder->requiresReencoding('hello'));
227 }
228
232 public function testNameShouldBeBcrypt(ilBcryptPasswordEncoder $encoder): void
233 {
234 $this->assertSame('bcrypt', $encoder->getName());
235 }
236
238 {
239 $this->skipIfvfsStreamNotSupported();
240
241 $this->expectException(ilPasswordException::class);
242 $encoder = $this->getInstanceWithConfiguredDataDirectory();
243 $encoder->setClientSalt(null);
244 $encoder->setCosts(self::VALID_COSTS);
245 $encoder->encodePassword(self::PASSWORD, self::PASSWORD_SALT);
246 }
247
249 {
250 $this->skipIfvfsStreamNotSupported();
251
252 $this->expectException(ilPasswordException::class);
253 $encoder = $this->getInstanceWithConfiguredDataDirectory();
254 $encoder->setClientSalt(null);
255 $encoder->setCosts(self::VALID_COSTS);
256 $encoder->isPasswordValid('12121212', self::PASSWORD, self::PASSWORD_SALT);
257 }
258
260 {
261 $this->skipIfvfsStreamNotSupported();
262
263 $this->getTestDirectory()->chmod(0777);
264 vfs\vfsStream::newFile(ilBcryptPasswordEncoder::SALT_STORAGE_FILENAME)->withContent(self::CLIENT_SALT)->at($this->getTestDirectory());
265
266 $encoder = $this->getInstanceWithConfiguredDataDirectory();
267 $this->assertSame(self::CLIENT_SALT, $encoder->getClientSalt());
268 }
269
271 {
272 $this->skipIfvfsStreamNotSupported();
273
274 $this->getTestDirectory()->chmod(0777);
275
276 $encoder = $this->getInstanceWithConfiguredDataDirectory();
277 $this->assertNotNull($encoder->getClientSalt());
278 }
279
281 {
282 $this->skipIfvfsStreamNotSupported();
283
284 $this->expectException(ilPasswordException::class);
285 $this->getTestDirectory()->chmod(0000);
286
287 $this->getInstanceWithConfiguredDataDirectory();
288 }
289
291 {
292 $this->skipIfvfsStreamNotSupported();
293
294 $encoder = $this->getInstanceWithConfiguredDataDirectory();
295 $encoder->setBackwardCompatibility(true);
296 $this->assertTrue($encoder->isBackwardCompatibilityEnabled());
297 $encoder->setBackwardCompatibility(false);
298 $this->assertFalse($encoder->isBackwardCompatibilityEnabled());
299 }
300
301 public function testBackwardCompatibility(): void
302 {
303 $this->skipIfvfsStreamNotSupported();
304
305 $encoder = $this->getInstanceWithConfiguredDataDirectory();
306 $encoder->setClientSalt(self::CLIENT_SALT);
307 $encoder->setBackwardCompatibility(true);
308
309 $encoded_password = $encoder->encodePassword(self::PASSWORD, self::PASSWORD_SALT);
310 $this->assertTrue($encoder->isPasswordValid($encoded_password, self::PASSWORD, self::PASSWORD_SALT));
311 $this->assertSame('$2a$', substr($encoded_password, 0, 4));
312
313 $another_encoder = $this->getInstanceWithConfiguredDataDirectory();
314 $another_encoder->setClientSalt(self::CLIENT_SALT);
315
316 $another_encoder->setBackwardCompatibility(false);
317 $another_encoded_password = $another_encoder->encodePassword(self::PASSWORD, self::PASSWORD_SALT);
318 $this->assertSame('$2y$', substr($another_encoded_password, 0, 4));
319 $this->assertTrue($another_encoder->isPasswordValid($encoded_password, self::PASSWORD, self::PASSWORD_SALT));
320 }
321
323 {
324 $this->skipIfvfsStreamNotSupported();
325
326 $this->expectException(ilPasswordException::class);
327 $encoder = $this->getInstanceWithConfiguredDataDirectory();
328 $encoder->setClientSalt(self::CLIENT_SALT);
329 $encoder->setBackwardCompatibility(true);
330 $encoder->encodePassword(self::PASSWORD . chr(195), self::PASSWORD_SALT);
331 }
332
338 {
339 $this->skipIfvfsStreamNotSupported();
340
341 $encoder = $this->getInstanceWithConfiguredDataDirectory();
342 $encoder->setClientSalt(self::CLIENT_SALT);
343 $encoder->setBackwardCompatibility(true);
344 $encoder->setIsSecurityFlawIgnored(true);
345 $encoder->encodePassword(self::PASSWORD . chr(195), self::PASSWORD_SALT);
346 }
347}
testCostsCanBeSetInRange(string $costs, ilBcryptPasswordEncoder $encoder)
@doesNotPerformAssertions @depends testInstanceCanBeCreated @dataProvider costsProvider
testPasswordVerificationShouldFailIfTheRawPasswordExceedsTheSupportedLength(ilBcryptPasswordEncoder $encoder)
@depends testInstanceCanBeCreated
setTestDirectory(vfs\vfsStreamDirectory $testDirectory)
testPasswordShouldBeCorrectlyEncodedAndVerified(ilBcryptPasswordEncoder $encoder)
@depends testInstanceCanBeCreated
testCostsCanBeRetrievedWhenCostsAreSet(ilBcryptPasswordEncoder $encoder)
@depends testInstanceCanBeCreated
testCostsCannotBeSetBelowRange(ilBcryptPasswordEncoder $encoder)
@depends testInstanceCanBeCreated
testNoExceptionIfPasswordsContainA8BitCharacterAndBackwardCompatibilityIsEnabledWithIgnoredSecurityFlaw()
@doesNotPerformAssertions
setTestDirectoryUrl(string $testDirectoryUrl)
testEncoderReliesOnSalts(ilBcryptPasswordEncoder $encoder)
@depends testInstanceCanBeCreated
testExceptionIsRaisedIfThePasswordExceedsTheSupportedLengthOnEncoding(ilBcryptPasswordEncoder $encoder)
@depends testInstanceCanBeCreated
testEncoderDoesNotSupportReencoding(ilBcryptPasswordEncoder $encoder)
@depends testInstanceCanBeCreated
testCostsCannotBeSetAboveRange(ilBcryptPasswordEncoder $encoder)
@depends testInstanceCanBeCreated
testNameShouldBeBcrypt(ilBcryptPasswordEncoder $encoder)
@depends testInstanceCanBeCreated
encodePassword(string $raw, string $salt)
Encodes the raw password.
getName()
Returns a unique name/id of the concrete password encoder.
setBackwardCompatibility(bool $backward_compatibility)
Set the backward compatibility $2a$ instead of $2y$ for PHP 5.3.7+.
requiresSalt()
Returns whether the encoder requires a salt.
isPasswordValid(string $encoded, string $raw, string $salt)
Checks a raw password against an encoded password.
requiresReencoding(string $encoded)
Returns whether the encoded password needs to be re-encoded.
setIsSecurityFlawIgnored(bool $is_security_flaw_ignored)
Class for user password exception handling in ILIAS.
$i
Definition: metadata.php:41