| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177 |
- <?php
- declare(strict_types=1);
- namespace App\Tests\Integration\Auth;
- use App\Domain\Auth\Role;
- use App\Domain\Auth\TokenKind;
- use App\Tests\Integration\Support\AppTestCase;
- /**
- * Covers the auth matrix from M03 task §7. Each test corresponds to one
- * row of the table:
- *
- * | Token kind | X-Acting-User-Id | User exists | Required | Expected |
- * | ------------ | ---------------- | ----------- | -------- | -------- |
- * | none | - | - | viewer | 401 |
- * | bad token | - | - | viewer | 401 |
- * | reporter | - | - | viewer | 401 |
- * | admin/viewer | - | - | viewer | 200 |
- * | admin/viewer | - | - | operator | 403 |
- * | admin/admin | - | - | admin | 200 |
- * | service | no | - | viewer | 400 |
- * | service | yes | no | viewer | 403 |
- * | service | yes (viewer) | yes | viewer | 200 |
- * | service | yes (viewer) | yes | operator | 403 |
- * | service | yes (admin) | yes | admin | 200 |
- *
- * /admin/me requires Viewer; /auth/users/upsert-local requires Admin.
- * Together they exercise both required-role rungs.
- */
- final class AuthMatrixTest extends AppTestCase
- {
- public function testNoTokenReturns401(): void
- {
- $response = $this->request('GET', '/api/v1/admin/me');
- self::assertSame(401, $response->getStatusCode());
- self::assertSame('unauthorized', $this->decode($response)['error']);
- }
- public function testBadlyFormattedTokenReturns401(): void
- {
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer not_a_real_token',
- ]);
- self::assertSame(401, $response->getStatusCode());
- }
- public function testWellFormedButUnknownTokenReturns401(): void
- {
- // Right format, never persisted → no DB hit on lookup.
- $raw = 'irdb_adm_ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer ' . $raw,
- ]);
- self::assertSame(401, $response->getStatusCode());
- }
- public function testReporterTokenOnAdminRouteReturns401(): void
- {
- $reporterId = $this->createReporter();
- $token = $this->createToken(TokenKind::Reporter, reporterId: $reporterId);
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer ' . $token,
- ]);
- self::assertSame(401, $response->getStatusCode());
- }
- public function testAdminTokenWithViewerRoleSatisfiesViewerRoute(): void
- {
- $token = $this->createToken(TokenKind::Admin, role: Role::Viewer);
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer ' . $token,
- ]);
- self::assertSame(200, $response->getStatusCode());
- $body = $this->decode($response);
- self::assertSame('viewer', $body['role']);
- self::assertSame('admin-token', $body['source']);
- self::assertNull($body['user_id']);
- }
- public function testAdminTokenWithAdminRoleSatisfiesAdminRoute(): void
- {
- $token = $this->createToken(TokenKind::Admin, role: Role::Admin);
- // /admin/me requires Viewer; an Admin role satisfies it.
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer ' . $token,
- ]);
- self::assertSame(200, $response->getStatusCode());
- self::assertSame('admin', $this->decode($response)['role']);
- }
- public function testServiceTokenWithoutImpersonationHeaderReturns400(): void
- {
- $token = $this->createToken(TokenKind::Service);
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer ' . $token,
- ]);
- self::assertSame(400, $response->getStatusCode());
- self::assertSame('missing X-Acting-User-Id', $this->decode($response)['error']);
- }
- public function testServiceTokenWithUnknownUserReturns403(): void
- {
- $token = $this->createToken(TokenKind::Service);
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer ' . $token,
- 'X-Acting-User-Id' => '99999',
- ]);
- self::assertSame(403, $response->getStatusCode());
- }
- public function testServiceTokenWithMalformedImpersonationHeaderReturns400(): void
- {
- $token = $this->createToken(TokenKind::Service);
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer ' . $token,
- 'X-Acting-User-Id' => 'not-an-int',
- ]);
- self::assertSame(400, $response->getStatusCode());
- }
- public function testServiceTokenImpersonatingViewerReachesViewerRoute(): void
- {
- $token = $this->createToken(TokenKind::Service);
- $userId = $this->createUser(Role::Viewer, isLocal: false, subject: 'sub-viewer');
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer ' . $token,
- 'X-Acting-User-Id' => (string) $userId,
- ]);
- self::assertSame(200, $response->getStatusCode());
- $body = $this->decode($response);
- self::assertSame($userId, $body['user_id']);
- self::assertSame('viewer', $body['role']);
- self::assertSame('oidc', $body['source']);
- self::assertFalse($body['is_local']);
- }
- public function testServiceTokenImpersonatingAdminReachesAdminRoute(): void
- {
- $token = $this->createToken(TokenKind::Service);
- $userId = $this->createUser(Role::Admin, isLocal: true);
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer ' . $token,
- 'X-Acting-User-Id' => (string) $userId,
- ]);
- self::assertSame(200, $response->getStatusCode());
- $body = $this->decode($response);
- self::assertSame('admin', $body['role']);
- self::assertTrue($body['is_local']);
- self::assertSame('local', $body['source']);
- }
- public function testActingUserHeaderIgnoredForAdminToken(): void
- {
- // Per SPEC §8: X-Acting-User-Id is *only* trusted with the service token.
- // For an admin token it must not be 400'd, just ignored.
- $token = $this->createToken(TokenKind::Admin, role: Role::Viewer);
- $response = $this->request('GET', '/api/v1/admin/me', [
- 'Authorization' => 'Bearer ' . $token,
- 'X-Acting-User-Id' => '99999',
- ]);
- self::assertSame(200, $response->getStatusCode());
- self::assertSame('admin-token', $this->decode($response)['source']);
- }
- }
|