From 0b451af152d9de4c2f54d0e7c150b48572fd1c17 Mon Sep 17 00:00:00 2001 From: Victor Malai Date: Mon, 27 Jan 2025 10:34:48 +0200 Subject: [PATCH 1/2] fix(postgresql): enforce public properties to fix postgreSQL serialization --- config/mailator.php | 10 +++ src/Models/MailatorSchedule.php | 21 +++++ .../Feature/AllowNonPublicPropertiesTest.php | 86 +++++++++++++++++++ tests/Fixtures/PrivatePropertyMailable.php | 24 ++++++ tests/Fixtures/ProtectedPropertyMailable.php | 24 ++++++ tests/Fixtures/PublicPropertyMailable.php | 24 ++++++ 6 files changed, 189 insertions(+) create mode 100644 tests/Feature/AllowNonPublicPropertiesTest.php create mode 100644 tests/Fixtures/PrivatePropertyMailable.php create mode 100644 tests/Fixtures/ProtectedPropertyMailable.php create mode 100644 tests/Fixtures/PublicPropertyMailable.php diff --git a/config/mailator.php b/config/mailator.php index dab7be5440a5..d9282568f949 100644 --- a/config/mailator.php +++ b/config/mailator.php @@ -63,4 +63,14 @@ Binarcode\LaravelMailator\Replacers\SampleReplacer::class, ], ], + + 'serialization' => [ + /* + > Controls constructor property accessibility in mailable objects for PostgreSQL compatibility. + > When set to false, allows private/protected properties. When true, enforces + > public properties only to prevent PostgreSQL serialization errors caused by + > null bytes (\x00) in non-public properties. + */ + 'enforce_public_properties' => false, + ] ]; diff --git a/src/Models/MailatorSchedule.php b/src/Models/MailatorSchedule.php index 6fa7ac509496..b3a840d66cb8 100644 --- a/src/Models/MailatorSchedule.php +++ b/src/Models/MailatorSchedule.php @@ -28,6 +28,8 @@ use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Opis\Closure\SerializableClosure; +use ReflectionClass; +use RuntimeException; use Throwable; use TypeError; @@ -106,6 +108,10 @@ public static function init(string $name): self public function mailable(Mailable $mailable): self { + if (config('mailator.serialization.enforce_public_properties') && $this->hasNonPublicConstructorProps($mailable)) { + throw new RuntimeException('Mailable contains non-public constructor properties which cannot be safely serialized'); + } + if ($mailable instanceof Constraintable) { collect($mailable->constraints()) ->filter(fn ($constraint) => $constraint instanceof SendScheduleConstraint) @@ -598,4 +604,19 @@ public function save(array $options = []) return parent::save($options); } + + private function hasNonPublicConstructorProps(Mailable $mailable): bool + { + $reflection = new ReflectionClass($mailable); + $constructor = $reflection->getConstructor(); + + if (! $constructor) { + return false; + } + + return collect($constructor->getParameters()) + ->filter(fn ($param) => $reflection->getProperty($param->getName())->isPrivate() + || $reflection->getProperty($param->getName())->isProtected()) + ->isNotEmpty(); + } } diff --git a/tests/Feature/AllowNonPublicPropertiesTest.php b/tests/Feature/AllowNonPublicPropertiesTest.php new file mode 100644 index 000000000000..30669f47ef02 --- /dev/null +++ b/tests/Feature/AllowNonPublicPropertiesTest.php @@ -0,0 +1,86 @@ +set('mailator.serialization.enforce_public_properties', false); + + MailatorSchedule::init('private') + ->mailable(new PrivatePropertyMailable('test')) + ->execute(); + + Mail::assertSent(PrivatePropertyMailable::class); + } + + public function test_can_not_send_email_with_private_property(): void + { + config()->set('mailator.serialization.enforce_public_properties', true); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Mailable contains non-public constructor properties which cannot be safely serialized'); + + MailatorSchedule::init('private') + ->mailable(new PrivatePropertyMailable('test')) + ->execute(); + } + + public function test_can_send_email_with_protected_property(): void + { + config()->set('mailator.serialization.enforce_public_properties', false); + + MailatorSchedule::init('protected') + ->mailable(new ProtectedPropertyMailable('test')) + ->execute(); + + Mail::assertSent(ProtectedPropertyMailable::class); + } + + public function test_can_not_send_email_with_protected_property(): void + { + config()->set('mailator.serialization.enforce_public_properties', true); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Mailable contains non-public constructor properties which cannot be safely serialized'); + + MailatorSchedule::init('protected') + ->mailable(new ProtectedPropertyMailable('test')) + ->execute(); + } + + public function test_can_send_email_with_public_property(): void + { + config()->set('mailator.serialization.enforce_public_properties', true); + + MailatorSchedule::init('protected') + ->mailable(new PublicPropertyMailable('test')) + ->execute(); + + Mail::assertSent(PublicPropertyMailable::class); + + config()->set('mailator.serialization.enforce_public_properties', false); + + MailatorSchedule::init('protected') + ->mailable(new PublicPropertyMailable('test')) + ->execute(); + } +} + diff --git a/tests/Fixtures/PrivatePropertyMailable.php b/tests/Fixtures/PrivatePropertyMailable.php new file mode 100644 index 000000000000..71e626c9c723 --- /dev/null +++ b/tests/Fixtures/PrivatePropertyMailable.php @@ -0,0 +1,24 @@ +view('laravel-mailator::mails.stub_invoice_reminder_view'); + } +} diff --git a/tests/Fixtures/ProtectedPropertyMailable.php b/tests/Fixtures/ProtectedPropertyMailable.php new file mode 100644 index 000000000000..18922eae6c49 --- /dev/null +++ b/tests/Fixtures/ProtectedPropertyMailable.php @@ -0,0 +1,24 @@ +view('laravel-mailator::mails.stub_invoice_reminder_view'); + } +} diff --git a/tests/Fixtures/PublicPropertyMailable.php b/tests/Fixtures/PublicPropertyMailable.php new file mode 100644 index 000000000000..024d260a72ce --- /dev/null +++ b/tests/Fixtures/PublicPropertyMailable.php @@ -0,0 +1,24 @@ +view('laravel-mailator::mails.stub_invoice_reminder_view'); + } +} From a958ddd9c1a06d0fcb1c5b4afeb6b68268b925e2 Mon Sep 17 00:00:00 2001 From: maloun96 Date: Mon, 27 Jan 2025 10:33:09 +0000 Subject: [PATCH 2/2] Fix styling --- tests/Feature/AllowNonPublicPropertiesTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Feature/AllowNonPublicPropertiesTest.php b/tests/Feature/AllowNonPublicPropertiesTest.php index 30669f47ef02..b964cf4e9c41 100644 --- a/tests/Feature/AllowNonPublicPropertiesTest.php +++ b/tests/Feature/AllowNonPublicPropertiesTest.php @@ -12,7 +12,6 @@ class AllowNonPublicPropertiesTest extends TestCase { - protected function setUp(): void { parent::setUp(); @@ -83,4 +82,3 @@ public function test_can_send_email_with_public_property(): void ->execute(); } } -