feat(security): add audit log listeners and tests for client secret and status events
This commit is contained in:
parent
0d2b0124ee
commit
dc83df4105
5 changed files with 277 additions and 0 deletions
33
src/Infra/Listeners/LogClientSecretRotated.php
Normal file
33
src/Infra/Listeners/LogClientSecretRotated.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Listeners;
|
||||
|
||||
use Application\Security\Events\ClientSecretGenerated;
|
||||
use Application\Security\Events\ClientSecretRegenerated;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
final readonly class LogClientSecretRotated
|
||||
{
|
||||
public function __construct(
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(ClientSecretGenerated | ClientSecretRegenerated $event): void
|
||||
{
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Client secret rotated for client "%s" (ID %s)',
|
||||
$event->clientIdentifier->value,
|
||||
$event->clientId->value
|
||||
),
|
||||
[
|
||||
'client_id' => $event->clientId->value,
|
||||
'client_identifier' => $event->clientIdentifier->value,
|
||||
'rotated_at' => $event->generatedAt->format('Y-m-d H:i:s'),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
33
src/Infra/Listeners/LogClientStatusChanged.php
Normal file
33
src/Infra/Listeners/LogClientStatusChanged.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Listeners;
|
||||
|
||||
use Application\Security\Events\ClientStatusToggled;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
final readonly class LogClientStatusChanged
|
||||
{
|
||||
public function __construct(
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(ClientStatusToggled $event): void
|
||||
{
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Client status changed for client "%s" (ID %s)',
|
||||
$event->clientIdentifier->value,
|
||||
$event->clientId->value
|
||||
),
|
||||
[
|
||||
'client_id' => $event->clientId->value,
|
||||
'client_identifier' => $event->clientIdentifier->value,
|
||||
'is_active' => $event->isActive,
|
||||
'toggled_at' => $event->toggledAt->format('Y-m-d H:i:s'),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,15 @@ declare(strict_types=1);
|
|||
|
||||
namespace Infra\Providers;
|
||||
|
||||
use Application\Security\Events\ClientSecretGenerated;
|
||||
use Application\Security\Events\ClientSecretRegenerated;
|
||||
use Application\Security\Events\ClientStatusToggled;
|
||||
use Domain\Events\BrandCreated;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Infra\Listeners\LogBrandCreated;
|
||||
use Infra\Listeners\LogClientSecretRotated;
|
||||
use Infra\Listeners\LogClientStatusChanged;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -17,5 +22,20 @@ class EventServiceProvider extends ServiceProvider
|
|||
BrandCreated::class,
|
||||
LogBrandCreated::class,
|
||||
);
|
||||
|
||||
Event::listen(
|
||||
ClientSecretGenerated::class,
|
||||
LogClientSecretRotated::class,
|
||||
);
|
||||
|
||||
Event::listen(
|
||||
ClientSecretRegenerated::class,
|
||||
LogClientSecretRotated::class,
|
||||
);
|
||||
|
||||
Event::listen(
|
||||
ClientStatusToggled::class,
|
||||
LogClientStatusChanged::class,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
94
tests/Unit/Infra/Listeners/LogClientSecretRotatedTest.php
Normal file
94
tests/Unit/Infra/Listeners/LogClientSecretRotatedTest.php
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Infra\Listeners;
|
||||
|
||||
use Application\Security\Events\ClientSecretGenerated;
|
||||
use Application\Security\Events\ClientSecretRegenerated;
|
||||
use Application\Security\Models\ValueObjects\ClientIdentifier;
|
||||
use Application\Security\Models\ValueObjects\Entities\ClientId;
|
||||
use Infra\Listeners\LogClientSecretRotated;
|
||||
use Mockery;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
covers(LogClientSecretRotated::class);
|
||||
|
||||
describe('LogClientSecretRotated Listener Unit Tests', function (): void {
|
||||
it('logs informative message when client secret is generated', function (): void {
|
||||
// GIVEN
|
||||
$clientId = new ClientId(Uuid::uuid7());
|
||||
$clientIdentifier = new ClientIdentifier('arcadia_id_11111111111111111111111111111111');
|
||||
$generatedAt = new \DateTimeImmutable('2026-08-21 10:00:00');
|
||||
|
||||
$event = new ClientSecretGenerated(
|
||||
clientId: $clientId,
|
||||
clientIdentifier: $clientIdentifier,
|
||||
generatedAt: $generatedAt,
|
||||
);
|
||||
|
||||
/** @var LoggerInterface&Mockery\MockInterface $loggerMock */
|
||||
$loggerMock = Mockery::mock(LoggerInterface::class);
|
||||
$loggerMock->shouldReceive('info')
|
||||
->once()
|
||||
->with(
|
||||
sprintf(
|
||||
'Client secret rotated for client "%s" (ID %s)',
|
||||
$clientIdentifier->value,
|
||||
$clientId->value
|
||||
),
|
||||
[
|
||||
'client_id' => $clientId->value,
|
||||
'client_identifier' => $clientIdentifier->value,
|
||||
'rotated_at' => '2026-08-21 10:00:00',
|
||||
]
|
||||
);
|
||||
|
||||
$listener = new LogClientSecretRotated($loggerMock);
|
||||
|
||||
// WHEN
|
||||
$listener->handle($event);
|
||||
|
||||
// THEN
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
|
||||
it('logs informative message when client secret is regenerated', function (): void {
|
||||
// GIVEN
|
||||
$clientId = new ClientId(Uuid::uuid7());
|
||||
$clientIdentifier = new ClientIdentifier('arcadia_id_22222222222222222222222222222222');
|
||||
$generatedAt = new \DateTimeImmutable('2026-08-21 11:00:00');
|
||||
|
||||
$event = new ClientSecretRegenerated(
|
||||
clientId: $clientId,
|
||||
clientIdentifier: $clientIdentifier,
|
||||
generatedAt: $generatedAt,
|
||||
);
|
||||
|
||||
/** @var LoggerInterface&Mockery\MockInterface $loggerMock */
|
||||
$loggerMock = Mockery::mock(LoggerInterface::class);
|
||||
$loggerMock->shouldReceive('info')
|
||||
->once()
|
||||
->with(
|
||||
sprintf(
|
||||
'Client secret rotated for client "%s" (ID %s)',
|
||||
$clientIdentifier->value,
|
||||
$clientId->value
|
||||
),
|
||||
[
|
||||
'client_id' => $clientId->value,
|
||||
'client_identifier' => $clientIdentifier->value,
|
||||
'rotated_at' => '2026-08-21 11:00:00',
|
||||
]
|
||||
);
|
||||
|
||||
$listener = new LogClientSecretRotated($loggerMock);
|
||||
|
||||
// WHEN
|
||||
$listener->handle($event);
|
||||
|
||||
// THEN
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
});
|
||||
97
tests/Unit/Infra/Listeners/LogClientStatusChangedTest.php
Normal file
97
tests/Unit/Infra/Listeners/LogClientStatusChangedTest.php
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Infra\Listeners;
|
||||
|
||||
use Application\Security\Events\ClientStatusToggled;
|
||||
use Application\Security\Models\ValueObjects\ClientIdentifier;
|
||||
use Application\Security\Models\ValueObjects\Entities\ClientId;
|
||||
use Infra\Listeners\LogClientStatusChanged;
|
||||
use Mockery;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
covers(LogClientStatusChanged::class);
|
||||
|
||||
describe('LogClientStatusChanged Listener Unit Tests', function (): void {
|
||||
it('logs informative message when client status is changed to active', function (): void {
|
||||
// GIVEN
|
||||
$clientId = new ClientId(Uuid::uuid7());
|
||||
$clientIdentifier = new ClientIdentifier('arcadia_id_33333333333333333333333333333333');
|
||||
$toggledAt = new \DateTimeImmutable('2026-08-21 12:00:00');
|
||||
|
||||
$event = new ClientStatusToggled(
|
||||
clientId: $clientId,
|
||||
clientIdentifier: $clientIdentifier,
|
||||
isActive: true,
|
||||
toggledAt: $toggledAt,
|
||||
);
|
||||
|
||||
/** @var LoggerInterface&Mockery\MockInterface $loggerMock */
|
||||
$loggerMock = Mockery::mock(LoggerInterface::class);
|
||||
$loggerMock->shouldReceive('info')
|
||||
->once()
|
||||
->with(
|
||||
sprintf(
|
||||
'Client status changed for client "%s" (ID %s)',
|
||||
$clientIdentifier->value,
|
||||
$clientId->value
|
||||
),
|
||||
[
|
||||
'client_id' => $clientId->value,
|
||||
'client_identifier' => $clientIdentifier->value,
|
||||
'is_active' => true,
|
||||
'toggled_at' => '2026-08-21 12:00:00',
|
||||
]
|
||||
);
|
||||
|
||||
$listener = new LogClientStatusChanged($loggerMock);
|
||||
|
||||
// WHEN
|
||||
$listener->handle($event);
|
||||
|
||||
// THEN
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
|
||||
it('logs informative message when client status is changed to inactive', function (): void {
|
||||
// GIVEN
|
||||
$clientId = new ClientId(Uuid::uuid7());
|
||||
$clientIdentifier = new ClientIdentifier('arcadia_id_44444444444444444444444444444444');
|
||||
$toggledAt = new \DateTimeImmutable('2026-08-21 12:30:00');
|
||||
|
||||
$event = new ClientStatusToggled(
|
||||
clientId: $clientId,
|
||||
clientIdentifier: $clientIdentifier,
|
||||
isActive: false,
|
||||
toggledAt: $toggledAt,
|
||||
);
|
||||
|
||||
/** @var LoggerInterface&Mockery\MockInterface $loggerMock */
|
||||
$loggerMock = Mockery::mock(LoggerInterface::class);
|
||||
$loggerMock->shouldReceive('info')
|
||||
->once()
|
||||
->with(
|
||||
sprintf(
|
||||
'Client status changed for client "%s" (ID %s)',
|
||||
$clientIdentifier->value,
|
||||
$clientId->value
|
||||
),
|
||||
[
|
||||
'client_id' => $clientId->value,
|
||||
'client_identifier' => $clientIdentifier->value,
|
||||
'is_active' => false,
|
||||
'toggled_at' => '2026-08-21 12:30:00',
|
||||
]
|
||||
);
|
||||
|
||||
$listener = new LogClientStatusChanged($loggerMock);
|
||||
|
||||
// WHEN
|
||||
$listener->handle($event);
|
||||
|
||||
// THEN
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue