OidcFlowTest.php 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Integration\Auth;
  4. use App\Auth\OidcAuthenticator;
  5. use App\Auth\OidcClaims;
  6. use App\Auth\OidcException;
  7. use App\Tests\Integration\Support\AppTestCase;
  8. /**
  9. * OIDC flow tested with a stub `OidcAuthenticator`. Real Entra
  10. * authentication is verified manually; this test guards against
  11. * regressions in the controller's success/no-access/error branches.
  12. */
  13. final class OidcFlowTest extends AppTestCase
  14. {
  15. protected function setUp(): void
  16. {
  17. $this->bootApp(['oidc_enabled' => true]);
  18. }
  19. public function testCallbackSuccessSetsSessionAndRedirectsToMe(): void
  20. {
  21. $this->bindOidcAuthenticator(new class () implements OidcAuthenticator {
  22. public function authenticate(): OidcClaims
  23. {
  24. return new OidcClaims(
  25. subject: 'sub-1',
  26. email: 'alice@example.com',
  27. displayName: 'Alice',
  28. groups: ['group-admin'],
  29. );
  30. }
  31. });
  32. $this->enqueueApiResponse(200, [
  33. 'user_id' => 99, 'role' => 'admin', 'email' => 'alice@example.com',
  34. 'display_name' => 'Alice', 'is_local' => false,
  35. ]);
  36. $response = $this->request('GET', '/oidc/callback');
  37. self::assertSame(302, $response->getStatusCode());
  38. self::assertSame('/app/me', $response->getHeaderLine('Location'));
  39. self::assertSame(99, $_SESSION['_user']['user_id'] ?? null);
  40. self::assertSame('admin', $_SESSION['_user']['role']);
  41. }
  42. public function testNoneRoleRedirectsToNoAccess(): void
  43. {
  44. $this->bindOidcAuthenticator(new class () implements OidcAuthenticator {
  45. public function authenticate(): OidcClaims
  46. {
  47. return new OidcClaims('sub-x', 'x@x', 'X', []);
  48. }
  49. });
  50. $this->enqueueApiResponse(200, [
  51. 'user_id' => 0, 'role' => 'none', 'email' => 'x@x', 'display_name' => 'X', 'is_local' => false,
  52. ]);
  53. $response = $this->request('GET', '/oidc/callback');
  54. self::assertSame(302, $response->getStatusCode());
  55. self::assertSame('/no-access', $response->getHeaderLine('Location'));
  56. self::assertArrayNotHasKey('_user', $_SESSION);
  57. }
  58. public function testHandshakeFailureRedirectsToLogin(): void
  59. {
  60. $this->bindOidcAuthenticator(new class () implements OidcAuthenticator {
  61. public function authenticate(): OidcClaims
  62. {
  63. throw new OidcException('state mismatch');
  64. }
  65. });
  66. $response = $this->request('GET', '/oidc/callback');
  67. self::assertSame(302, $response->getStatusCode());
  68. self::assertSame('/login', $response->getHeaderLine('Location'));
  69. $flash = $_SESSION['_flash'] ?? [];
  70. self::assertNotEmpty($flash);
  71. self::assertSame('error', $flash[0]['type']);
  72. }
  73. public function testApiUnreachableDuringUpsertFlashesAndRedirects(): void
  74. {
  75. $this->bindOidcAuthenticator(new class () implements OidcAuthenticator {
  76. public function authenticate(): OidcClaims
  77. {
  78. return new OidcClaims('sub-1', null, 'Alice', []);
  79. }
  80. });
  81. $this->enqueueApiException(new \GuzzleHttp\Exception\ConnectException(
  82. 'down',
  83. new \GuzzleHttp\Psr7\Request('POST', '/'),
  84. ));
  85. $this->enqueueApiException(new \GuzzleHttp\Exception\ConnectException(
  86. 'down',
  87. new \GuzzleHttp\Psr7\Request('POST', '/'),
  88. ));
  89. $response = $this->request('GET', '/oidc/callback');
  90. self::assertSame(302, $response->getStatusCode());
  91. self::assertSame('/login', $response->getHeaderLine('Location'));
  92. }
  93. }