Skip to content

Commit acb6d01

Browse files
committed
Added task handler authorization
1 parent 825c084 commit acb6d01

File tree

4 files changed

+178
-9
lines changed

4 files changed

+178
-9
lines changed

src/CloudTasksQueue.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
use Google\Cloud\Tasks\V2\CloudTasksClient;
77
use Google\Cloud\Tasks\V2\HttpMethod;
88
use Google\Cloud\Tasks\V2\HttpRequest;
9+
use Google\Cloud\Tasks\V2\OidcToken;
910
use Google\Cloud\Tasks\V2\Task;
1011
use Google\Protobuf\Timestamp;
1112
use Illuminate\Contracts\Queue\Queue as QueueContract;
1213
use Illuminate\Queue\Queue as LaravelQueue;
1314
use Illuminate\Support\InteractsWithTime;
15+
use Illuminate\Support\Str;
1416

1517
class CloudTasksQueue extends LaravelQueue implements QueueContract
1618
{
@@ -55,14 +57,18 @@ protected function pushToCloudTasks($queue, $payload, $delay = 0, $attempts = 0)
5557
$queueName = $this->client->queueName(Config::project(), Config::location(), $queue);
5658
$availableAt = $this->availableAt($delay);
5759

58-
$httpRequest = app(HttpRequest::class);
60+
$httpRequest = $this->createHttpRequest();
5961
$httpRequest->setUrl(Config::handler());
6062
$httpRequest->setHttpMethod(HttpMethod::POST);
6163
$httpRequest->setBody($payload);
6264

63-
$task = app(Task::class);
65+
$task = $this->createTask();
66+
$randomString = Str::random();
67+
$task->setName($queueName . '/tasks/' . $randomString);
6468
$task->setHttpRequest($httpRequest);
6569

70+
$httpRequest->setHeaders(['X-Stackkit-Auth-Token' => encrypt($randomString)]);
71+
6672
if ($availableAt > time()) {
6773
$task->setScheduleTime(new Timestamp(['seconds' => $availableAt]));
6874
}
@@ -79,4 +85,20 @@ private function getQueue($queue = null)
7985
{
8086
return $queue ?: $this->default;
8187
}
88+
89+
/**
90+
* @return HttpRequest
91+
*/
92+
private function createHttpRequest()
93+
{
94+
return app(HttpRequest::class);
95+
}
96+
97+
/**
98+
* @return Task
99+
*/
100+
private function createTask()
101+
{
102+
return app(Task::class);
103+
}
82104
}

src/CloudTasksServiceProvider.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Stackkit\LaravelGoogleCloudTasksQueue;
44

5+
use Google\Cloud\Tasks\V2\CloudTasksClient;
56
use Illuminate\Queue\QueueManager;
67
use Illuminate\Routing\Router;
78
use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
@@ -10,10 +11,20 @@ class CloudTasksServiceProvider extends LaravelServiceProvider
1011
{
1112
public function boot(QueueManager $queue, Router $router)
1213
{
14+
$this->registerClient();
1315
$this->registerConnector($queue);
1416
$this->registerRoutes($router);
1517
}
1618

19+
private function registerClient()
20+
{
21+
$this->app->singleton(CloudTasksClient::class, function () {
22+
return new CloudTasksClient([
23+
'credentials' => Config::credentials(),
24+
]);
25+
});
26+
}
27+
1728
private function registerConnector(QueueManager $queue)
1829
{
1930
$queue->addConnector('cloudtasks', function () {

src/TaskHandler.php

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,76 @@
22

33
namespace Stackkit\LaravelGoogleCloudTasksQueue;
44

5+
use Ahc\Jwt\JWT;
6+
use Google\Cloud\Tasks\V2\CloudTasksClient;
7+
use Illuminate\Http\Request;
58
use Illuminate\Queue\Worker;
69
use Illuminate\Queue\WorkerOptions;
710
use Throwable;
811

912
class TaskHandler
1013
{
14+
private $client;
15+
private $request;
16+
17+
public function __construct(CloudTasksClient $client, Request $request)
18+
{
19+
$this->client = $client;
20+
$this->request = $request;
21+
}
22+
1123
/**
1224
* @param $task
1325
* @throws CloudTasksException
1426
*/
1527
public function handle($task = null)
1628
{
29+
$this->authorizeRequest();
30+
1731
$task = $task ?: $this->captureTask();
1832

1933
$this->handleTask($task);
2034
}
2135

36+
/**
37+
* @throws CloudTasksException
38+
*/
39+
private function authorizeRequest()
40+
{
41+
$this->checkForRequiredHeaders();
42+
43+
$taskName = $this->request->header('X-Cloudtasks-Taskname');
44+
$queueName = $this->request->header('X-Cloudtasks-Queuename');
45+
$authToken = $this->request->header('X-Stackkit-Auth-Token');
46+
47+
$fullQueueName = $this->client->queueName(Config::project(), Config::location(), $queueName);
48+
49+
try {
50+
$this->client->getTask($fullQueueName . '/tasks/' . $taskName);
51+
} catch (Throwable $e) {
52+
throw new CloudTasksException('Could not find task');
53+
}
54+
55+
if (decrypt($authToken) != $taskName) {
56+
throw new CloudTasksException('Auth token is not valid');
57+
}
58+
}
59+
60+
private function checkForRequiredHeaders()
61+
{
62+
$headers = [
63+
'X-Cloudtasks-Taskname',
64+
'X-Cloudtasks-Queuename',
65+
'X-Stackkit-Auth-Token',
66+
];
67+
68+
foreach ($headers as $header) {
69+
if (!$this->request->hasHeader($header)) {
70+
throw new CloudTasksException('Missing [' . $header . '] header');
71+
}
72+
}
73+
}
74+
2275
/**
2376
* @throws CloudTasksException
2477
*/
@@ -49,11 +102,7 @@ private function handleTask($task)
49102

50103
$worker = $this->getQueueWorker();
51104

52-
try {
53-
$worker->process('cloudtasks', $job, new WorkerOptions());
54-
} catch (Throwable $e) {
55-
throw new CloudTasksException('Failed handling task');
56-
}
105+
$worker->process('cloudtasks', $job, new WorkerOptions());
57106
}
58107

59108
/**

tests/TaskHandlerTest.php

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,107 @@
22

33
namespace Tests;
44

5+
use Google\Cloud\Tasks\V2\CloudTasksClient;
56
use Illuminate\Support\Facades\Mail;
7+
use Stackkit\LaravelGoogleCloudTasksQueue\CloudTasksException;
68
use Stackkit\LaravelGoogleCloudTasksQueue\TaskHandler;
79
use Tests\Support\TestMailable;
810

911
class TaskHandlerTest extends TestCase
1012
{
13+
/**
14+
* @var TaskHandler
15+
*/
16+
private $handler;
17+
18+
private $client;
19+
20+
protected function setUp(): void
21+
{
22+
parent::setUp();
23+
24+
$this->client = \Mockery::mock(CloudTasksClient::class)->makePartial();
25+
$this->handler = new TaskHandler(
26+
$this->client,
27+
request()
28+
);
29+
}
30+
31+
/** @test */
32+
public function it_needs_a_task_name_header()
33+
{
34+
$this->expectException(CloudTasksException::class);
35+
$this->expectExceptionMessage('Missing [X-Cloudtasks-Taskname] header');
36+
37+
$this->handler->handle();
38+
}
39+
40+
/** @test */
41+
public function it_needs_a_queue_name_header()
42+
{
43+
$this->expectException(CloudTasksException::class);
44+
$this->expectExceptionMessage('Missing [X-Cloudtasks-Queuename] header');
45+
46+
request()->headers->add(['X-Cloudtasks-Taskname' => 'test']);
47+
$this->handler->handle();
48+
}
49+
50+
/** @test */
51+
public function it_needs_a_stackkit_auth_token_header()
52+
{
53+
$this->expectException(CloudTasksException::class);
54+
$this->expectExceptionMessage('Missing [X-Stackkit-Auth-Token] header');
55+
56+
request()->headers->add(['X-Cloudtasks-Taskname' => 'test']);
57+
request()->headers->add(['X-Cloudtasks-Queuename' => 'test']);
58+
$this->handler->handle();
59+
}
60+
61+
/** @test */
62+
public function it_will_check_if_the_incoming_task_exists()
63+
{
64+
request()->headers->add(['X-Cloudtasks-Taskname' => 'test']);
65+
request()->headers->add(['X-Cloudtasks-Queuename' => 'test']);
66+
request()->headers->add(['X-Stackkit-Auth-Token' => encrypt('test')]);
67+
68+
Mail::fake();
69+
70+
$this->client
71+
->shouldReceive('getTask')
72+
->once()
73+
->with('projects/test-project/locations/europe-west6/queues/test/tasks/test')
74+
->andReturnNull();
75+
76+
$this->handler->handle(json_decode(file_get_contents(__DIR__ . '/Support/test-job-payload.json'), true));
77+
}
78+
79+
/** @test */
80+
public function it_will_check_the_auth_token()
81+
{
82+
request()->headers->add(['X-Cloudtasks-Taskname' => 'test']);
83+
request()->headers->add(['X-Cloudtasks-Queuename' => 'test']);
84+
request()->headers->add(['X-Stackkit-Auth-Token' => encrypt('does not match the task name')]);
85+
86+
$this->client->shouldReceive('getTask')->andReturnNull();
87+
88+
$this->expectException(CloudTasksException::class);
89+
$this->expectExceptionMessage('Auth token is not valid');
90+
91+
$this->handler->handle(json_decode(file_get_contents(__DIR__ . '/Support/test-job-payload.json'), true));
92+
}
93+
1194
/** @test */
1295
public function it_runs_the_incoming_job()
1396
{
1497
Mail::fake();
1598

16-
$handler = new TaskHandler();
99+
request()->headers->add(['X-Cloudtasks-Taskname' => 'test']);
100+
request()->headers->add(['X-Cloudtasks-Queuename' => 'test']);
101+
request()->headers->add(['X-Stackkit-Auth-Token' => encrypt('test')]);
102+
103+
$this->client->shouldReceive('getTask')->andReturnNull();
17104

18-
$handler->handle(json_decode(file_get_contents(__DIR__ . '/Support/test-job-payload.json'), true));
105+
$this->handler->handle(json_decode(file_get_contents(__DIR__ . '/Support/test-job-payload.json'), true));
19106

20107
Mail::assertSent(TestMailable::class);
21108
}

0 commit comments

Comments
 (0)