RbacMiddlewareTest.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Unit\Auth;
  4. use App\Domain\Auth\AuthenticatedPrincipal;
  5. use App\Domain\Auth\Role;
  6. use App\Domain\Auth\TokenKind;
  7. use App\Infrastructure\Http\Middleware\RbacMiddleware;
  8. use App\Infrastructure\Http\Middleware\TokenAuthenticationMiddleware;
  9. use PHPUnit\Framework\TestCase;
  10. use Psr\Http\Message\ResponseInterface;
  11. use Psr\Http\Message\ServerRequestInterface;
  12. use Psr\Http\Server\RequestHandlerInterface;
  13. use Slim\Psr7\Factory\ResponseFactory;
  14. use Slim\Psr7\Factory\ServerRequestFactory;
  15. use Slim\Psr7\Response;
  16. /**
  17. * Direct unit coverage for the RBAC tier table from M03 §7. Integration
  18. * tests in AuthMatrixTest exercise the routes that exist; these target
  19. * the operator/admin rungs which have no live route in M03.
  20. */
  21. final class RbacMiddlewareTest extends TestCase
  22. {
  23. public function testAdminPrincipalSatisfiesAdminRoute(): void
  24. {
  25. self::assertSame(200, $this->dispatch(Role::Admin, $this->principal(Role::Admin)));
  26. }
  27. public function testOperatorPrincipalSatisfiesOperatorRoute(): void
  28. {
  29. self::assertSame(200, $this->dispatch(Role::Operator, $this->principal(Role::Operator)));
  30. }
  31. public function testViewerPrincipalForbiddenFromOperatorRoute(): void
  32. {
  33. self::assertSame(403, $this->dispatch(Role::Operator, $this->principal(Role::Viewer)));
  34. }
  35. public function testOperatorPrincipalForbiddenFromAdminRoute(): void
  36. {
  37. self::assertSame(403, $this->dispatch(Role::Admin, $this->principal(Role::Operator)));
  38. }
  39. public function testReporterPrincipalUnauthorizedOnAnyAdminRoute(): void
  40. {
  41. $principal = new AuthenticatedPrincipal(
  42. tokenKind: TokenKind::Reporter,
  43. userId: null,
  44. role: null,
  45. reporterId: 1,
  46. consumerId: null,
  47. tokenId: 1,
  48. );
  49. self::assertSame(401, $this->dispatch(Role::Viewer, $principal));
  50. }
  51. public function testServicePrincipalWithoutRoleForbidden(): void
  52. {
  53. // ImpersonationMiddleware would normally have populated role; if it
  54. // didn't (e.g. someone mounted an admin route without it), we expect
  55. // a defensive 403 rather than a misleading 200.
  56. $principal = new AuthenticatedPrincipal(
  57. tokenKind: TokenKind::Service,
  58. userId: null,
  59. role: null,
  60. reporterId: null,
  61. consumerId: null,
  62. tokenId: 1,
  63. );
  64. self::assertSame(403, $this->dispatch(Role::Viewer, $principal));
  65. }
  66. public function testMissingPrincipalReturns401(): void
  67. {
  68. $factory = new ResponseFactory();
  69. $middleware = RbacMiddleware::require($factory, Role::Viewer);
  70. $request = (new ServerRequestFactory())->createServerRequest('GET', '/x');
  71. $handler = new class () implements RequestHandlerInterface {
  72. public function handle(ServerRequestInterface $request): ResponseInterface
  73. {
  74. return new Response();
  75. }
  76. };
  77. $response = $middleware->process($request, $handler);
  78. self::assertSame(401, $response->getStatusCode());
  79. }
  80. private function principal(Role $role): AuthenticatedPrincipal
  81. {
  82. return new AuthenticatedPrincipal(
  83. tokenKind: TokenKind::Admin,
  84. userId: 42,
  85. role: $role,
  86. reporterId: null,
  87. consumerId: null,
  88. tokenId: 7,
  89. );
  90. }
  91. private function dispatch(Role $required, AuthenticatedPrincipal $principal): int
  92. {
  93. $factory = new ResponseFactory();
  94. $middleware = RbacMiddleware::require($factory, $required);
  95. $request = (new ServerRequestFactory())
  96. ->createServerRequest('GET', '/x')
  97. ->withAttribute(TokenAuthenticationMiddleware::ATTR_PRINCIPAL, $principal);
  98. $handler = new class () implements RequestHandlerInterface {
  99. public function handle(ServerRequestInterface $request): ResponseInterface
  100. {
  101. return new Response();
  102. }
  103. };
  104. return $middleware->process($request, $handler)->getStatusCode();
  105. }
  106. }