Merge pull request #2095 from nextcloud/bruteforcesetttings
Introduce bruteforce settings
This commit is contained in:
commit
e0227cb458
|
@ -40,7 +40,6 @@
|
||||||
/apps/files_external/tests/config.*.php
|
/apps/files_external/tests/config.*.php
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ignore themes except the example and the README
|
# ignore themes except the example and the README
|
||||||
/themes/*
|
/themes/*
|
||||||
!/themes/example
|
!/themes/example
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"shippedApps": [
|
"shippedApps": [
|
||||||
"activity",
|
"activity",
|
||||||
"admin_audit",
|
"admin_audit",
|
||||||
|
"bruteforcesettings",
|
||||||
"comments",
|
"comments",
|
||||||
"dav",
|
"dav",
|
||||||
"encryption",
|
"encryption",
|
||||||
|
|
|
@ -185,6 +185,67 @@ class Throttler {
|
||||||
$qb->execute();
|
$qb->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the IP is whitelisted
|
||||||
|
*
|
||||||
|
* @param string $ip
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isIPWhitelisted($ip) {
|
||||||
|
$keys = $this->config->getAppKeys('bruteForce');
|
||||||
|
$keys = array_filter($keys, function($key) {
|
||||||
|
$regex = '/^whitelist_/S';
|
||||||
|
return preg_match($regex, $key) === 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
$type = 4;
|
||||||
|
} else if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
|
$type = 6;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = inet_pton($ip);
|
||||||
|
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$cidr = $this->config->getAppValue('bruteForce', $key, null);
|
||||||
|
|
||||||
|
$cx = explode('/', $cidr);
|
||||||
|
$addr = $cx[0];
|
||||||
|
$mask = (int)$cx[1];
|
||||||
|
|
||||||
|
// Do not compare ipv4 to ipv6
|
||||||
|
if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
|
||||||
|
($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$addr = inet_pton($addr);
|
||||||
|
|
||||||
|
$valid = true;
|
||||||
|
for($i = 0; $i < $mask; $i++) {
|
||||||
|
$part = ord($addr[(int)($i/8)]);
|
||||||
|
$orig = ord($ip[(int)($i/8)]);
|
||||||
|
|
||||||
|
$part = $part & (15 << (1 - ($i % 2)));
|
||||||
|
$orig = $orig & (15 << (1 - ($i % 2)));
|
||||||
|
|
||||||
|
if ($part !== $orig) {
|
||||||
|
$valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($valid === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the throttling delay (in milliseconds)
|
* Get the throttling delay (in milliseconds)
|
||||||
*
|
*
|
||||||
|
@ -193,6 +254,10 @@ class Throttler {
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getDelay($ip, $action = '') {
|
public function getDelay($ip, $action = '') {
|
||||||
|
if ($this->isIPWhitelisted($ip)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
$cutoffTime = (new \DateTime())
|
$cutoffTime = (new \DateTime())
|
||||||
->sub($this->getCutoff(43200))
|
->sub($this->getCutoff(43200))
|
||||||
->getTimestamp();
|
->getTimestamp();
|
||||||
|
|
|
@ -273,6 +273,7 @@ class Manager implements IManager {
|
||||||
$sections = [
|
$sections = [
|
||||||
0 => [new Section('server', $this->l->t('Server settings'), 0, $this->url->imagePath('settings', 'admin.svg'))],
|
0 => [new Section('server', $this->l->t('Server settings'), 0, $this->url->imagePath('settings', 'admin.svg'))],
|
||||||
5 => [new Section('sharing', $this->l->t('Sharing'), 0, $this->url->imagePath('core', 'actions/share.svg'))],
|
5 => [new Section('sharing', $this->l->t('Sharing'), 0, $this->url->imagePath('core', 'actions/share.svg'))],
|
||||||
|
10 => [new Section('security', $this->l->t('Security'), 0, $this->url->imagePath('core', 'actions/password.svg'))],
|
||||||
45 => [new Section('encryption', $this->l->t('Encryption'), 0, $this->url->imagePath('core', 'actions/password.svg'))],
|
45 => [new Section('encryption', $this->l->t('Encryption'), 0, $this->url->imagePath('core', 'actions/password.svg'))],
|
||||||
98 => [new Section('additional', $this->l->t('Additional settings'), 0, $this->url->imagePath('core', 'actions/settings-dark.svg'))],
|
98 => [new Section('additional', $this->l->t('Additional settings'), 0, $this->url->imagePath('core', 'actions/settings-dark.svg'))],
|
||||||
99 => [new Section('tips-tricks', $this->l->t('Tips & tricks'), 0, $this->url->imagePath('settings', 'help.svg'))],
|
99 => [new Section('tips-tricks', $this->l->t('Tips & tricks'), 0, $this->url->imagePath('settings', 'help.svg'))],
|
||||||
|
|
|
@ -40,7 +40,7 @@ class ThrottlerTest extends TestCase {
|
||||||
private $dbConnection;
|
private $dbConnection;
|
||||||
/** @var ILogger */
|
/** @var ILogger */
|
||||||
private $logger;
|
private $logger;
|
||||||
/** @var IConfig */
|
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
|
||||||
private $config;
|
private $config;
|
||||||
|
|
||||||
public function setUp() {
|
public function setUp() {
|
||||||
|
@ -120,4 +120,92 @@ class ThrottlerTest extends TestCase {
|
||||||
$this->invokePrivate($this->throttler, 'getIPv6Subnet', ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 40])
|
$this->invokePrivate($this->throttler, 'getIPv6Subnet', ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 40])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function dataIsIPWhitelisted() {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'10.10.10.10',
|
||||||
|
[
|
||||||
|
'whitelist_0' => '10.10.10.0/24',
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'10.10.10.10',
|
||||||
|
[
|
||||||
|
'whitelist_0' => '192.168.0.0/16',
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'10.10.10.10',
|
||||||
|
[
|
||||||
|
'whitelist_0' => '192.168.0.0/16',
|
||||||
|
'whitelist_1' => '10.10.10.0/24',
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'dead:beef:cafe::1',
|
||||||
|
[
|
||||||
|
'whitelist_0' => '192.168.0.0/16',
|
||||||
|
'whitelist_1' => '10.10.10.0/24',
|
||||||
|
'whitelist_2' => 'deaf:beef:cafe:1234::/64'
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'dead:beef:cafe::1',
|
||||||
|
[
|
||||||
|
'whitelist_0' => '192.168.0.0/16',
|
||||||
|
'whitelist_1' => '10.10.10.0/24',
|
||||||
|
'whitelist_2' => 'deaf:beef::/64'
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'dead:beef:cafe::1',
|
||||||
|
[
|
||||||
|
'whitelist_0' => '192.168.0.0/16',
|
||||||
|
'whitelist_1' => '10.10.10.0/24',
|
||||||
|
'whitelist_2' => 'deaf:cafe::/8'
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'invalid',
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider dataIsIPWhitelisted
|
||||||
|
*
|
||||||
|
* @param string $ip
|
||||||
|
* @param string[] $whitelists
|
||||||
|
* @param bool $isWhiteListed
|
||||||
|
*/
|
||||||
|
public function testIsIPWhitelisted($ip, $whitelists, $isWhiteListed) {
|
||||||
|
$this->config->method('getAppKeys')
|
||||||
|
->with($this->equalTo('bruteForce'))
|
||||||
|
->willReturn(array_keys($whitelists));
|
||||||
|
|
||||||
|
$this->config->method('getAppValue')
|
||||||
|
->will($this->returnCallback(function($app, $key, $default) use ($whitelists) {
|
||||||
|
if ($app !== 'bruteForce') {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
if (isset($whitelists[$key])) {
|
||||||
|
return $whitelists[$key];
|
||||||
|
}
|
||||||
|
return $default;
|
||||||
|
}));
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
$isWhiteListed,
|
||||||
|
$this->invokePrivate($this->throttler, 'isIPWhitelisted', [$ip])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,7 +146,7 @@ class ManagerTest extends TestCase {
|
||||||
['class' => \OCA\WorkflowEngine\Settings\Section::class, 'priority' => 90]
|
['class' => \OCA\WorkflowEngine\Settings\Section::class, 'priority' => 90]
|
||||||
]));
|
]));
|
||||||
|
|
||||||
$this->url->expects($this->exactly(5))
|
$this->url->expects($this->exactly(6))
|
||||||
->method('imagePath')
|
->method('imagePath')
|
||||||
->willReturnMap([
|
->willReturnMap([
|
||||||
['settings', 'admin.svg', '1'],
|
['settings', 'admin.svg', '1'],
|
||||||
|
@ -159,6 +159,7 @@ class ManagerTest extends TestCase {
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
0 => [new Section('server', 'Server settings', 0, '1')],
|
0 => [new Section('server', 'Server settings', 0, '1')],
|
||||||
5 => [new Section('sharing', 'Sharing', 0, '2')],
|
5 => [new Section('sharing', 'Sharing', 0, '2')],
|
||||||
|
10 => [new Section('security', 'Security', 0, '3')],
|
||||||
45 => [new Section('encryption', 'Encryption', 0, '3')],
|
45 => [new Section('encryption', 'Encryption', 0, '3')],
|
||||||
90 => [\OC::$server->query(\OCA\WorkflowEngine\Settings\Section::class)],
|
90 => [\OC::$server->query(\OCA\WorkflowEngine\Settings\Section::class)],
|
||||||
98 => [new Section('additional', 'Additional settings', 0, '4')],
|
98 => [new Section('additional', 'Additional settings', 0, '4')],
|
||||||
|
@ -177,7 +178,7 @@ class ManagerTest extends TestCase {
|
||||||
->will($this->returnValue([
|
->will($this->returnValue([
|
||||||
]));
|
]));
|
||||||
|
|
||||||
$this->url->expects($this->exactly(5))
|
$this->url->expects($this->exactly(6))
|
||||||
->method('imagePath')
|
->method('imagePath')
|
||||||
->willReturnMap([
|
->willReturnMap([
|
||||||
['settings', 'admin.svg', '1'],
|
['settings', 'admin.svg', '1'],
|
||||||
|
@ -190,6 +191,7 @@ class ManagerTest extends TestCase {
|
||||||
$this->assertEquals([
|
$this->assertEquals([
|
||||||
0 => [new Section('server', 'Server settings', 0, '1')],
|
0 => [new Section('server', 'Server settings', 0, '1')],
|
||||||
5 => [new Section('sharing', 'Sharing', 0, '2')],
|
5 => [new Section('sharing', 'Sharing', 0, '2')],
|
||||||
|
10 => [new Section('security', 'Security', 0, '3')],
|
||||||
45 => [new Section('encryption', 'Encryption', 0, '3')],
|
45 => [new Section('encryption', 'Encryption', 0, '3')],
|
||||||
98 => [new Section('additional', 'Additional settings', 0, '4')],
|
98 => [new Section('additional', 'Additional settings', 0, '4')],
|
||||||
99 => [new Section('tips-tricks', 'Tips & tricks', 0, '5')],
|
99 => [new Section('tips-tricks', 'Tips & tricks', 0, '5')],
|
||||||
|
|
Loading…
Reference in New Issue