Skip to content
This repository was archived by the owner on Nov 9, 2020. It is now read-only.

Commit 7e44ddb

Browse files
committed
Implement optional IP and job class whitelists
Resolves #2 Resolves #3
1 parent a1cfcdd commit 7e44ddb

File tree

9 files changed

+138
-15
lines changed

9 files changed

+138
-15
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,21 @@ Environment: `REMOTE_QUEUE_CONNECTION`
8585

8686
Use this queue connection to process received jobs. If `null`, the default connection is used.
8787

88-
### remote-queue.accept-tokens
88+
### remote-queue.accept_tokens
8989

9090
Default: `[]`
9191
Environment: `REMOTE_QUEUE_ACCEPT_TOKENS`
9292

9393
Accept jobs only if they provide one of these tokens. Specify tokens as comma separated list if you use the environment variable. One way to generate tokens is this command: `head -c 32 /dev/urandom | base64`.
94+
95+
### remote-queue.accept_ips
96+
97+
Default: `[]`
98+
99+
Accept requests only from the IP addresses of this whitelist. Leave empty to accept jobs from all IPs.
100+
101+
### remote-queue.accept_jobs
102+
103+
Default: `[]`
104+
105+
Accept only jobs of this whitelist of class names. Leave empty to accept all job classes.

src/Http/Controllers/QueueController.php

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,39 @@ public function store(Request $request, $queue)
3434

3535
$job = @unserialize($request->input('job'));
3636

37-
if (is_object($job)) {
38-
if ($job instanceof __PHP_Incomplete_Class) {
39-
return response()->json(['errors' => ['job' => ['Unknown job class']]], 422);
40-
}
41-
} else {
42-
$job = $request->input('job');
43-
44-
if (!class_exists($job)) {
45-
return response()->json(['errors' => ['job' => ['Unknown job class']]], 422);
46-
}
37+
if ($this->isIncompleteClass($job)) {
38+
return response()->json(['errors' => ['job' => ['Unknown job class']]], 422);
39+
} elseif (!$this->isWhitelistedJob($job)) {
40+
return response()->json(['errors' => ['job' => ['Job class not whitelisted']]], 422);
4741
}
4842

4943
app('queue')->connection(config('remote-queue.connection'))
5044
->push($job, $request->input('data', ''), $queue);
5145
}
46+
47+
/**
48+
* Determine if the received job class exists.
49+
*
50+
* @param object $job Job class name
51+
*
52+
* @return boolean
53+
*/
54+
protected function isIncompleteClass($job)
55+
{
56+
return !is_object($job) || $job instanceof __PHP_Incomplete_Class;
57+
}
58+
59+
/**
60+
* Determine if the received job is whitelisted.
61+
*
62+
* @param object $job Job class name
63+
*
64+
* @return boolean
65+
*/
66+
protected function isWhitelistedJob($job)
67+
{
68+
$whitelist = config('remote-queue.accept_jobs');
69+
70+
return empty($whitelist) || in_array(get_class($job), $whitelist);
71+
}
5272
}

src/Http/Middleware/Authenticate.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class Authenticate
1717
public function handle($request, Closure $next, $guard = null)
1818
{
1919
$token = $request->bearerToken();
20-
if ($token && in_array($token, config('remote-queue.accept-tokens'))) {
20+
if ($token && in_array($token, config('remote-queue.accept_tokens'))) {
2121
return $next($request);
2222
}
2323

src/Http/Middleware/WhitelistIps.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Biigle\RemoteQueue\Http\Middleware;
4+
5+
use Closure;
6+
7+
class WhitelistIps
8+
{
9+
/**
10+
* Handle an incoming request.
11+
*
12+
* @param \Illuminate\Http\Request $request
13+
* @param \Closure $next
14+
* @param string|null $guard
15+
* @return mixed
16+
*/
17+
public function handle($request, Closure $next, $guard = null)
18+
{
19+
$whitelist = config('remote-queue.accept_ips');
20+
21+
$token = $request->bearerToken();
22+
if (empty($whitelist) || in_array($request->ip(), $whitelist)) {
23+
return $next($request);
24+
}
25+
26+
return response('Unauthorized.', 401);
27+
}
28+
}

src/RemoteQueueServiceProvider.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Illuminate\Support\ServiceProvider;
66
use Biigle\RemoteQueue\Http\Middleware\Authenticate;
7+
use Biigle\RemoteQueue\Http\Middleware\WhitelistIps;
78

89
class RemoteQueueServiceProvider extends ServiceProvider
910
{
@@ -22,7 +23,7 @@ public function boot()
2223
$this->app['router']->group([
2324
'prefix' => config('remote-queue.endpoint'),
2425
'namespace' => 'Biigle\RemoteQueue\Http\Controllers',
25-
'middleware' => Authenticate::class,
26+
'middleware' => [Authenticate::class, WhitelistIps::class],
2627
], function ($router) {
2728
$router->post('{queue}', 'QueueController@store');
2829
$router->get('{queue}/size', 'QueueController@show');

src/config/remote-queue.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@
2525
| Accept jobs only if they provide one of these tokens.
2626
*/
2727

28-
'accept-tokens' => array_filter(explode(',', env('REMOTE_QUEUE_ACCEPT_TOKENS'))),
28+
'accept_tokens' => array_filter(explode(',', env('REMOTE_QUEUE_ACCEPT_TOKENS'))),
29+
30+
/*
31+
| Accept jobs only from the IP addresses of this whitelist. Leave empty to accept
32+
| jobs from all IPs.
33+
*/
34+
'accept_ips' => [],
35+
36+
/*
37+
| Accept only jobs of this whitelist of class names. Leave empty to accept all job
38+
| classes.
39+
*/
40+
'accept_jobs' => [],
2941

3042
];

tests/Http/Controllers/QueueControllerTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,34 @@ public function testStore()
4141
->postJson('api/v1/remote-queue/myqueue', $payload)
4242
->assertStatus(200);
4343
}
44+
45+
public function testStoreJobWhitelist()
46+
{
47+
config(['remote-queue.accept_jobs' => [TestJob::class]]);
48+
$payload = ['job' => serialize(new TestJob2), 'data' => 'mydata'];
49+
50+
$mock = Mockery::mock();
51+
$mock->shouldReceive('push')->once()
52+
->with(Mockery::type(TestJob::class), 'mydata', 'myqueue');
53+
Queue::shouldReceive('connection')->once()->andReturn($mock);
54+
55+
$this->withoutMiddleware()
56+
->postJson('api/v1/remote-queue/myqueue', $payload)
57+
// Job class is not whitelisted.
58+
->assertStatus(422);
59+
60+
$payload = ['job' => serialize(new TestJob), 'data' => 'mydata'];
61+
62+
$this->withoutMiddleware()
63+
->postJson('api/v1/remote-queue/myqueue', $payload)
64+
->assertStatus(200);
65+
}
4466
}
4567

4668
class TestJob
4769
{
4870
}
71+
72+
class TestJob2
73+
{
74+
}

tests/Http/Middleware/AuthenticateTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class AuthenticateTest extends TestCase
88
{
99
public function testHandle()
1010
{
11-
config(['remote-queue.accept-tokens' => ['mytoken']]);
11+
config(['remote-queue.accept_tokens' => ['mytoken']]);
1212

1313
$this->get('api/v1/remote-queue/default/size')->assertStatus(401);
1414

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Biigle\RemoteQueue\Tests\Http\Middleware;
4+
5+
use Biigle\RemoteQueue\Tests\TestCase;
6+
7+
class WhitelistIpsTest extends TestCase
8+
{
9+
public function testHandle()
10+
{
11+
config(['remote-queue.accept_tokens' => ['mytoken']]);
12+
config(['remote-queue.accept_ips' => ['192.168.100.1']]);
13+
14+
$this->get('api/v1/remote-queue/default/size', [
15+
'Authorization' => 'Bearer mytoken',
16+
'REMOTE_ADDR' => '192.168.100.2',
17+
])->assertStatus(401);
18+
19+
$this->get('api/v1/remote-queue/default/size', [
20+
'Authorization' => 'Bearer mytoken',
21+
'REMOTE_ADDR' => '192.168.100.1',
22+
])->assertStatus(200);
23+
}
24+
}

0 commit comments

Comments
 (0)