localBaseSettings($hash))); } public function testValidBcryptAtMinimumCostIsAccepted(): void { $hash = password_hash('test', PASSWORD_BCRYPT, ['cost' => Config::BCRYPT_MIN_COST]); self::assertSame([], Config::collectErrors($this->localBaseSettings($hash))); } public function testBcryptBelowMinimumCostIsRejected(): void { $hash = password_hash('test', PASSWORD_BCRYPT, ['cost' => 4]); $errors = Config::collectErrors($this->localBaseSettings($hash)); self::assertNotEmpty($errors); $joined = implode("\n", $errors); self::assertStringContainsString('cost=4', $joined); self::assertStringContainsString('LOCAL_ADMIN_PASSWORD_HASH', $joined); } public function testArgon2iIsRejected(): void { if (!defined('PASSWORD_ARGON2I')) { self::markTestSkipped('argon2i not built into this PHP'); } $hash = password_hash('test', PASSWORD_ARGON2I); $errors = Config::collectErrors($this->localBaseSettings($hash)); self::assertNotEmpty($errors); self::assertStringContainsString('argon2i', implode("\n", $errors)); } public function testMd5CryptIsRejected(): void { // crypt() with $1$ prefix produces md5-crypt, which password_get_info // reports as 'unknown'. The literal string SEC_REVIEW called out. $hash = '$1$salt1234$KdyIvFMZJKR1qDJ5qE5W31'; $errors = Config::collectErrors($this->localBaseSettings($hash)); self::assertNotEmpty($errors); self::assertStringContainsString('"unknown"', implode("\n", $errors)); } public function testPlainStringIsRejected(): void { $errors = Config::collectErrors($this->localBaseSettings('not-a-hash-at-all')); self::assertNotEmpty($errors); } public function testEmptyHashIsStillRejectedWhenLocalEnabled(): void { $errors = Config::collectErrors($this->localBaseSettings('')); self::assertNotEmpty($errors); self::assertStringContainsString('empty', implode("\n", $errors)); } public function testHashIsNotValidatedWhenLocalAdminDisabled(): void { // Operators who only run OIDC don't need a strong dummy hash. $settings = [ 'ui_service_token' => 'irdb_svc_AAAA', 'api_base_url' => 'http://api:8081', 'oidc_enabled' => true, 'oidc_issuer' => 'https://issuer', 'oidc_client_id' => 'cid', 'oidc_client_secret' => 'csec', 'oidc_redirect_uri' => 'https://r/cb', 'local_admin_enabled' => false, 'local_admin_username' => '', 'local_admin_password_hash' => 'this-would-fail-if-checked', ]; self::assertSame([], Config::collectErrors($settings)); } /** * @return array */ private function localBaseSettings(string $hash): array { return [ 'ui_service_token' => 'irdb_svc_AAAA', 'api_base_url' => 'http://api:8081', 'oidc_enabled' => false, 'local_admin_enabled' => true, 'local_admin_username' => 'admin', 'local_admin_password_hash' => $hash, ]; } }