1
0

LocalAdminTest.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Auth;
  4. use App\Auth\LocalAdmin;
  5. use App\Tests\TestCase;
  6. /**
  7. * Lock-in tests for the hash-only LocalAdmin path (R01-N01). The fallback
  8. * accepts ONLY a password_hash()-produced string in LOCAL_ADMIN_PASSWORD_HASH;
  9. * the legacy plaintext LOCAL_ADMIN_PASSWORD env var is no longer consulted.
  10. */
  11. final class LocalAdminTest extends TestCase
  12. {
  13. /** @var array<string, string|false> */
  14. private array $envBackup = [];
  15. /** @var string[] */
  16. private array $envKeys = [
  17. 'LOCAL_ADMIN_EMAIL',
  18. 'LOCAL_ADMIN_PASSWORD_HASH',
  19. 'LOCAL_ADMIN_PASSWORD',
  20. 'LOCAL_ADMIN_NAME',
  21. ];
  22. protected function setUp(): void
  23. {
  24. parent::setUp();
  25. foreach ($this->envKeys as $k) {
  26. $this->envBackup[$k] = getenv($k);
  27. putenv($k); // unset
  28. }
  29. }
  30. protected function tearDown(): void
  31. {
  32. foreach ($this->envKeys as $k) {
  33. $prev = $this->envBackup[$k] ?? false;
  34. if ($prev === false) {
  35. putenv($k);
  36. } else {
  37. putenv("{$k}={$prev}");
  38. }
  39. }
  40. parent::tearDown();
  41. }
  42. public function testDisabledWhenEmailOrHashMissing(): void
  43. {
  44. self::assertFalse(LocalAdmin::isEnabled(), 'no env at all');
  45. putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
  46. self::assertFalse(LocalAdmin::isEnabled(), 'email only');
  47. putenv('LOCAL_ADMIN_EMAIL');
  48. putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));
  49. self::assertFalse(LocalAdmin::isEnabled(), 'hash only');
  50. }
  51. public function testEnabledWhenBothSet(): void
  52. {
  53. putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
  54. putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));
  55. self::assertTrue(LocalAdmin::isEnabled());
  56. }
  57. public function testVerifyAcceptsCorrectPasswordAgainstStoredHash(): void
  58. {
  59. $hash = password_hash('correct horse battery staple', PASSWORD_DEFAULT);
  60. putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
  61. putenv('LOCAL_ADMIN_PASSWORD_HASH=' . $hash);
  62. self::assertTrue(LocalAdmin::verify('admin@example.com', 'correct horse battery staple'));
  63. }
  64. public function testVerifyRejectsWrongPassword(): void
  65. {
  66. putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
  67. putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));
  68. self::assertFalse(LocalAdmin::verify('admin@example.com', 'hunter1'));
  69. }
  70. public function testVerifyRejectsWrongEmail(): void
  71. {
  72. putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
  73. putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('hunter2', PASSWORD_DEFAULT));
  74. self::assertFalse(LocalAdmin::verify('attacker@example.com', 'hunter2'));
  75. }
  76. public function testVerifyRejectsLegacyPlaintextEnvVar(): void
  77. {
  78. // Prior versions read LOCAL_ADMIN_PASSWORD verbatim. After R01-N01 the
  79. // hash-only contract means a plaintext env var alone must NOT enable
  80. // the fallback or authenticate anyone. (Belt-and-braces: if an
  81. // operator forgot to migrate, /auth/local should 404, not silently
  82. // accept the old value.)
  83. putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
  84. putenv('LOCAL_ADMIN_PASSWORD=hunter2');
  85. self::assertFalse(LocalAdmin::isEnabled());
  86. self::assertFalse(LocalAdmin::verify('admin@example.com', 'hunter2'));
  87. }
  88. public function testVerifyReturnsFalseWhenDisabledRegardlessOfInput(): void
  89. {
  90. self::assertFalse(LocalAdmin::verify('admin@example.com', 'anything'));
  91. }
  92. public function testEmailIsTrimmedOnComparison(): void
  93. {
  94. putenv('LOCAL_ADMIN_EMAIL=admin@example.com');
  95. putenv('LOCAL_ADMIN_PASSWORD_HASH=' . password_hash('pw', PASSWORD_DEFAULT));
  96. self::assertTrue(LocalAdmin::verify(' admin@example.com ', 'pw'));
  97. }
  98. public function testDisplayNameDefaultsAndOverride(): void
  99. {
  100. self::assertSame('Local Admin', LocalAdmin::displayName());
  101. putenv('LOCAL_ADMIN_NAME=Site Operator');
  102. self::assertSame('Site Operator', LocalAdmin::displayName());
  103. }
  104. }