From 645f3f32960c49f0ef641aa10f8663c2ea3aadea Mon Sep 17 00:00:00 2001 From: Jose Andres Tejerina Date: Mon, 11 May 2026 23:34:31 -0300 Subject: [PATCH 1/2] fix: disable metric formatters --- app/Audit/AuditLogFormatterFactory.php | 18 +++++ config/audit_log.php | 4 +- .../AuditLogFormatterFactoryTest.php | 70 +++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/app/Audit/AuditLogFormatterFactory.php b/app/Audit/AuditLogFormatterFactory.php index 624949c24..99998287f 100644 --- a/app/Audit/AuditLogFormatterFactory.php +++ b/app/Audit/AuditLogFormatterFactory.php @@ -42,6 +42,10 @@ public function __construct() public function make(AuditContext $ctx, $subject, string $event_type): ?IAuditLogFormatter { + if ($this->isAuditDisabledForSubject($subject)) { + return null; + } + $formatter = null; switch ($event_type) { case IAuditStrategy::EVENT_COLLECTION_UPDATE: @@ -190,4 +194,18 @@ private function routeMatches(string $route, string $actual_route): bool { return strcmp($actual_route, $route) === 0; } + + private function isAuditDisabledForSubject(mixed $subject): bool + { + if (!is_object($subject)) { + return false; + } + + $entities = $this->config['entities'] ?? []; + $entity_config = $entities[get_class($subject)] ?? null; + + return is_array($entity_config) + && array_key_exists('enabled', $entity_config) + && $entity_config['enabled'] === false; + } } diff --git a/config/audit_log.php b/config/audit_log.php index 057bda8ee..d3cae2ce4 100644 --- a/config/audit_log.php +++ b/config/audit_log.php @@ -97,7 +97,7 @@ 'strategy' => \App\Audit\ConcreteFormatters\PresentationFormatters\PresentationActionTypeAuditLogFormatter::class, ], \models\summit\SummitEventAttendanceMetric::class => [ - 'enabled' => true, + 'enabled' => false, 'strategy' => \App\Audit\ConcreteFormatters\SummitEventAttendanceMetricAuditLogFormatter::class, ], \models\summit\SummitMediaUploadType::class => [ @@ -149,7 +149,7 @@ 'strategy' => \App\Audit\ConcreteFormatters\SummitVenueRoomAuditLogFormatter::class, ], \models\summit\SummitMetric::class => [ - 'enabled' => true, + 'enabled' => false, 'strategy' => \App\Audit\ConcreteFormatters\SummitMetricAuditLogFormatter::class, ], \models\summit\SummitSponsorship::class => [ diff --git a/tests/OpenTelemetry/AuditLogFormatterFactoryTest.php b/tests/OpenTelemetry/AuditLogFormatterFactoryTest.php index 2569bdfb4..dcc05062a 100644 --- a/tests/OpenTelemetry/AuditLogFormatterFactoryTest.php +++ b/tests/OpenTelemetry/AuditLogFormatterFactoryTest.php @@ -16,6 +16,8 @@ use App\Audit\AuditContext; use App\Audit\AuditLogFormatterFactory; +use App\Audit\AbstractAuditLogFormatter; +use App\Audit\Interfaces\IAuditStrategy; use PHPUnit\Framework\TestCase; /** @@ -154,4 +156,72 @@ public function testMatchesStrategyReturnsFalseWhenRouteDoesNotMatch(): void $result = $method->invoke($this->factory, $strategy, $ctx); $this->assertFalse($result, 'matchesStrategy should return false when routes do not match'); } + + public function testIsAuditDisabledForSubjectReturnsTrueWhenEntityIsDisabled(): void + { + $this->setFactoryConfig([ + 'entities' => [ + FakeAuditEntity::class => [ + 'enabled' => false, + 'strategy' => FakeAuditFormatter::class, + ], + ], + ]); + + $method = (new \ReflectionClass($this->factory))->getMethod('isAuditDisabledForSubject'); + $method->setAccessible(true); + + $this->assertTrue($method->invoke($this->factory, new FakeAuditEntity())); + } + + public function testIsAuditDisabledForSubjectReturnsFalseWhenEntityIsEnabled(): void + { + $this->setFactoryConfig([ + 'entities' => [ + FakeAuditEntity::class => [ + 'enabled' => true, + 'strategy' => FakeAuditFormatter::class, + ], + ], + ]); + + $method = (new \ReflectionClass($this->factory))->getMethod('isAuditDisabledForSubject'); + $method->setAccessible(true); + + $this->assertFalse($method->invoke($this->factory, new FakeAuditEntity())); + } + + public function testMakeReturnsNullWhenEntityIsDisabled(): void + { + $this->setFactoryConfig([ + 'entities' => [ + FakeAuditEntity::class => [ + 'enabled' => false, + 'strategy' => FakeAuditFormatter::class, + ], + ], + ]); + + $ctx = new AuditContext(); + $formatter = $this->factory->make($ctx, new FakeAuditEntity(), IAuditStrategy::EVENT_ENTITY_CREATION); + + $this->assertNull($formatter); + } + + private function setFactoryConfig(array $config): void + { + $prop = (new \ReflectionClass($this->factory))->getProperty('config'); + $prop->setAccessible(true); + $prop->setValue($this->factory, $config); + } +} + +class FakeAuditEntity {} + +class FakeAuditFormatter extends AbstractAuditLogFormatter +{ + public function format(mixed $subject, array $change_set): ?string + { + return 'ok'; + } } From c1774e98c04e43fa6fbdb86ada45bae376a3b252 Mon Sep 17 00:00:00 2001 From: Jose Andres Tejerina Date: Tue, 19 May 2026 14:44:08 -0300 Subject: [PATCH 2/2] fix: remove the entry when the formatter is disabled --- app/Audit/AuditLogFormatterFactory.php | 29 +++++++++++++-- app/Audit/AuditLogOtlpStrategy.php | 3 ++ app/Audit/IAuditLogFormatterFactory.php | 1 + .../AuditLogFormatterFactoryTest.php | 14 +++++++ .../AuditLogOtlpStrategyDisabledAuditTest.php | 37 +++++++++++++++++++ 5 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 tests/OpenTelemetry/AuditLogOtlpStrategyDisabledAuditTest.php diff --git a/app/Audit/AuditLogFormatterFactory.php b/app/Audit/AuditLogFormatterFactory.php index 99998287f..68181b92e 100644 --- a/app/Audit/AuditLogFormatterFactory.php +++ b/app/Audit/AuditLogFormatterFactory.php @@ -22,6 +22,7 @@ use App\Audit\ConcreteFormatters\DefaultEntityManyToManyCollectionDeleteAuditLogFormatter; use App\Audit\ConcreteFormatters\EntityUpdateAuditLogFormatter; use App\Audit\Interfaces\IAuditStrategy; +use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\PersistentCollection; use Illuminate\Support\Facades\Log; use Doctrine\ORM\Mapping\ClassMetadata; @@ -148,9 +149,17 @@ public function make(AuditContext $ctx, $subject, string $event_type): ?IAuditLo return $formatter; } + public function isAuditDisabled(mixed $subject): bool + { + return $this->isAuditDisabledForSubject($subject); + } + private function getFormatterByContext(object $subject, string $event_type, AuditContext $ctx): ?IAuditLogFormatter { - $class = get_class($subject); + $class = $this->getSubjectClass($subject); + if ($class === null) { + return null; + } $entity_config = $this->config['entities'][$class] ?? null; if (!$entity_config) { @@ -197,15 +206,29 @@ private function routeMatches(string $route, string $actual_route): bool private function isAuditDisabledForSubject(mixed $subject): bool { - if (!is_object($subject)) { + $class = $this->getSubjectClass($subject); + if ($class === null) { return false; } $entities = $this->config['entities'] ?? []; - $entity_config = $entities[get_class($subject)] ?? null; + $entity_config = $entities[$class] ?? null; return is_array($entity_config) && array_key_exists('enabled', $entity_config) && $entity_config['enabled'] === false; } + + private function getSubjectClass(mixed $subject): ?string + { + if (!is_object($subject)) { + return null; + } + + if (class_exists(ClassUtils::class)) { + return ClassUtils::getClass($subject); + } + + return get_class($subject); + } } diff --git a/app/Audit/AuditLogOtlpStrategy.php b/app/Audit/AuditLogOtlpStrategy.php index 1da61a7d8..171a9fa8e 100644 --- a/app/Audit/AuditLogOtlpStrategy.php +++ b/app/Audit/AuditLogOtlpStrategy.php @@ -57,6 +57,9 @@ public function audit($subject, array $change_set, string $event_type, AuditCon return; } Log::debug("AuditLogOtlpStrategy::audit current user", ["user_id" => $ctx->userId, "user_email" => $ctx->userEmail]); + if ($this->formatterFactory->isAuditDisabled($subject)) { + return; + } $formatter = $this->formatterFactory->make($ctx, $subject, $event_type); if(is_null($formatter)) { Log::warning("AuditLogOtlpStrategy::audit formatter not found"); diff --git a/app/Audit/IAuditLogFormatterFactory.php b/app/Audit/IAuditLogFormatterFactory.php index a41ea6ebe..b96e1bda3 100644 --- a/app/Audit/IAuditLogFormatterFactory.php +++ b/app/Audit/IAuditLogFormatterFactory.php @@ -3,4 +3,5 @@ interface IAuditLogFormatterFactory { public function make(AuditContext $ctx, $subject, string $event_type): ?IAuditLogFormatter; + public function isAuditDisabled(mixed $subject): bool; } diff --git a/tests/OpenTelemetry/AuditLogFormatterFactoryTest.php b/tests/OpenTelemetry/AuditLogFormatterFactoryTest.php index dcc05062a..f3003defc 100644 --- a/tests/OpenTelemetry/AuditLogFormatterFactoryTest.php +++ b/tests/OpenTelemetry/AuditLogFormatterFactoryTest.php @@ -208,6 +208,20 @@ public function testMakeReturnsNullWhenEntityIsDisabled(): void $this->assertNull($formatter); } + public function testIsAuditDisabledPublicMethodReturnsFalseForNonObjectSubject(): void + { + $this->setFactoryConfig([ + 'entities' => [ + FakeAuditEntity::class => [ + 'enabled' => false, + 'strategy' => FakeAuditFormatter::class, + ], + ], + ]); + + $this->assertFalse($this->factory->isAuditDisabled('not-an-object')); + } + private function setFactoryConfig(array $config): void { $prop = (new \ReflectionClass($this->factory))->getProperty('config'); diff --git a/tests/OpenTelemetry/AuditLogOtlpStrategyDisabledAuditTest.php b/tests/OpenTelemetry/AuditLogOtlpStrategyDisabledAuditTest.php new file mode 100644 index 000000000..b7eb087e0 --- /dev/null +++ b/tests/OpenTelemetry/AuditLogOtlpStrategyDisabledAuditTest.php @@ -0,0 +1,37 @@ +createMock(IAuditLogFormatterFactory::class); + $factory->expects($this->once()) + ->method('isAuditDisabled') + ->willReturn(true); + $factory->expects($this->never()) + ->method('make'); + + $strategy = new AuditLogOtlpStrategy($factory); + $enabledProperty = (new \ReflectionClass($strategy))->getProperty('enabled'); + $enabledProperty->setAccessible(true); + $enabledProperty->setValue($strategy, true); + + Queue::fake(); + + $strategy->audit( + new \stdClass(), + ['name' => ['old', 'new']], + AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE, + new AuditContext() + ); + + Queue::assertNothingPushed(); + } +}