Skip to content
This repository was archived by the owner on May 30, 2023. It is now read-only.

Commit f85c1d6

Browse files
author
Robert Kummer
committed
securing api with token validation integrated
1 parent e1c2b89 commit f85c1d6

File tree

5 files changed

+238
-0
lines changed

5 files changed

+238
-0
lines changed

app/Http/Kernel.php

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

33
namespace App\Http;
44

5+
use App\Http\Middleware\TokenValidation;
56
use Illuminate\Foundation\Http\Kernel as HttpKernel;
67

78
class Kernel extends HttpKernel
@@ -38,6 +39,7 @@ class Kernel extends HttpKernel
3839

3940
'api' => [
4041
'throttle:60,1',
42+
'token',
4143
'bindings',
4244
],
4345
];
@@ -57,5 +59,6 @@ class Kernel extends HttpKernel
5759
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
5860
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
5961
'feature' => \App\Http\Middleware\WebUiFeatureAvailability::class,
62+
'token' => TokenValidation::class,
6063
];
6164
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use Closure;
6+
7+
class TokenValidation
8+
{
9+
/**
10+
* The URIs that should be excluded from CSRF verification.
11+
*
12+
* @var array
13+
*/
14+
protected $except = [
15+
'/api/health'
16+
];
17+
18+
/**
19+
* Handle an incoming request.
20+
*
21+
* @param \Illuminate\Http\Request $request
22+
* @param \Closure $next
23+
* @return mixed
24+
*/
25+
public function handle($request, Closure $next)
26+
{
27+
if (!$this->inExceptArray($request)
28+
&& $this->hasSecretToken()
29+
&& $request->header($this->secretTokenName()) !== $this->secretToken()
30+
) {
31+
abort(401, 'Token missing');
32+
}
33+
34+
return $next($request);
35+
}
36+
37+
/**
38+
* do we have a secret token configured?
39+
*
40+
* @return bool
41+
*/
42+
private function hasSecretToken() : bool
43+
{
44+
return config('fileproxy.api.secret_token', null) !== null;
45+
}
46+
47+
/**
48+
* returns secret token.
49+
*
50+
* @return string
51+
*/
52+
private function secretToken() : string
53+
{
54+
return config('fileproxy.api.secret_token', '');
55+
}
56+
57+
/**
58+
* returns secret token name.
59+
*
60+
* @return string
61+
*/
62+
private function secretTokenName() : string
63+
{
64+
$tokenName = config('fileproxy.api.token_name', null);
65+
66+
return $tokenName ?? 'X-FILEPROXY-TOKEN';
67+
}
68+
69+
/**
70+
* Determine if the request has a URI that should pass through CSRF verification.
71+
*
72+
* @param \Illuminate\Http\Request $request
73+
* @return bool
74+
*/
75+
protected function inExceptArray($request)
76+
{
77+
foreach ($this->except as $except) {
78+
if ($except !== '/') {
79+
$except = trim($except, '/');
80+
}
81+
82+
if ($request->is($except)) {
83+
return true;
84+
}
85+
}
86+
87+
return false;
88+
}
89+
}

config/fileproxy.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,21 @@
3232
*/
3333
'accept_remote_creation' => env('FILEPROXY_WEB_ACCEPT_REMOTE_CREATION', false),
3434
],
35+
36+
/*
37+
* api configuration
38+
*/
39+
'api' => [
40+
/*
41+
* The secret token has to be set for security reason. If it is not null you have to
42+
* add a http header `X-FILEPROXY-TOKEN` to each api request.
43+
*/
44+
'secret_token' => env('FILEPROXY_API_SECRET_TOKEN', null),
45+
46+
/*
47+
* The secret token has to be set for security reason. If it is not null you have to
48+
* add a http header `X-FILEPROXY-TOKEN` to each api request.
49+
*/
50+
'token_name' => env('FILEPROXY_API_TOKEN_NAME', 'X-FILEPROXY-TOKEN'),
51+
],
3552
];

readme.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,29 @@ You can enable or disable the ability to create remote files via web frontend. T
9999

100100
**Environment variable**: `FILEPROXY_WEB_ACCEPT_REMOTE_CREATION`
101101

102+
### Api
103+
104+
All settings depending on the api endpoint.
105+
106+
#### secret_token
107+
108+
The secret token is for security reason. Api calls can be secured by this value. If it is set you have to add a header `X-FILEPROXY-TOKEN` to each api request.
109+
110+
When it is `null` no header validation will be executed.
111+
112+
**Default**: `null`
113+
114+
**Environment variable**: `FILEPROXY_API_SECRET_TOKEN`
115+
116+
#### token_name
117+
118+
The secret token will be checked by a header named `X-FILEPROXY-TOKEN` by default. You can override this concrete header name if you want.
119+
120+
**Default**: `X-FILEPROXY-TOKEN`
121+
122+
**Environment variable**: `FILEPROXY_API_TOKEN_NAME`
123+
124+
102125
## Test
103126

104127
All commands for running the business functions are unit tested. You can run `composer test` to run our test suite.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
namespace Tests\Feature\Api;
4+
5+
use Illuminate\Foundation\Testing\DatabaseMigrations;
6+
use Illuminate\Foundation\Testing\DatabaseTransactions;
7+
use Tests\TestCase;
8+
9+
class TokenValidationMiddlewareTest extends TestCase
10+
{
11+
use DatabaseMigrations, DatabaseTransactions;
12+
13+
/** @test */
14+
public function it_can_not_access_api_with_missing_header_when_secret_token_configured()
15+
{
16+
// ARRANGE
17+
config(['fileproxy.api.secret_token' => 'S3cr3T']);
18+
19+
// ACT
20+
$response = $this->getJson('/api/statistics');
21+
22+
// ASSERT
23+
$response->assertStatus(401)
24+
->assertExactJson([
25+
'errors' => [
26+
[
27+
'status' => 401,
28+
'code' => 0,
29+
'title' => 'Token missing'
30+
]
31+
],
32+
]);
33+
}
34+
35+
/** @test */
36+
public function it_can_access_api_with_correct_header_when_secret_token_configured()
37+
{
38+
// ARRANGE
39+
config(['fileproxy.api.secret_token' => 'S3cr3T']);
40+
41+
// ACT
42+
$response = $this->getJson('/api/statistics', ['X-FILEPROXY-TOKEN' => 'S3cr3T']);
43+
44+
// ASSERT
45+
$response->assertStatus(200)
46+
->assertHeader('Content-Type', 'application/vnd.api+json')
47+
->assertExactJson([
48+
'data' => [
49+
'type' => 'statistics',
50+
'id' => '',
51+
'attributes' => [
52+
'size' => 0,
53+
'files' => 0,
54+
'aliases' => 0,
55+
'hits' => 0,
56+
],
57+
'links' => [
58+
'self' => 'http://localhost/api/statistics/',
59+
],
60+
]
61+
]);
62+
}
63+
64+
/** @test */
65+
public function it_can_access_api_with_correct_header_when_secret_token_configured_and_token_name_header_modified()
66+
{
67+
// ARRANGE
68+
config(['fileproxy.api.secret_token' => 'S3cr3T', 'fileproxy.api.token_name' => 'X-TEST']);
69+
70+
// ACT
71+
$response = $this->getJson('/api/statistics', ['X-TEST' => 'S3cr3T']);
72+
73+
// ASSERT
74+
$response->assertStatus(200)
75+
->assertHeader('Content-Type', 'application/vnd.api+json')
76+
->assertExactJson([
77+
'data' => [
78+
'type' => 'statistics',
79+
'id' => '',
80+
'attributes' => [
81+
'size' => 0,
82+
'files' => 0,
83+
'aliases' => 0,
84+
'hits' => 0,
85+
],
86+
'links' => [
87+
'self' => 'http://localhost/api/statistics/',
88+
],
89+
]
90+
]);
91+
}
92+
93+
/** @test */
94+
public function it_can_access_health_check_with_secured_api_without_secret_token()
95+
{
96+
// ARRANGE
97+
config(['fileproxy.api.secret_token' => 'S3cr3T']);
98+
99+
// ACT
100+
$response = $this->get('/api/health');
101+
102+
// ASSERT
103+
$response->assertStatus(200);
104+
}
105+
106+
}

0 commit comments

Comments
 (0)