Skip to content

Instantly share code, notes, and snippets.

@scott1702
Last active May 20, 2020 05:35
Show Gist options
  • Save scott1702/9b666d1c18136bd6f654f617aefa7e64 to your computer and use it in GitHub Desktop.
Save scott1702/9b666d1c18136bd6f654f617aefa7e64 to your computer and use it in GitHub Desktop.
Whitelist Swiftype by User Agent, based on implementation from https://github.com/silverstripe/cwp-core
---
Name: basicauth
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Security\BasicAuthMiddleware:
class: 'App\Security\CustomBasicAuthMiddleware'
properties:
# Inject customisable IP whitelist and User agent
WhitelistedIps: '127.0.0.1,127.0.0.2'
WhitelistedUserAgents: "`SWIFTYPE_USER_AGENT`"
---
Name: basicauth_uat
Only:
environment: test
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Security\BasicAuthMiddleware:
properties:
# Enforce basic authentication in UAT environments for all routes except for the "change password" form
URLPatterns:
'#^Security/lostpassword#i': false
'#^Security/changepassword#i': false
'#.*#': true
<?php
namespace App\Security;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Security\BasicAuthMiddleware;
class CustomBasicAuthMiddleware extends BasicAuthMiddleware
{
/**
* Whitelisted IP addresses will not be given a basic authentication prompt when other basic authentication
* rules via {@link BasicAuthMiddleware} are enabled.
*
* Please note that this will not have any effect if using BasicAuth.entire_site_protected, which will
* always enabled basic authentication for the entire site.
*
* @var array
*/
protected $whitelistedIps = [];
protected $whitelistedUserAgents = [];
/**
* @return array
*/
public function getWhitelistedIps()
{
return $this->whitelistedIps;
}
/**
* @return array
*/
public function getWhitelistedUserAgents()
{
return $this->whitelistedUserAgents;
}
/**
* @param string|string[] $whitelistedIps An array of IP addresses, a comma delimited string, or an array of IPs
* or comma delimited IP list strings
* @return $this
*/
public function setWhitelistedIps($whitelistedIps)
{
// Allow string or array input
$ipLists = is_array($whitelistedIps) ? $whitelistedIps : [$whitelistedIps];
$whitelistedIps = [];
// Break each string in the array by commas to support nested IP lists
foreach ($ipLists as $ipList) {
if (!$ipList) {
continue;
}
$ips = array_map('trim', explode(',', $ipList));
$whitelistedIps = array_merge($whitelistedIps, $ips);
}
// Return unique values with keys reset
$this->whitelistedIps = array_values(array_unique($whitelistedIps));
return $this;
}
/**
* @param string|string[] $whitelistedUserAgents An array of user agents, a pipe (|) delimited string, or an array of IPs
* or comma delimited IP list strings
* @return $this
*/
public function setWhitelistedUserAgents($whitelistedUserAgents)
{
// Allow string or array input
$userAgentLists = is_array($whitelistedUserAgents) ? $whitelistedUserAgents : [$whitelistedUserAgents];
$whitelistedUserAgents = [];
// Break each string in the array by commas to support nested IP lists
foreach ($userAgentLists as $userAgentList) {
if (!$userAgentList) {
continue;
}
$userAgents = array_map('trim', explode('|', $userAgentList));
$whitelistedUserAgents = array_merge($whitelistedUserAgents, $userAgents);
}
// Return unique values with keys reset
$this->whitelistedUserAgents = array_values(array_unique($whitelistedUserAgents));
return $this;
}
/**
* Check for any whitelisted IP addresses. If one matches the current user's IP then return false early,
* otherwise allow the default {@link BasicAuthMiddleware} to continue its logic.
*
* {@inheritDoc}
*/
protected function checkMatchingURL(HTTPRequest $request)
{
if ($this->ipMatchesWhitelist() || $this->userAgentMatchesWhitelist()) {
return false;
}
return parent::checkMatchingURL($request);
}
/**
* Check whether the current user's IP address is in the IP whitelist
*
* @return bool
*/
protected function ipMatchesWhitelist()
{
$whitelist = $this->getWhitelistedIps();
// Continue if no whitelist is defined
if (empty($whitelist)) {
return false;
}
$userIp = $_SERVER['REMOTE_ADDR'];
if (in_array($userIp, $whitelist)) {
return true;
}
return false;
}
/**
* Check whether the current user's user agent is in the whitelist
*
* @return bool
*/
protected function userAgentMatchesWhitelist()
{
$whitelist = $this->getWhitelistedUserAgents();
// Continue if no whitelist is defined
if (empty($whitelist)) {
return false;
}
$userAgent = $_SERVER['HTTP_USER_AGENT'];
if (in_array($userAgent, $whitelist)) {
return true;
}
return false;
}
}
<?php
namespace App\Test;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Security\BasicAuth;
use SilverStripe\Security\BasicAuthMiddleware;
use App\Security\CustomBasicAuthMiddleware;
class CustomBasicAuthMiddlewareTest extends SapphireTest
{
/**
* @var CustomBasicAuthMiddleware
*/
protected $middleware;
/**
* @var array
*/
protected $originalServersVars = [];
protected function setUp()
{
parent::setUp();
$this->middleware = Injector::inst()->get(BasicAuthMiddleware::class);
$this->originalServersVars = $_SERVER;
Config::modify()->set(BasicAuth::class, 'ignore_cli', false);
}
protected function tearDown()
{
$_SERVER = $this->originalServersVars;
parent::tearDown();
}
public function testSetWhitelistedIpsAcceptsStrings()
{
$this->middleware->setWhitelistedIps('127.0.0.1,127.0.0.2');
$this->assertSame([
'127.0.0.1',
'127.0.0.2',
], $this->middleware->getWhitelistedIps(), 'Accepts comma delimited strings');
}
public function testSetWhitelistedIpsAcceptsArraysOfStrings()
{
$this->middleware->setWhitelistedIps(['127.0.0.1']);
$this->assertSame(['127.0.0.1'], $this->middleware->getWhitelistedIps(), 'Accepts array values');
}
public function testSetWhitelistedIpsSupportedNestedStringListsInsideArrays()
{
$this->middleware->setWhitelistedIps([
'127.0.0.1,127.0.0.2',
' 137.0.0.1 , 127.0.0.2',
'127.0.0.3',
'127.0.0.3', // check results are unique
'127.0.0.4',
]);
$this->assertSame([
'127.0.0.1',
'127.0.0.2',
'137.0.0.1',
'127.0.0.3',
'127.0.0.4',
], $this->middleware->getWhitelistedIps(), 'Accepts IP list strings inside arrays');
}
/**
* @param string $currentIp
* @param int $expected
* @dataProvider whitelistingProvider
*/
public function testIpWhitelisting($currentIp, $expected)
{
// Enable basic auth everywhere
$this->middleware->setURLPatterns(['#.*#' => true]);
// Set a whitelisted IP address
$_SERVER['REMOTE_ADDR'] = $currentIp;
$this->middleware->setWhitelistedIps(['127.0.0.1']);
$response = $this->mockRequest();
$this->assertEquals($expected, $response->getStatusCode());
}
/**
* @return array[]
*/
public function whitelistingProvider()
{
return [
'IP not in whitelist' => ['123.456.789.012', 401],
'IP in whitelist' => ['127.0.0.1', 200],
];
}
/**
* Perform a mock middleware request. Will return 200 if everything is OK.
*
* @param string $url
* @return HTTPResponse
*/
protected function mockRequest($url = '/foo')
{
$request = new HTTPRequest('GET', $url);
return $this->middleware->process($request, function () {
return new HTTPResponse('OK', 200);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment