From 1338a0b45306dc7e6d9bd570ee9f9977f855026e Mon Sep 17 00:00:00 2001 From: Davey Shafik Date: Fri, 29 Jul 2022 07:19:00 -0700 Subject: [PATCH 1/2] Support encryption, avoid double __unserialize call Laravel uses the magic method `__unserialize()` in the `SerializesModels` trait, which does a bunch of work to rehydrate models attached to jobs. This gets called every time the job is unserialized. The addition of an `unserialize()` call in the constructor to get the `queue` property causes it to run twice because of the original call to `unserialize()` is in `\Illuminate\Queue\CallQueuedHandler->getCommand()`. `CallQueuedHandler->getCommand()` also implements support for encrypted command payloads. This change brings over the encryption support logic from `CallQueuedHandler->getCommand()` and passed `['allowed_classes' => false]` to the `unserialize()` call. This will make it _not_ hydrate to the original job object, but instead to an instance of `__PHP_Incomplete_Class` avoiding the unnecessary call to `__unserialize()` with this `unserialize()` call. We then cast to an `(array)` to access the `queue` property without issue. --- src/CloudTasksJob.php | 6 +++--- src/TaskHandler.php | 19 +++++++++++++++++-- tests/QueueTest.php | 9 +++++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/CloudTasksJob.php b/src/CloudTasksJob.php index fc83cbe..63f1e12 100644 --- a/src/CloudTasksJob.php +++ b/src/CloudTasksJob.php @@ -24,9 +24,9 @@ public function __construct(array $job, CloudTasksQueue $cloudTasksQueue) $this->job = $job; $this->container = Container::getInstance(); $this->cloudTasksQueue = $cloudTasksQueue; - /** @var \stdClass $command */ - $command = unserialize($job['data']['command']); - $this->queue = $command->queue; + + $command = TaskHandler::getCommandProperties($job['data']['command']); + $this->queue = $command['queue'] ?? config('queue.connections.' .config('queue.default') . '.queue'); } public function getJobId(): string diff --git a/src/TaskHandler.php b/src/TaskHandler.php index 9c1ba50..989679c 100644 --- a/src/TaskHandler.php +++ b/src/TaskHandler.php @@ -5,8 +5,10 @@ use Google\Cloud\Tasks\V2\CloudTasksClient; use Google\Cloud\Tasks\V2\RetryConfig; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Queue\Jobs\Job; use Illuminate\Queue\WorkerOptions; +use Illuminate\Support\Str; use stdClass; use UnexpectedValueException; use function Safe\json_decode; @@ -56,8 +58,8 @@ private function loadQueueConnectionConfiguration(array $task): void /** * @var stdClass $command */ - $command = unserialize($task['data']['command']); - $connection = $command->connection ?? config('queue.default'); + $command = self::getCommandProperties($task['data']['command']); + $connection = $command['connection'] ?? config('queue.default'); $this->config = array_merge( (array) config("queue.connections.{$connection}"), ['connection' => $connection] @@ -131,4 +133,17 @@ private function loadQueueRetryConfig(CloudTasksJob $job): void $this->retryConfig = CloudTasksApi::getRetryConfig($queueName); } + + public static function getCommandProperties(string $command): array + { + if (Str::startsWith($command, 'O:')) { + return (array) unserialize($command, ['allowed_classes' => false]); + } + + if (app()->bound(Encrypter::class)) { + return (array) unserialize(app(Encrypter::class)->decrypt($command), ['allowed_classes' => false]); + } + + return []; + } } diff --git a/tests/QueueTest.php b/tests/QueueTest.php index 0a89bda..cdd303f 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -7,6 +7,7 @@ use Google\Cloud\Tasks\V2\HttpMethod; use Google\Cloud\Tasks\V2\Task; use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksApi; +use Stackkit\LaravelGoogleCloudTasksQueue\TaskHandler; use Tests\Support\FailingJob; use Tests\Support\SimpleJob; @@ -137,19 +138,19 @@ public function it_posts_the_task_the_correct_queue() // Assert CloudTasksApi::assertTaskCreated(function (Task $task, string $queueName): bool { $decoded = json_decode($task->getHttpRequest()->getBody(), true); - $command = unserialize($decoded['data']['command']); + $command = TaskHandler::getCommandProperties($decoded['data']['command']); return $decoded['displayName'] === SimpleJob::class - && $command->queue === null + && $command['queue'] === null && $queueName === 'projects/my-test-project/locations/europe-west6/queues/barbequeue'; }); CloudTasksApi::assertTaskCreated(function (Task $task, string $queueName): bool { $decoded = json_decode($task->getHttpRequest()->getBody(), true); - $command = unserialize($decoded['data']['command']); + $command = TaskHandler::getCommandProperties($decoded['data']['command']); return $decoded['displayName'] === FailingJob::class - && $command->queue === 'my-special-queue' + && $command['queue'] === 'my-special-queue' && $queueName === 'projects/my-test-project/locations/europe-west6/queues/my-special-queue'; }); } From d5715db233ca10009f3598f599eeb09c8f2472c5 Mon Sep 17 00:00:00 2001 From: Marick van Tuil Date: Sat, 13 Aug 2022 14:44:40 +0200 Subject: [PATCH 2/2] Add test for encrypted jobs --- tests/Support/EncryptedJob.php | 20 ++++++++++++++++++++ tests/TaskHandlerTest.php | 25 +++++++++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 tests/Support/EncryptedJob.php diff --git a/tests/Support/EncryptedJob.php b/tests/Support/EncryptedJob.php new file mode 100644 index 0000000..8f8e4ff --- /dev/null +++ b/tests/Support/EncryptedJob.php @@ -0,0 +1,20 @@ +assertEquals('failed', $task->fresh()->status); } + + /** + * @test + */ + public function it_can_handle_encrypted_jobs() + { + // Arrange + OpenIdVerificator::fake(); + Log::swap(new LogFake()); + + // Act + $job = $this->dispatch(new EncryptedJob()); + $job->run(); + + // Assert + $this->assertEquals('O:26:"Tests\Support\EncryptedJob":0:{}', decrypt($job->payload['data']['command'])); + Log::assertLogged('EncryptedJob:success'); + } }