BootstrapAdminTest.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Auth;
  4. use App\Auth\BootstrapAdmin;
  5. use App\Tests\TestCase;
  6. /**
  7. * Lock-in tests for R01-N03 — the OIDC bootstrap matcher. The matcher
  8. * is the only thing standing between an unconfigured deploy and the
  9. * historical "first user wins" land-grab.
  10. */
  11. final class BootstrapAdminTest extends TestCase
  12. {
  13. /** @var array<string, string|false> */
  14. private array $envBackup = [];
  15. /** @var string[] */
  16. private array $envKeys = [
  17. 'BOOTSTRAP_ADMIN_OID',
  18. 'BOOTSTRAP_ADMIN_EMAIL',
  19. ];
  20. protected function setUp(): void
  21. {
  22. parent::setUp();
  23. foreach ($this->envKeys as $k) {
  24. $this->envBackup[$k] = getenv($k);
  25. putenv($k);
  26. }
  27. }
  28. protected function tearDown(): void
  29. {
  30. foreach ($this->envKeys as $k) {
  31. $prev = $this->envBackup[$k] ?? false;
  32. if ($prev === false) {
  33. putenv($k);
  34. } else {
  35. putenv("{$k}={$prev}");
  36. }
  37. }
  38. parent::tearDown();
  39. }
  40. public function testNotConfiguredByDefault(): void
  41. {
  42. self::assertFalse(BootstrapAdmin::isConfigured());
  43. self::assertFalse(
  44. BootstrapAdmin::matches('any-oid', 'any@example.com'),
  45. 'unconfigured matcher must never promote anyone'
  46. );
  47. }
  48. public function testMatchesByOid(): void
  49. {
  50. putenv('BOOTSTRAP_ADMIN_OID=00000000-0000-0000-0000-000000000001');
  51. self::assertTrue(BootstrapAdmin::isConfigured());
  52. self::assertTrue(BootstrapAdmin::matches(
  53. '00000000-0000-0000-0000-000000000001',
  54. 'unrelated@example.com'
  55. ));
  56. self::assertFalse(BootstrapAdmin::matches(
  57. '00000000-0000-0000-0000-000000000002',
  58. 'unrelated@example.com'
  59. ));
  60. }
  61. public function testMatchesByEmail(): void
  62. {
  63. putenv('BOOTSTRAP_ADMIN_EMAIL=admin@example.com');
  64. self::assertTrue(BootstrapAdmin::isConfigured());
  65. self::assertTrue(BootstrapAdmin::matches(
  66. 'oid-does-not-matter',
  67. 'admin@example.com'
  68. ));
  69. self::assertFalse(BootstrapAdmin::matches(
  70. 'oid-does-not-matter',
  71. 'attacker@example.com'
  72. ));
  73. }
  74. public function testEitherChannelMatchesWhenBothConfigured(): void
  75. {
  76. putenv('BOOTSTRAP_ADMIN_OID=oid-1');
  77. putenv('BOOTSTRAP_ADMIN_EMAIL=admin@example.com');
  78. self::assertTrue(BootstrapAdmin::matches('oid-1', 'someone-else@x'));
  79. self::assertTrue(BootstrapAdmin::matches('oid-99', 'admin@example.com'));
  80. self::assertFalse(BootstrapAdmin::matches('oid-99', 'someone-else@x'));
  81. }
  82. public function testMatchIsCaseInsensitiveAndTrimmed(): void
  83. {
  84. putenv('BOOTSTRAP_ADMIN_EMAIL=Admin@Example.COM');
  85. self::assertTrue(BootstrapAdmin::matches('', ' admin@example.com '));
  86. putenv('BOOTSTRAP_ADMIN_EMAIL');
  87. putenv('BOOTSTRAP_ADMIN_OID=ABCDEF-1234');
  88. self::assertTrue(BootstrapAdmin::matches(' abcdef-1234 ', ''));
  89. }
  90. public function testBlankIncomingFieldsNeverMatch(): void
  91. {
  92. // Blank email in env shouldn't match a blank claim — config is
  93. // OID-only here, so an empty email coming back from Entra must
  94. // not opportunistically match the absent BOOTSTRAP_ADMIN_EMAIL.
  95. putenv('BOOTSTRAP_ADMIN_OID=oid-1');
  96. self::assertFalse(BootstrapAdmin::matches('', ''));
  97. self::assertFalse(BootstrapAdmin::matches('', 'bob@x'));
  98. }
  99. public function testWhitespaceOnlyEnvTreatedAsUnset(): void
  100. {
  101. putenv('BOOTSTRAP_ADMIN_OID= ');
  102. putenv('BOOTSTRAP_ADMIN_EMAIL= ');
  103. self::assertFalse(BootstrapAdmin::isConfigured());
  104. self::assertFalse(BootstrapAdmin::matches('anything', 'anything@x'));
  105. }
  106. public function testOidValueOverEmailIsPreferred(): void
  107. {
  108. // Both are set; if the user's oid matches we promote regardless of
  109. // a (presumably stale) BOOTSTRAP_ADMIN_EMAIL. Sanity check that
  110. // either-channel-wins still holds.
  111. putenv('BOOTSTRAP_ADMIN_OID=primary-oid');
  112. putenv('BOOTSTRAP_ADMIN_EMAIL=admin@example.com');
  113. self::assertTrue(BootstrapAdmin::matches('primary-oid', 'noone@x'));
  114. }
  115. }