| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- <?php
- declare(strict_types=1);
- namespace App\Tests\Auth;
- use App\Auth\LocalAdmin;
- use App\Tests\TestCase;
- /**
- * Lock-in tests for the hash-only LocalAdmin path (R01-N01). The fallback
- * accepts ONLY a password_hash()-produced string in LOCAL_ADMIN_PASSWORD_HASH;
- * the legacy plaintext LOCAL_ADMIN_PASSWORD env var is no longer consulted.
- */
- final class LocalAdminTest extends TestCase
- {
- /** @var array<string, string|false> */
- private array $envBackup = [];
- /** @var string[] */
- private array $envKeys = [
- 'LOCAL_ADMIN_EMAIL',
- 'LOCAL_ADMIN_PASSWORD_HASH',
- 'LOCAL_ADMIN_PASSWORD',
- 'LOCAL_ADMIN_NAME',
- ];
- protected function setUp(): void
- {
- parent::setUp();
- foreach ($this->envKeys as $k) {
- $this->envBackup[$k] = getenv($k);
- putenv($k); // unset
- }
- }
- protected function tearDown(): void
- {
- foreach ($this->envKeys as $k) {
- $prev = $this->envBackup[$k] ?? false;
- if ($prev === false) {
- putenv($k);
- } else {
- putenv("{$k}={$prev}");
- }
- }
- parent::tearDown();
- }
- public function testDisabledWhenEmailOrHashMissing(): void
- {
- self::assertFalse(LocalAdmin::isEnabled(), 'no env at all');
- putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
- self::assertFalse(LocalAdmin::isEnabled(), 'email only');
- putenv('LOCAL_ADMIN_EMAIL');
- putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));
- self::assertFalse(LocalAdmin::isEnabled(), 'hash only');
- }
- public function testEnabledWhenBothSet(): void
- {
- putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
- putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));
- self::assertTrue(LocalAdmin::isEnabled());
- }
- public function testVerifyAcceptsCorrectPasswordAgainstStoredHash(): void
- {
- $hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT);
- putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
- putenv('LOCAL_ADMIN_PASSWORD_HASH=' . $hash);
- self::assertTrue(LocalAdmin::verify('admin@example.com', 'correct horse battery staple'));
- }
- public function testVerifyRejectsWrongPassword(): void
- {
- putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
- putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));
- self::assertFalse(LocalAdmin::verify('admin@example.com', 'hunter1'));
- }
- public function testVerifyRejectsWrongEmail(): void
- {
- putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
- putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));
- self::assertFalse(LocalAdmin::verify('attacker@example.com', 'hunter2'));
- }
- public function testVerifyRejectsLegacyPlaintextEnvVar(): void
- {
- // Prior versions read LOCAL_ADMIN_PASSWORD verbatim. After R01-N01 the
- // hash-only contract means a plaintext env var alone must NOT enable
- // the fallback or authenticate anyone. (Belt-and-braces: if an
- // operator forgot to migrate, /auth/local should 404, not silently
- // accept the old value.)
- putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
- putenv('LOCAL_ADMIN_PASSWORD=hunter2');
- self::assertFalse(LocalAdmin::isEnabled());
- self::assertFalse(LocalAdmin::verify('admin@example.com', 'hunter2'));
- }
- public function testVerifyReturnsFalseWhenDisabledRegardlessOfInput(): void
- {
- self::assertFalse(LocalAdmin::verify('admin@example.com', 'anything'));
- }
- public function testEmailIsTrimmedOnComparison(): void
- {
- putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
- putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('pw', PASSWORD_DEFAULT));
- self::assertTrue(LocalAdmin::verify(' admin@example.com ', 'pw'));
- }
- public function testDisplayNameDefaultsAndOverride(): void
- {
- self::assertSame('Local Admin', LocalAdmin::displayName());
- putenv('LOCAL_ADMIN_NAME=Site Operator');
- self::assertSame('Site Operator', LocalAdmin::displayName());
- }
- }
|