Skip to content

Commit c1a0719

Browse files
committed
Add options to only allow whitelisted IP's
1 parent 9ae0608 commit c1a0719

File tree

4 files changed

+207
-5
lines changed

4 files changed

+207
-5
lines changed

config/stagefront.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,32 @@
7777
'database_login_field' => env('STAGEFRONT_DATABASE_LOGIN_FIELD', 'email'),
7878
'database_password_field' => env('STAGEFRONT_DATABASE_PASSWORD_FIELD', 'password'),
7979

80+
/**
81+
* Allow access from whitelisted IP's.
82+
* Enter a string of comma separated IP's or null to allow all IP's.
83+
* For example: '1.2.3.4,1.2.3.4'
84+
* If empty, all IP's are allowed.
85+
*
86+
* Default: ''
87+
*/
88+
'ip_whitelist' => env('STAGEFRONT_IP_WHITELIST', ''),
89+
90+
/**
91+
* Should only whitelisted IP's get access?
92+
* If set to false, non whitelisted IP's will be presented with the login form.
93+
* If set to true, non whitelisted IP's will get a 403 Forbidden error.
94+
*
95+
* Default: false
96+
*/
97+
'ip_whitelist_only' => env('STAGEFRONT_IP_WHITELIST_ONLY', false),
98+
99+
/**
100+
* Require users with whitelisted IP's to also login.
101+
*
102+
* Default: false
103+
*/
104+
'ip_whitelist_require_login' => env('STAGEFRONT_IP_WHITELIST_REQUIRE_LOGIN', false),
105+
80106
/**
81107
* The URL to use for the StageFront login route.
82108
* This URL will be used for a GET and POST request.

src/Middleware/RedirectIfStageFrontIsEnabled.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ public function handle($request, Closure $next)
3737
{
3838
$stageFrontUrl = Config::get('stagefront.url');
3939

40+
if ($this->shield->shouldDenyAccess()) {
41+
return abort(403);
42+
}
43+
4044
if ($this->shield->requiresLogin()) {
4145
return redirect($stageFrontUrl);
4246
}

src/Shield.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,39 @@
88

99
class Shield
1010
{
11+
/**
12+
* Check if StageFront should deny access to the user
13+
* with a 403 Forbidden HTTP status.
14+
*
15+
* @return bool
16+
*/
17+
public function shouldDenyAccess()
18+
{
19+
return $this->isActive()
20+
&& ($this->hasIpWhitelist() && ! $this->clientIpIsWhitelisted() && $this->allowWhitelistedIpsOnly());
21+
}
22+
1123
/**
1224
* Check if StageFront requires the user to log in.
1325
*
1426
* @return bool
1527
*/
1628
public function requiresLogin()
29+
{
30+
return $this->isActive()
31+
&& ( ! $this->hasIpWhitelist()
32+
|| ($this->clientIpIsWhitelisted() && $this->whitelistRequiresLogin())
33+
|| ( ! $this->clientIpIsWhitelisted() && ! $this->allowWhitelistedIpsOnly())
34+
);
35+
}
36+
37+
/**
38+
* Check if StageFront is active.
39+
* Once a user logs in, the shield is considered inactive.
40+
*
41+
* @return bool
42+
*/
43+
protected function isActive()
1744
{
1845
$enabled = Config::get('stagefront.enabled', false);
1946
$unlocked = Session::get('stagefront.unlocked', false);
@@ -41,4 +68,58 @@ protected function currentUrlIsIgnored()
4168

4269
return false;
4370
}
71+
72+
/**
73+
* Check if the client IP is whitelisted.
74+
*
75+
* @return bool
76+
*/
77+
protected function clientIpIsWhitelisted()
78+
{
79+
$clientIp = Request::ip();
80+
$whitelist = explode(',', $this->getIpWhitelist());
81+
$ips = array_map('trim', $whitelist);
82+
83+
return in_array($clientIp, $ips);
84+
}
85+
86+
/**
87+
* Check if a IP whitelist is configured.
88+
*
89+
* @return bool
90+
*/
91+
protected function hasIpWhitelist()
92+
{
93+
return ! empty(trim($this->getIpWhitelist()));
94+
}
95+
96+
/**
97+
* Get the IP whitelist from the config file.
98+
*
99+
* @return string
100+
*/
101+
protected function getIpWhitelist()
102+
{
103+
return Config::get('stagefront.ip_whitelist', '');
104+
}
105+
106+
/**
107+
* Get the option to grant access to whitelisted IP's only.
108+
*
109+
* @return bool
110+
*/
111+
protected function allowWhitelistedIpsOnly()
112+
{
113+
return Config::get('stagefront.ip_whitelist_only', false);
114+
}
115+
116+
/**
117+
* Get the option to require users with whitelisted IP's to login.
118+
*
119+
* @return bool
120+
*/
121+
public function whitelistRequiresLogin()
122+
{
123+
return Config::get('stagefront.ip_whitelist_require_login', false);
124+
}
44125
}

tests/StageFrontTest.php

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,77 @@ public function you_can_limit_which_database_users_have_access_using_an_array()
250250
])->assertRedirect($this->url)->assertSessionHasErrors('password');
251251
}
252252

253+
/** @test */
254+
public function it_allows_access_to_whitelisted_ips_only()
255+
{
256+
$this->url = Config::get('stagefront.url');
257+
$this->registerRoute('/page', 'Some Page');
258+
259+
$this->enableStageFront();
260+
$this->setIntendedUrl('/page');
261+
262+
Config::set('stagefront.ip_whitelist', ' 0.0.0.0 , 1.1.1.1 ');
263+
Config::set('stagefront.ip_whitelist_only', true);
264+
Config::set('stagefront.ip_whitelist_require_login', false);
265+
266+
$this->get('/page', ['REMOTE_ADDR' => '1.2.3.4'])
267+
->assertStatus(403);
268+
269+
$this->get('/page', ['REMOTE_ADDR' => '1.1.1.1'])
270+
->assertOk();
271+
}
272+
273+
/** @test */
274+
public function it_allows_access_to_whitelisted_ips_only_with_required_login()
275+
{
276+
$this->url = Config::get('stagefront.url');
277+
$this->registerRoute('/page', 'Some Page');
278+
279+
$this->enableStageFront();
280+
$this->setIntendedUrl('/page');
281+
282+
Config::set('stagefront.login', 'tester');
283+
Config::set('stagefront.password', 'p4ssw0rd');
284+
Config::set('stagefront.ip_whitelist', ' 0.0.0.0 , 1.1.1.1 ');
285+
Config::set('stagefront.ip_whitelist_only', true);
286+
Config::set('stagefront.ip_whitelist_require_login', true);
287+
288+
$this->get('/page', ['REMOTE_ADDR' => '1.2.3.4'])
289+
->assertStatus(403);
290+
291+
$this->get('/page', ['REMOTE_ADDR' => '1.1.1.1'])
292+
->assertRedirect($this->url);
293+
294+
$response = $this->submitForm([
295+
'login' => 'tester',
296+
'password' => 'p4ssw0rd',
297+
], ['REMOTE_ADDR' => '1.1.1.1']);
298+
299+
$response->assertRedirect('/page');
300+
}
301+
302+
/** @test */
303+
public function it_allows_instant_access_to_whitelisted_ips_and_password_access_to_other_ips()
304+
{
305+
$this->url = Config::get('stagefront.url');
306+
$this->registerRoute('/page', 'Some Page');
307+
308+
$this->enableStageFront();
309+
$this->setIntendedUrl('/page');
310+
311+
Config::set('stagefront.login', 'tester');
312+
Config::set('stagefront.password', 'p4ssw0rd');
313+
Config::set('stagefront.ip_whitelist', ' 0.0.0.0 , 1.1.1.1 ');
314+
Config::set('stagefront.ip_whitelist_only', false);
315+
Config::set('stagefront.ip_whitelist_require_login', false);
316+
317+
$this->get('/page', ['REMOTE_ADDR' => '1.2.3.4'])
318+
->assertRedirect($this->url);
319+
320+
$this->get('/page', ['REMOTE_ADDR' => '1.1.1.1'])
321+
->assertOk();
322+
}
323+
253324
/** @test */
254325
public function urls_can_be_ignored_so_access_is_not_denied_by_stagefront()
255326
{
@@ -267,6 +338,26 @@ public function urls_can_be_ignored_so_access_is_not_denied_by_stagefront()
267338
$this->get('/public/route')->assertStatus(200)->assertSee('Route');
268339
}
269340

341+
/** @test */
342+
public function ignored_urls_can_be_accessed_by_non_whitelisted_ips()
343+
{
344+
$this->url = Config::get('stagefront.url');
345+
$this->registerRoute('/page', 'Some Page');
346+
347+
$this->registerRoute('/public', 'Public');
348+
$this->registerRoute('/public/route', 'Route');
349+
350+
Config::set('stagefront.ignore_urls', ['/public/*']);
351+
Config::set('stagefront.ip_whitelist', '0.0.0.0');
352+
Config::set('stagefront.ip_whitelist_only', true);
353+
Config::set('stagefront.ip_whitelist_require_login', false);
354+
355+
$this->enableStageFront();
356+
357+
$this->get('/public', ['REMOTE_ADDR' => '1.2.3.4'])->assertStatus(403);
358+
$this->get('/public/route', ['REMOTE_ADDR' => '1.2.3.4'])->assertStatus(200)->assertSee('Route');
359+
}
360+
270361
/** @test */
271362
public function it_throttles_login_attempts()
272363
{
@@ -334,16 +425,16 @@ protected function setIntendedUrl($url)
334425
*
335426
* @return \Illuminate\Foundation\Testing\TestResponse
336427
*/
337-
protected function submitForm(array $credentials)
428+
protected function submitForm(array $credentials, $headers = [])
338429
{
339-
$response = $this->post($this->url, $credentials, [
430+
$headers += [
340431
// Since we're calling routes directly,
341432
// we need to fake the referring page
342433
// so that redirect()->back() will work.
343-
'HTTP_REFERER' => $this->url
344-
]);
434+
'HTTP_REFERER' => $this->url,
435+
];
345436

346-
return $response;
437+
return $this->post($this->url, $credentials, $headers);
347438
}
348439

349440
/**

0 commit comments

Comments
 (0)