| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- <?php
- declare(strict_types=1);
- namespace App\Tests\Unit\Logging;
- use App\Infrastructure\Logging\SecretScrubbingProcessor;
- use Monolog\Formatter\JsonFormatter;
- use Monolog\Level;
- use Monolog\LogRecord;
- use PHPUnit\Framework\TestCase;
- final class SecretScrubbingProcessorTest extends TestCase
- {
- public function testBearerTokenInContextIsScrubbedAndPreservesKindPrefix(): void
- {
- $processor = new SecretScrubbingProcessor();
- $record = $this->record('outbound api call', [
- 'authorization' => 'Bearer irdb_svc_ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
- ]);
- $out = $processor($record);
- // Key-based redaction wins; the whole value is replaced with ***.
- self::assertSame('***', $out->context['authorization']);
- }
- public function testBareTokenInMessageIsScrubbed(): void
- {
- $processor = new SecretScrubbingProcessor();
- $record = $this->record(
- 'attempted with token irdb_adm_ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
- []
- );
- $out = $processor($record);
- self::assertStringContainsString('irdb_adm_***', $out->message);
- self::assertStringNotContainsString('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $out->message);
- }
- public function testFormattedOutputDoesNotLeakBearerToken(): void
- {
- // Round-trip the record through the processor and the JsonFormatter
- // to confirm the *rendered* log line is clean — what actually hits
- // stdout in production.
- $processor = new SecretScrubbingProcessor();
- $record = $this->record('api request', [
- 'headers' => ['Authorization' => 'Bearer irdb_rep_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
- ]);
- $out = $processor($record);
- $formatter = new JsonFormatter();
- $line = $formatter->format($out);
- self::assertStringNotContainsString('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', $line);
- self::assertStringContainsString('***', $line);
- }
- public function testPasswordHashKeyIsScrubbed(): void
- {
- $processor = new SecretScrubbingProcessor();
- $record = $this->record('config dump', [
- 'LOCAL_ADMIN_PASSWORD_HASH' => '$argon2id$v=19$m=65536,t=4,p=1$abcdef$ghijkl',
- 'OIDC_CLIENT_SECRET' => 'super-secret-value',
- 'MAXMIND_LICENSE_KEY' => 'license-12345',
- 'IPINFO_TOKEN' => 'token-67890',
- 'DB_MYSQL_PASSWORD' => 'rootpass',
- ]);
- $out = $processor($record);
- foreach (['LOCAL_ADMIN_PASSWORD_HASH', 'OIDC_CLIENT_SECRET', 'MAXMIND_LICENSE_KEY', 'IPINFO_TOKEN', 'DB_MYSQL_PASSWORD'] as $key) {
- self::assertSame('***', $out->context[$key], "key {$key} not scrubbed");
- }
- }
- public function testArgon2HashEmbeddedInMessageIsScrubbed(): void
- {
- $processor = new SecretScrubbingProcessor();
- $record = $this->record(
- 'verifying $argon2id$v=19$m=65536,t=4,p=1$abc$def for user',
- []
- );
- $out = $processor($record);
- self::assertStringNotContainsString('$argon2id$v=19', $out->message);
- self::assertStringContainsString('$argon2***', $out->message);
- }
- public function testNonSensitiveContentIsLeftAlone(): void
- {
- $processor = new SecretScrubbingProcessor();
- $record = $this->record('user search hit', [
- 'count' => 42,
- 'email' => 'someone@example.com',
- 'ip' => '203.0.113.42',
- ]);
- $out = $processor($record);
- self::assertSame(42, $out->context['count']);
- self::assertSame('someone@example.com', $out->context['email']);
- self::assertSame('203.0.113.42', $out->context['ip']);
- self::assertSame('user search hit', $out->message);
- }
- public function testNestedContextIsScrubbedRecursively(): void
- {
- $processor = new SecretScrubbingProcessor();
- $record = $this->record('nested', [
- 'request' => [
- 'method' => 'POST',
- 'authorization' => 'Bearer irdb_adm_ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
- 'body' => ['ok' => true],
- ],
- ]);
- $out = $processor($record);
- self::assertSame('***', $out->context['request']['authorization']);
- self::assertSame('POST', $out->context['request']['method']);
- self::assertTrue($out->context['request']['body']['ok']);
- }
- /**
- * @param array<string, mixed> $context
- */
- private function record(string $message, array $context): LogRecord
- {
- return new LogRecord(
- datetime: new \DateTimeImmutable(),
- channel: 'test',
- level: Level::Info,
- message: $message,
- context: $context,
- );
- }
- }
|