summaryrefslogtreecommitdiffstats
path: root/tests/lib/Security
diff options
context:
space:
mode:
authorLukas Reschke <lukas@statuscode.ch>2017-04-12 20:32:48 +0200
committerLukas Reschke <lukas@statuscode.ch>2017-04-13 12:00:16 +0200
commit66835476b59b8be7593d4cfa03a51c4f265d7e26 (patch)
tree91770c8fe403da25af50e6336727ab55fe57cd27 /tests/lib/Security
parent5505faa3d7b6f5a95f18fe5027355d700d69f396 (diff)
downloadnextcloud-server-66835476b59b8be7593d4cfa03a51c4f265d7e26.tar.gz
nextcloud-server-66835476b59b8be7593d4cfa03a51c4f265d7e26.zip
Add support for ratelimiting via annotations
This allows adding rate limiting via annotations to controllers, as one example: ``` @UserRateThrottle(limit=5, period=100) @AnonRateThrottle(limit=1, period=100) ``` Would mean that logged-in users can access the page 5 times within 100 seconds, and anonymous users 1 time within 100 seconds. If only an AnonRateThrottle is specified that one will also be applied to logged-in users. Signed-off-by: Lukas Reschke <lukas@statuscode.ch>
Diffstat (limited to 'tests/lib/Security')
-rw-r--r--tests/lib/Security/Bruteforce/ThrottlerTest.php45
-rw-r--r--tests/lib/Security/Normalizer/IpAddressTest.php59
-rw-r--r--tests/lib/Security/RateLimiting/Backend/MemoryCacheTest.php138
-rw-r--r--tests/lib/Security/RateLimiting/LimiterTest.php161
4 files changed, 358 insertions, 45 deletions
diff --git a/tests/lib/Security/Bruteforce/ThrottlerTest.php b/tests/lib/Security/Bruteforce/ThrottlerTest.php
index 02d5b701679..9679d0c1759 100644
--- a/tests/lib/Security/Bruteforce/ThrottlerTest.php
+++ b/tests/lib/Security/Bruteforce/ThrottlerTest.php
@@ -76,51 +76,6 @@ class ThrottlerTest extends TestCase {
$this->assertLessThan(2, $cutoff->s);
}
- public function testSubnet() {
- // IPv4
- $this->assertSame(
- '64.233.191.254/32',
- $this->invokePrivate($this->throttler, 'getIPv4Subnet', ['64.233.191.254', 32])
- );
- $this->assertSame(
- '64.233.191.252/30',
- $this->invokePrivate($this->throttler, 'getIPv4Subnet', ['64.233.191.254', 30])
- );
- $this->assertSame(
- '64.233.191.240/28',
- $this->invokePrivate($this->throttler, 'getIPv4Subnet', ['64.233.191.254', 28])
- );
- $this->assertSame(
- '64.233.191.0/24',
- $this->invokePrivate($this->throttler, 'getIPv4Subnet', ['64.233.191.254', 24])
- );
- $this->assertSame(
- '64.233.188.0/22',
- $this->invokePrivate($this->throttler, 'getIPv4Subnet', ['64.233.191.254', 22])
- );
- // IPv6
- $this->assertSame(
- '2001:db8:85a3::8a2e:370:7334/127',
- $this->invokePrivate($this->throttler, 'getIPv6Subnet', ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 127])
- );
- $this->assertSame(
- '2001:db8:85a3::8a2e:370:7300/120',
- $this->invokePrivate($this->throttler, 'getIPv6Subnet', ['2001:0db8:85a3:0000:0000:8a2e:0370:7300', 120])
- );
- $this->assertSame(
- '2001:db8:85a3::/64',
- $this->invokePrivate($this->throttler, 'getIPv6Subnet', ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 64])
- );
- $this->assertSame(
- '2001:db8:85a3::/48',
- $this->invokePrivate($this->throttler, 'getIPv6Subnet', ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 48])
- );
- $this->assertSame(
- '2001:db8:8500::/40',
- $this->invokePrivate($this->throttler, 'getIPv6Subnet', ['2001:0db8:85a3:0000:0000:8a2e:0370:7334', 40])
- );
- }
-
public function dataIsIPWhitelisted() {
return [
[
diff --git a/tests/lib/Security/Normalizer/IpAddressTest.php b/tests/lib/Security/Normalizer/IpAddressTest.php
new file mode 100644
index 00000000000..36a48f601d3
--- /dev/null
+++ b/tests/lib/Security/Normalizer/IpAddressTest.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\Security\Normalizer;
+
+use OC\Security\Normalizer\IpAddress;
+use Test\TestCase;
+
+class IpAddressTest extends TestCase {
+
+ public function subnetDataProvider() {
+ return [
+ [
+ '64.233.191.254',
+ '64.233.191.254/32',
+ ],
+ [
+ '192.168.0.123',
+ '192.168.0.123/32',
+ ],
+ [
+ '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ '2001:db8:85a3::8a2e:370:7334/128',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider subnetDataProvider
+ *
+ * @param string $input
+ * @param string $expected
+ */
+ public function testGetSubnet($input, $expected) {
+ $this->assertSame($expected, (new IpAddress($input))->getSubnet());
+ }
+
+ public function testToString() {
+ $this->assertSame('127.0.0.1', (string)(new IpAddress('127.0.0.1')));
+ }
+}
diff --git a/tests/lib/Security/RateLimiting/Backend/MemoryCacheTest.php b/tests/lib/Security/RateLimiting/Backend/MemoryCacheTest.php
new file mode 100644
index 00000000000..f00d734661d
--- /dev/null
+++ b/tests/lib/Security/RateLimiting/Backend/MemoryCacheTest.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\Security\RateLimiting\Backend;
+
+use OC\Security\RateLimiting\Backend\MemoryCache;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use Test\TestCase;
+
+class MemoryCacheTest extends TestCase {
+ /** @var ICacheFactory|\PHPUnit_Framework_MockObject_MockObject */
+ private $cacheFactory;
+ /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
+ private $timeFactory;
+ /** @var ICache|\PHPUnit_Framework_MockObject_MockObject */
+ private $cache;
+ /** @var MemoryCache */
+ private $memoryCache;
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->cacheFactory = $this->createMock(ICacheFactory::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->cache = $this->createMock(ICache::class);
+
+ $this->cacheFactory
+ ->expects($this->once())
+ ->method('create')
+ ->with('OC\Security\RateLimiting\Backend\MemoryCache')
+ ->willReturn($this->cache);
+
+ $this->memoryCache = new MemoryCache(
+ $this->cacheFactory,
+ $this->timeFactory
+ );
+ }
+
+ public function testGetAttemptsWithNoAttemptsBefore() {
+ $this->cache
+ ->expects($this->once())
+ ->method('get')
+ ->with('eea460b8d756885099c7f0a4c083bf6a745069ee4a301984e726df58fd4510bffa2dac4b7fd5d835726a6753ffa8343ba31c7e902bbef78fc68c2e743667cb4b')
+ ->willReturn(false);
+
+ $this->assertSame(0, $this->memoryCache->getAttempts('Method', 'User', 123));
+ }
+
+ public function testGetAttempts() {
+ $this->timeFactory
+ ->expects($this->once())
+ ->method('getTime')
+ ->willReturn(210);
+ $this->cache
+ ->expects($this->once())
+ ->method('get')
+ ->with('eea460b8d756885099c7f0a4c083bf6a745069ee4a301984e726df58fd4510bffa2dac4b7fd5d835726a6753ffa8343ba31c7e902bbef78fc68c2e743667cb4b')
+ ->willReturn(json_encode([
+ '1',
+ '2',
+ '87',
+ '123',
+ '123',
+ '124',
+ ]));
+
+ $this->assertSame(3, $this->memoryCache->getAttempts('Method', 'User', 123));
+ }
+
+ public function testRegisterAttemptWithNoAttemptsBefore() {
+ $this->cache
+ ->expects($this->once())
+ ->method('get')
+ ->with('eea460b8d756885099c7f0a4c083bf6a745069ee4a301984e726df58fd4510bffa2dac4b7fd5d835726a6753ffa8343ba31c7e902bbef78fc68c2e743667cb4b')
+ ->willReturn(false);
+ $this->cache
+ ->expects($this->once())
+ ->method('set')
+ ->with(
+ 'eea460b8d756885099c7f0a4c083bf6a745069ee4a301984e726df58fd4510bffa2dac4b7fd5d835726a6753ffa8343ba31c7e902bbef78fc68c2e743667cb4b',
+ json_encode(['123'])
+ );
+
+ $this->memoryCache->registerAttempt('Method', 'User', 123);
+ }
+
+ public function testRegisterAttempts() {
+ $this->cache
+ ->expects($this->once())
+ ->method('get')
+ ->with('eea460b8d756885099c7f0a4c083bf6a745069ee4a301984e726df58fd4510bffa2dac4b7fd5d835726a6753ffa8343ba31c7e902bbef78fc68c2e743667cb4b')
+ ->willReturn(json_encode([
+ '1',
+ '2',
+ '87',
+ '123',
+ '123',
+ '124',
+ ]));
+ $this->cache
+ ->expects($this->once())
+ ->method('set')
+ ->with(
+ 'eea460b8d756885099c7f0a4c083bf6a745069ee4a301984e726df58fd4510bffa2dac4b7fd5d835726a6753ffa8343ba31c7e902bbef78fc68c2e743667cb4b',
+ json_encode([
+ '1',
+ '2',
+ '87',
+ '123',
+ '123',
+ '124',
+ '129',
+ ])
+ );
+
+ $this->memoryCache->registerAttempt('Method', 'User', 129);
+ }
+}
diff --git a/tests/lib/Security/RateLimiting/LimiterTest.php b/tests/lib/Security/RateLimiting/LimiterTest.php
new file mode 100644
index 00000000000..80b63ebb391
--- /dev/null
+++ b/tests/lib/Security/RateLimiting/LimiterTest.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\Security\RateLimiting;
+
+use OC\Security\RateLimiting\Backend\IBackend;
+use OC\Security\RateLimiting\Limiter;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\ICacheFactory;
+use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserSession;
+use Test\TestCase;
+
+class LimiterTest extends TestCase {
+ /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
+ private $userSession;
+ /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
+ private $request;
+ /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
+ private $timeFactory;
+ /** @var IBackend|\PHPUnit_Framework_MockObject_MockObject */
+ private $backend;
+ /** @var Limiter */
+ private $limiter;
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->userSession = $this->createMock(IUserSession::class);
+ $this->request = $this->createMock(IRequest::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->backend = $this->createMock(IBackend::class);
+
+ $this->limiter = new Limiter(
+ $this->userSession,
+ $this->request,
+ $this->timeFactory,
+ $this->backend
+ );
+ }
+
+ /**
+ * @expectedException \OC\Security\RateLimiting\Exception\RateLimitExceededException
+ * @expectedExceptionMessage Rate limit exceeded
+ */
+ public function testRegisterAnonRequestExceeded() {
+ $this->backend
+ ->expects($this->once())
+ ->method('getAttempts')
+ ->with(
+ 'MyIdentifier',
+ '4664f0d9c88dcb7552be47b37bb52ce35977b2e60e1ac13757cf625f31f87050a41f3da064887fa87d49fd042e4c8eb20de8f10464877d3959677ab011b73a47',
+ 100
+ )
+ ->willReturn(101);
+
+ $this->limiter->registerAnonRequest('MyIdentifier', 100, 100, '127.0.0.1');
+ }
+
+ public function testRegisterAnonRequestSuccess() {
+ $this->timeFactory
+ ->expects($this->once())
+ ->method('getTime')
+ ->willReturn(2000);
+ $this->backend
+ ->expects($this->once())
+ ->method('getAttempts')
+ ->with(
+ 'MyIdentifier',
+ '4664f0d9c88dcb7552be47b37bb52ce35977b2e60e1ac13757cf625f31f87050a41f3da064887fa87d49fd042e4c8eb20de8f10464877d3959677ab011b73a47',
+ 100
+ )
+ ->willReturn(99);
+ $this->backend
+ ->expects($this->once())
+ ->method('registerAttempt')
+ ->with(
+ 'MyIdentifier',
+ '4664f0d9c88dcb7552be47b37bb52ce35977b2e60e1ac13757cf625f31f87050a41f3da064887fa87d49fd042e4c8eb20de8f10464877d3959677ab011b73a47',
+ 2000
+ );
+
+ $this->limiter->registerAnonRequest('MyIdentifier', 100, 100, '127.0.0.1');
+ }
+
+ /**
+ * @expectedException \OC\Security\RateLimiting\Exception\RateLimitExceededException
+ * @expectedExceptionMessage Rate limit exceeded
+ */
+ public function testRegisterUserRequestExceeded() {
+ /** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
+ $user = $this->createMock(IUser::class);
+ $user
+ ->expects($this->once())
+ ->method('getUID')
+ ->willReturn('MyUid');
+ $this->backend
+ ->expects($this->once())
+ ->method('getAttempts')
+ ->with(
+ 'MyIdentifier',
+ 'ddb2ec50fa973fd49ecf3d816f677c8095143e944ad10485f30fb3dac85c13a346dace4dae2d0a15af91867320957bfd38a43d9eefbb74fe6919e15119b6d805',
+ 100
+ )
+ ->willReturn(101);
+
+ $this->limiter->registerUserRequest('MyIdentifier', 100, 100, $user);
+ }
+
+ public function testRegisterUserRequestSuccess() {
+ /** @var IUser|\PHPUnit_Framework_MockObject_MockObject $user */
+ $user = $this->createMock(IUser::class);
+ $user
+ ->expects($this->once())
+ ->method('getUID')
+ ->willReturn('MyUid');
+
+ $this->timeFactory
+ ->expects($this->once())
+ ->method('getTime')
+ ->willReturn(2000);
+ $this->backend
+ ->expects($this->once())
+ ->method('getAttempts')
+ ->with(
+ 'MyIdentifier',
+ 'ddb2ec50fa973fd49ecf3d816f677c8095143e944ad10485f30fb3dac85c13a346dace4dae2d0a15af91867320957bfd38a43d9eefbb74fe6919e15119b6d805',
+ 100
+ )
+ ->willReturn(99);
+ $this->backend
+ ->expects($this->once())
+ ->method('registerAttempt')
+ ->with(
+ 'MyIdentifier',
+ 'ddb2ec50fa973fd49ecf3d816f677c8095143e944ad10485f30fb3dac85c13a346dace4dae2d0a15af91867320957bfd38a43d9eefbb74fe6919e15119b6d805',
+ 2000
+ );
+
+ $this->limiter->registerUserRequest('MyIdentifier', 100, 100, $user);
+ }
+}