95 lines
3.4 KiB
PHP
95 lines
3.4 KiB
PHP
<?php
|
|
namespace Xxx\Tests\Unit;
|
|
|
|
use Xxx\Tests\Support\SiteData;
|
|
use Xxx\Tests\Support\TestCase;
|
|
|
|
// Password hashing and upgrades in session.lib.php
|
|
class PasswordTest extends TestCase {
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
open_db('open');
|
|
}
|
|
|
|
private function addEditor(string $hash): void {
|
|
SiteData::$tables['editors'][] = array('username' => 'ed', 'password' => $hash, 'email' => 'ed@example.org');
|
|
}
|
|
|
|
private function storedHash(): string {
|
|
return SiteData::$tables['editors'][0]['password'];
|
|
}
|
|
|
|
public function testAlgorithmIsArgon2idWhenAvailable(): void {
|
|
$this->assertSame(defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_BCRYPT, password_algorithm());
|
|
}
|
|
|
|
public function testEditorPasswordValid(): void {
|
|
$hash = password_hash('secret', password_algorithm());
|
|
$this->assertTrue(editor_password_valid('secret', $hash));
|
|
$this->assertFalse(editor_password_valid('wrong', $hash));
|
|
$this->assertFalse(editor_password_valid('', ''));
|
|
$this->assertFalse(editor_password_valid('secret', NULL));
|
|
$this->assertFalse(editor_password_valid('', NULL));
|
|
}
|
|
|
|
// A fast answer for an unknown username would show which usernames exist
|
|
public function testAnUnknownEditorTakesAsLongAsAWrongPassword(): void {
|
|
$hash = password_hash('secret', password_algorithm());
|
|
$time = function ($stored) {
|
|
$best = INF;
|
|
for ($i = 0; $i < 3; $i++) {
|
|
$start = hrtime(TRUE);
|
|
editor_password_valid('wrong', $stored);
|
|
$best = min($best, hrtime(TRUE) - $start);
|
|
}
|
|
return $best;
|
|
};
|
|
$this->assertGreaterThan($time($hash) / 2, $time(NULL));
|
|
}
|
|
|
|
public function testHashIsStoredAndChecked(): void {
|
|
$this->addEditor('old');
|
|
$hash = password_hash('secret', password_algorithm());
|
|
$this->assertTrue(store_password_hash('ed', $hash));
|
|
$this->assertSame($hash, $this->storedHash());
|
|
$this->assertTrue(password_hash_is_stored('ed', $hash));
|
|
$this->assertFalse(password_hash_is_stored('ed', 'other'));
|
|
$this->assertFalse(password_hash_is_stored('nobody', $hash));
|
|
}
|
|
|
|
public function testCutHashIsNotStored(): void {
|
|
$this->addEditor('old');
|
|
SiteData::$passwordLength = 60;
|
|
$hash = str_repeat('h', 97);
|
|
$this->assertFalse(store_password_hash('ed', $hash));
|
|
}
|
|
|
|
public function testOldMd5CryptHashIsUpgraded(): void {
|
|
$old = crypt('secret', '$1$abcdefgh$');
|
|
$this->assertTrue(password_verify('secret', $old), 'password_verify() still accepts the old hashes');
|
|
$this->addEditor($old);
|
|
rehash_password('ed', 'secret', $old);
|
|
$new = $this->storedHash();
|
|
$this->assertNotSame($old, $new);
|
|
$this->assertTrue(password_verify('secret', $new));
|
|
$this->assertFalse(password_needs_rehash($new, password_algorithm()));
|
|
}
|
|
|
|
public function testCurrentHashIsLeftAlone(): void {
|
|
$hash = password_hash('secret', password_algorithm());
|
|
$this->addEditor($hash);
|
|
rehash_password('ed', 'secret', $hash);
|
|
$this->assertSame($hash, $this->storedHash());
|
|
$this->assertSame('', $this->loggedErrors());
|
|
}
|
|
|
|
public function testShortColumnKeepsTheOldHash(): void {
|
|
$old = crypt('secret', '$1$abcdefgh$');
|
|
$this->addEditor($old);
|
|
SiteData::$passwordLength = 50;
|
|
rehash_password('ed', 'secret', $old);
|
|
$this->assertSame($old, $this->storedHash());
|
|
$this->assertStringContainsString('could not store a new password hash for ed; widen editors.password', $this->loggedErrors());
|
|
}
|
|
}
|