diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php
index 50348a6020236eba51f4a773ed51fd873ee36458..975fd34ae8ec936bf74b5fe3922d677afeb5e986 100644
--- a/apps/dav/appinfo/v1/caldav.php
+++ b/apps/dav/appinfo/v1/caldav.php
@@ -35,6 +35,7 @@ $authBackend = new Auth(
 	\OC::$server->getUserSession(),
 	\OC::$server->getRequest(),
 	\OC::$server->getTwoFactorAuthManager(),
+	\OC::$server->getBruteForceThrottler(),
 	'principals/'
 );
 $principalBackend = new Principal(
diff --git a/apps/dav/appinfo/v1/carddav.php b/apps/dav/appinfo/v1/carddav.php
index fc7aff4a63c2f46ddaebbaf56680b4b82e9412c9..e2d8944fcb6a3e6a93589bfa5c22724b1cc166d5 100644
--- a/apps/dav/appinfo/v1/carddav.php
+++ b/apps/dav/appinfo/v1/carddav.php
@@ -36,6 +36,7 @@ $authBackend = new Auth(
 	\OC::$server->getUserSession(),
 	\OC::$server->getRequest(),
 	\OC::$server->getTwoFactorAuthManager(),
+	\OC::$server->getBruteForceThrottler(),
 	'principals/'
 );
 $principalBackend = new Principal(
diff --git a/apps/dav/appinfo/v1/webdav.php b/apps/dav/appinfo/v1/webdav.php
index 3b733c0fbd502afeb943bf323cf62a0ef4085696..2af49177ce14680398a260e418674408ecf7f917 100644
--- a/apps/dav/appinfo/v1/webdav.php
+++ b/apps/dav/appinfo/v1/webdav.php
@@ -43,6 +43,7 @@ $authBackend = new \OCA\DAV\Connector\Sabre\Auth(
 	\OC::$server->getUserSession(),
 	\OC::$server->getRequest(),
 	\OC::$server->getTwoFactorAuthManager(),
+	\OC::$server->getBruteForceThrottler(),
 	'principals/'
 );
 $requestUri = \OC::$server->getRequest()->getRequestUri();
diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php
index 28e4ae2bcdea96877501cd6ecdd2c9ce886049a8..3f9e16b04c506b253e3fa171da29d7887f8e91a3 100644
--- a/apps/dav/lib/Connector/Sabre/Auth.php
+++ b/apps/dav/lib/Connector/Sabre/Auth.php
@@ -33,6 +33,7 @@ use Exception;
 use OC\AppFramework\Http\Request;
 use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
 use OC\Authentication\TwoFactorAuth\Manager;
+use OC\Security\Bruteforce\Throttler;
 use OC\User\Session;
 use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden;
 use OCP\IRequest;
@@ -58,23 +59,28 @@ class Auth extends AbstractBasic {
 	private $currentUser;
 	/** @var Manager */
 	private $twoFactorManager;
+	/** @var Throttler */
+	private $throttler;
 
 	/**
 	 * @param ISession $session
 	 * @param Session $userSession
 	 * @param IRequest $request
 	 * @param Manager $twoFactorManager
+	 * @param Throttler $throttler
 	 * @param string $principalPrefix
 	 */
 	public function __construct(ISession $session,
 								Session $userSession,
 								IRequest $request,
 								Manager $twoFactorManager,
+								Throttler $throttler,
 								$principalPrefix = 'principals/users/') {
 		$this->session = $session;
 		$this->userSession = $userSession;
 		$this->twoFactorManager = $twoFactorManager;
 		$this->request = $request;
+		$this->throttler = $throttler;
 		$this->principalPrefix = $principalPrefix;
 
 		// setup realm
@@ -107,6 +113,7 @@ class Auth extends AbstractBasic {
 	 * @param string $username
 	 * @param string $password
 	 * @return bool
+	 * @throws PasswordLoginForbidden
 	 */
 	protected function validateUserPass($username, $password) {
 		if ($this->userSession->isLoggedIn() &&
@@ -118,7 +125,7 @@ class Auth extends AbstractBasic {
 		} else {
 			\OC_Util::setupFS(); //login hooks may need early access to the filesystem
 			try {
-				if ($this->userSession->logClientIn($username, $password, $this->request)) {
+				if ($this->userSession->logClientIn($username, $password, $this->request, $this->throttler)) {
 					\OC_Util::setupFS($this->userSession->getUser()->getUID());
 					$this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID());
 					$this->session->close();
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index 73a07072d3cea0de363144d19937be5906b6fd28..982d299c951a996613057dc463da80f6df69adce 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -64,7 +64,8 @@ class Server {
 			\OC::$server->getSession(),
 			\OC::$server->getUserSession(),
 			\OC::$server->getRequest(),
-			\OC::$server->getTwoFactorAuthManager()
+			\OC::$server->getTwoFactorAuthManager(),
+			\OC::$server->getBruteForceThrottler()
 		);
 
 		// Set URL explicitly due to reverse-proxy situations
diff --git a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php
index 92798797d6ce789f5f6654757e7aea0e812925ad..142b83a45b8e3be72c5e0e2a10b97118bc4a959e 100644
--- a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php
+++ b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php
@@ -28,6 +28,7 @@
 namespace OCA\DAV\Tests\unit\Connector\Sabre;
 
 use OC\Authentication\TwoFactorAuth\Manager;
+use OC\Security\Bruteforce\Throttler;
 use OC\User\Session;
 use OCP\IRequest;
 use OCP\ISession;
@@ -51,6 +52,8 @@ class AuthTest extends TestCase {
 	private $request;
 	/** @var Manager */
 	private $twoFactorManager;
+	/** @var Throttler */
+	private $throttler;
 
 	public function setUp() {
 		parent::setUp();
@@ -63,11 +66,15 @@ class AuthTest extends TestCase {
 		$this->twoFactorManager = $this->getMockBuilder('\OC\Authentication\TwoFactorAuth\Manager')
 			->disableOriginalConstructor()
 			->getMock();
+		$this->throttler = $this->getMockBuilder('\OC\Security\Bruteforce\Throttler')
+			->disableOriginalConstructor()
+			->getMock();
 		$this->auth = new \OCA\DAV\Connector\Sabre\Auth(
 			$this->session,
 			$this->userSession,
 			$this->request,
-			$this->twoFactorManager
+			$this->twoFactorManager,
+			$this->throttler
 		);
 	}
 
diff --git a/build/integration/run.sh b/build/integration/run.sh
index 2abceaa1fad62c94a191f32f347d87c6dc1a97fc..eccb378eec49122b4d8ec7f41decf43a81debe7f 100755
--- a/build/integration/run.sh
+++ b/build/integration/run.sh
@@ -9,6 +9,9 @@ else
 	exit 1
 fi
 
+# Disable bruteforce protection because the integration tests do trigger them
+../../occ config:system:set auth.bruteforce.protection.enabled --value false --type bool
+
 composer install
 
 SCENARIO_TO_RUN=$1
diff --git a/config/config.sample.php b/config/config.sample.php
index 051e5422fe5cd14520f563267b189ef05dd71436..c9f5fecf5f9db3a198687ba845140a6166767cba 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -207,6 +207,13 @@ $CONFIG = array(
  */
 'token_auth_enforced' => false,
 
+/**
+ * Whether the bruteforce protection shipped with Nextcloud should be enabled or not.
+ *
+ * Disabling this is discouraged for security reasons.
+ */
+'auth.bruteforce.protection.enabled' => true,
+
 /**
  * The directory where the skeleton files are located. These files will be
  * copied to the data directory of new users. Leave empty to not copy any
diff --git a/core/Application.php b/core/Application.php
index 1485f7a7516b3a84ddc5e752134ed44025ce088c..82ec5ad023c02f04be9ecc715e7b4c510710b89d 100644
--- a/core/Application.php
+++ b/core/Application.php
@@ -103,7 +103,8 @@ class Application extends App {
 				$c->query('Session'),
 				$c->query('UserSession'),
 				$c->query('URLGenerator'),
-				$c->query('TwoFactorAuthManager')
+				$c->query('TwoFactorAuthManager'),
+				$c->query('ServerContainer')->getBruteforceThrottler()
 			);
 		});
 		$container->registerService('TwoFactorChallengeController', function (SimpleContainer $c) {
diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php
index 7806e1de904dda5cf30b8061890bbdf780ef3458..66bb13dbb54a634802620d7ec153e78c3f4fcfd5 100644
--- a/core/Controller/LoginController.php
+++ b/core/Controller/LoginController.php
@@ -22,7 +22,9 @@
 
 namespace OC\Core\Controller;
 
+use OC\AppFramework\Utility\TimeFactory;
 use OC\Authentication\TwoFactorAuth\Manager;
+use OC\Security\Bruteforce\Throttler;
 use OC\User\Session;
 use OC_App;
 use OC_Util;
@@ -37,24 +39,20 @@ use OCP\IUser;
 use OCP\IUserManager;
 
 class LoginController extends Controller {
-
 	/** @var IUserManager */
 	private $userManager;
-
 	/** @var IConfig */
 	private $config;
-
 	/** @var ISession */
 	private $session;
-
 	/** @var Session */
 	private $userSession;
-
 	/** @var IURLGenerator */
 	private $urlGenerator;
-
 	/** @var Manager */
 	private $twoFactorManager;
+	/** @var Throttler */
+	private $throttler;
 
 	/**
 	 * @param string $appName
@@ -65,9 +63,17 @@ class LoginController extends Controller {
 	 * @param Session $userSession
 	 * @param IURLGenerator $urlGenerator
 	 * @param Manager $twoFactorManager
+	 * @param Throttler $throttler
 	 */
-	function __construct($appName, IRequest $request, IUserManager $userManager, IConfig $config, ISession $session,
-		Session $userSession, IURLGenerator $urlGenerator, Manager $twoFactorManager) {
+	function __construct($appName,
+						 IRequest $request,
+						 IUserManager $userManager,
+						 IConfig $config,
+						 ISession $session,
+						 Session $userSession,
+						 IURLGenerator $urlGenerator,
+						 Manager $twoFactorManager,
+						 Throttler $throttler) {
 		parent::__construct($appName, $request);
 		$this->userManager = $userManager;
 		$this->config = $config;
@@ -75,6 +81,7 @@ class LoginController extends Controller {
 		$this->userSession = $userSession;
 		$this->urlGenerator = $urlGenerator;
 		$this->twoFactorManager = $twoFactorManager;
+		$this->throttler = $throttler;
 	}
 
 	/**
@@ -171,6 +178,9 @@ class LoginController extends Controller {
 	 * @return RedirectResponse
 	 */
 	public function tryLogin($user, $password, $redirect_url) {
+		$currentDelay = $this->throttler->getDelay($this->request->getRemoteAddress());
+		$this->throttler->sleepDelay($this->request->getRemoteAddress());
+
 		$originalUser = $user;
 		// TODO: Add all the insane error handling
 		/* @var $loginResult IUser */
@@ -184,6 +194,10 @@ class LoginController extends Controller {
 			}
 		}
 		if ($loginResult === false) {
+			$this->throttler->registerAttempt('login', $this->request->getRemoteAddress(), ['user' => $originalUser]);
+			if($currentDelay === 0) {
+				$this->throttler->sleepDelay($this->request->getRemoteAddress());
+			}
 			$this->session->set('loginMessages', [
 				['invalidpassword']
 			]);
diff --git a/core/Controller/TokenController.php b/core/Controller/TokenController.php
index 13b1db9044aef3aab94116ef7c0f76a7385b9114..8401c4f23a9f739b8fe3f536a2670eb3ec9deea8 100644
--- a/core/Controller/TokenController.php
+++ b/core/Controller/TokenController.php
@@ -1,5 +1,4 @@
 <?php
-
 /**
  * @author Christoph Wurst <christoph@owncloud.com>
  *
@@ -23,6 +22,7 @@
 namespace OC\Core\Controller;
 
 use OC\AppFramework\Http;
+use OC\AppFramework\Utility\TimeFactory;
 use OC\Authentication\Token\DefaultTokenProvider;
 use OC\Authentication\Token\IProvider;
 use OC\Authentication\Token\IToken;
@@ -35,27 +35,29 @@ use OCP\IRequest;
 use OCP\Security\ISecureRandom;
 
 class TokenController extends Controller {
-
 	/** @var UserManager */
 	private $userManager;
-
 	/** @var IProvider */
 	private $tokenProvider;
-
 	/** @var TwoFactorAuthManager */
 	private $twoFactorAuthManager;
-
 	/** @var ISecureRandom */
 	private $secureRandom;
 
 	/**
 	 * @param string $appName
 	 * @param IRequest $request
-	 * @param Manager $userManager
-	 * @param DefaultTokenProvider $tokenProvider
+	 * @param UserManager $userManager
+	 * @param IProvider $tokenProvider
+	 * @param TwoFactorAuthManager $twoFactorAuthManager
 	 * @param ISecureRandom $secureRandom
 	 */
-	public function __construct($appName, IRequest $request, UserManager $userManager, IProvider $tokenProvider, TwoFactorAuthManager $twoFactorAuthManager, ISecureRandom $secureRandom) {
+	public function __construct($appName,
+								IRequest $request,
+								UserManager $userManager,
+								IProvider $tokenProvider,
+								TwoFactorAuthManager $twoFactorAuthManager,
+								ISecureRandom $secureRandom) {
 		parent::__construct($appName, $request);
 		$this->userManager = $userManager;
 		$this->tokenProvider = $tokenProvider;
diff --git a/db_structure.xml b/db_structure.xml
index 1127f0d82d440090a82be19cd1b593f7215ef356..04c91ea494f70704d52d150560594a5ae1c74d00 100644
--- a/db_structure.xml
+++ b/db_structure.xml
@@ -1163,6 +1163,84 @@
 		</declaration>
 	</table>
 
+	<table>
+
+		<!--
+		List of usernames, their display name and login password.
+		-->
+		<name>*dbprefix*bruteforce_attempts</name>
+
+		<declaration>
+			<field>
+				<name>id</name>
+				<type>integer</type>
+				<default>0</default>
+				<notnull>true</notnull>
+				<autoincrement>1</autoincrement>
+				<unsigned>true</unsigned>
+				<length>4</length>
+			</field>
+
+			<field>
+				<name>action</name>
+				<type>text</type>
+				<default></default>
+				<notnull>true</notnull>
+				<length>64</length>
+			</field>
+
+			<field>
+				<name>occurred</name>
+				<type>integer</type>
+				<default>0</default>
+				<notnull>true</notnull>
+				<unsigned>true</unsigned>
+				<length>4</length>
+			</field>
+
+			<field>
+				<name>ip</name>
+				<type>text</type>
+				<default></default>
+				<notnull>true</notnull>
+				<length>255</length>
+			</field>
+
+			<field>
+				<name>subnet</name>
+				<type>text</type>
+				<default></default>
+				<notnull>true</notnull>
+				<length>255</length>
+			</field>
+
+			<field>
+				<name>metadata</name>
+				<type>text</type>
+				<default></default>
+				<notnull>true</notnull>
+				<length>255</length>
+			</field>
+
+			<index>
+				<name>bruteforce_attempts_ip</name>
+				<field>
+					<name>ip</name>
+					<sorting>ascending</sorting>
+				</field>
+			</index>
+			<index>
+				<name>bruteforce_attempts_subnet</name>
+				<field>
+					<name>subnet</name>
+					<sorting>ascending</sorting>
+				</field>
+			</index>
+
+		</declaration>
+
+	</table>
+
 	<table>
 
 		<!--
diff --git a/lib/base.php b/lib/base.php
index afa13cd104772cdee7ddf8eec2c910cb2b59d208..b2ac0bab7d8594e0d9812ca0142b50947f8251e1 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -993,7 +993,7 @@ class OC {
 		if ($userSession->tryTokenLogin($request)) {
 			return true;
 		}
-		if ($userSession->tryBasicAuthLogin($request)) {
+		if ($userSession->tryBasicAuthLogin($request, \OC::$server->getBruteForceThrottler())) {
 			return true;
 		}
 		return false;
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index 32a85606abfe7c62ad23a88827d08ea4145e5fe5..893d6cb9aa66e253a07e2ef64af4199419c28db8 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -352,7 +352,8 @@ class DIContainer extends SimpleContainer implements IAppContainer {
 			return new CORSMiddleware(
 				$c['Request'],
 				$c['ControllerMethodReflector'],
-				$c['OCP\IUserSession']
+				$c['OCP\IUserSession'],
+				$c->getServer()->getBruteForceThrottler()
 			);
 		});
 
diff --git a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
index 32a507623e323a22949778b93c78d04c64fbf015..04de4bc92d34d9d434328617f3fda3fd726e5bb6 100644
--- a/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php
@@ -27,6 +27,7 @@ namespace OC\AppFramework\Middleware\Security;
 use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
 use OC\AppFramework\Utility\ControllerMethodReflector;
 use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
+use OC\Security\Bruteforce\Throttler;
 use OC\User\Session;
 use OCP\AppFramework\Controller;
 use OCP\AppFramework\Http;
@@ -42,33 +43,29 @@ use OCP\IRequest;
  * https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
  */
 class CORSMiddleware extends Middleware {
-
-	/**
-	 * @var IRequest
-	 */
+	/** @var IRequest  */
 	private $request;
-
-	/**
-	 * @var ControllerMethodReflector
-	 */
+	/** @var ControllerMethodReflector */
 	private $reflector;
-
-	/**
-	 * @var Session
-	 */
+	/** @var Session */
 	private $session;
+	/** @var Throttler */
+	private $throttler;
 
 	/**
 	 * @param IRequest $request
 	 * @param ControllerMethodReflector $reflector
 	 * @param Session $session
+	 * @param Throttler $throttler
 	 */
 	public function __construct(IRequest $request,
 								ControllerMethodReflector $reflector,
-								Session $session) {
+								Session $session,
+								Throttler $throttler) {
 		$this->request = $request;
 		$this->reflector = $reflector;
 		$this->session = $session;
+		$this->throttler = $throttler;
 	}
 
 	/**
@@ -91,7 +88,7 @@ class CORSMiddleware extends Middleware {
 
 			$this->session->logout();
 			try {
-				if (!$this->session->logClientIn($user, $pass, $this->request)) {
+				if (!$this->session->logClientIn($user, $pass, $this->request, $this->throttler)) {
 					throw new SecurityException('CORS requires basic auth', Http::STATUS_UNAUTHORIZED);
 				}
 			} catch (PasswordLoginForbiddenException $ex) {
diff --git a/lib/private/Security/Bruteforce/Throttler.php b/lib/private/Security/Bruteforce/Throttler.php
new file mode 100644
index 0000000000000000000000000000000000000000..0de7677285b975eac5e936f689799752e425e2da
--- /dev/null
+++ b/lib/private/Security/Bruteforce/Throttler.php
@@ -0,0 +1,230 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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 OC\Security\Bruteforce;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\ILogger;
+
+/**
+ * Class Throttler implements the bruteforce protection for security actions in
+ * Nextcloud.
+ *
+ * It is working by logging invalid login attempts to the database and slowing
+ * down all login attempts from the same subnet. The max delay is 30 seconds and
+ * the starting delay are 200 milliseconds. (after the first failed login)
+ *
+ * This is based on Paragonie's AirBrake for Airship CMS. You can find the original
+ * code at https://github.com/paragonie/airship/blob/7e5bad7e3c0fbbf324c11f963fd1f80e59762606/src/Engine/Security/AirBrake.php
+ *
+ * @package OC\Security\Bruteforce
+ */
+class Throttler {
+	const LOGIN_ACTION = 'login';
+
+	/** @var IDBConnection */
+	private $db;
+	/** @var ITimeFactory */
+	private $timeFactory;
+	/** @var ILogger */
+	private $logger;
+	/** @var IConfig */
+	private $config;
+
+	/**
+	 * @param IDBConnection $db
+	 * @param ITimeFactory $timeFactory
+	 * @param ILogger $logger
+	 * @param IConfig $config
+	 */
+	public function __construct(IDBConnection $db,
+								ITimeFactory $timeFactory,
+								ILogger $logger,
+								IConfig $config) {
+		$this->db = $db;
+		$this->timeFactory = $timeFactory;
+		$this->logger = $logger;
+		$this->config = $config;
+	}
+
+	/**
+	 * Convert a number of seconds into the appropriate DateInterval
+	 *
+	 * @param int $expire
+	 * @return \DateInterval
+	 */
+	private function getCutoff($expire) {
+		$d1 = new \DateTime();
+		$d2 = clone $d1;
+		$d2->sub(new \DateInterval('PT' . $expire . 'S'));
+		return $d2->diff($d1);
+	}
+
+	/**
+	 * Return the given subnet for an IPv4 address and mask bits
+	 *
+	 * @param string $ip
+	 * @param int $maskBits
+	 * @return string
+	 */
+	private function getIPv4Subnet($ip,
+								  $maskBits = 32) {
+		$binary = \inet_pton($ip);
+		for ($i = 32; $i > $maskBits; $i -= 8) {
+			$j = \intdiv($i, 8) - 1;
+			$k = (int) \min(8, $i - $maskBits);
+			$mask = (0xff - ((pow(2, $k)) - 1));
+			$int = \unpack('C', $binary[$j]);
+			$binary[$j] = \pack('C', $int[1] & $mask);
+		}
+		return \inet_ntop($binary).'/'.$maskBits;
+	}
+
+	/**
+	 * Return the given subnet for an IPv6 address and mask bits
+	 *
+	 * @param string $ip
+	 * @param int $maskBits
+	 * @return string
+	 */
+	private function getIPv6Subnet($ip, $maskBits = 48) {
+		$binary = \inet_pton($ip);
+		for ($i = 128; $i > $maskBits; $i -= 8) {
+			$j = \intdiv($i, 8) - 1;
+			$k = (int) \min(8, $i - $maskBits);
+			$mask = (0xff - ((pow(2, $k)) - 1));
+			$int = \unpack('C', $binary[$j]);
+			$binary[$j] = \pack('C', $int[1] & $mask);
+		}
+		return \inet_ntop($binary).'/'.$maskBits;
+	}
+
+	/**
+	 * Return the given subnet for an IP and the configured mask bits
+	 *
+	 * Determine if the IP is an IPv4 or IPv6 address, then pass to the correct
+	 * method for handling that specific type.
+	 *
+	 * @param string $ip
+	 * @return string
+	 */
+	private function getSubnet($ip) {
+		if (\preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip)) {
+			return $this->getIPv4Subnet(
+				$ip,
+				32
+			);
+		}
+		return $this->getIPv6Subnet(
+			$ip,
+			128
+		);
+	}
+
+	/**
+	 * Register a failed attempt to bruteforce a security control
+	 *
+	 * @param string $action
+	 * @param string $ip
+	 * @param array $metadata Optional metadata logged to the database
+	 */
+	public function registerAttempt($action,
+									$ip,
+									array $metadata = []) {
+		// No need to log if the bruteforce protection is disabled
+		if($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
+			return;
+		}
+
+		$values = [
+			'action' => $action,
+			'occurred' => $this->timeFactory->getTime(),
+			'ip' => $ip,
+			'subnet' => $this->getSubnet($ip),
+			'metadata' => json_encode($metadata),
+		];
+
+		$this->logger->notice(
+			sprintf(
+				'Bruteforce attempt from "%s" detected for action "%s".',
+				$ip,
+				$action
+			),
+			[
+				'app' => 'core',
+			]
+		);
+
+		$qb = $this->db->getQueryBuilder();
+		$qb->insert('bruteforce_attempts');
+		foreach($values as $column => $value) {
+			$qb->setValue($column, $qb->createNamedParameter($value));
+		}
+		$qb->execute();
+	}
+
+	/**
+	 * Get the throttling delay (in milliseconds)
+	 *
+	 * @param string $ip
+	 * @return int
+	 */
+	public function getDelay($ip) {
+		$cutoffTime = (new \DateTime())
+			->sub($this->getCutoff(43200))
+			->getTimestamp();
+
+		$qb = $this->db->getQueryBuilder();
+		$qb->select('*')
+			->from('bruteforce_attempts')
+			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
+			->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($this->getSubnet($ip))));
+		$attempts = count($qb->execute()->fetchAll());
+
+		if ($attempts === 0) {
+			return 0;
+		}
+
+		$maxDelay = 30;
+		$firstDelay = 0.1;
+		if ($attempts > (8 * PHP_INT_SIZE - 1))  {
+			// Don't ever overflow. Just assume the maxDelay time:s
+			$firstDelay = $maxDelay;
+		} else {
+			$firstDelay *= pow(2, $attempts);
+			if ($firstDelay > $maxDelay) {
+				$firstDelay = $maxDelay;
+			}
+		}
+		return (int) \ceil($firstDelay * 1000);
+	}
+
+	/**
+	 * Will sleep for the defined amount of time
+	 *
+	 * @param string $ip
+	 */
+	public function sleepDelay($ip) {
+		usleep($this->getDelay($ip) * 1000);
+	}
+}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index eb2c26415bc3ea88ddb59c4af44a8a43daf656e0..6ffdeb9211eb5785314137fa942d053bdf51e8df 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -66,6 +66,7 @@ use OC\Lock\NoopLockingProvider;
 use OC\Mail\Mailer;
 use OC\Memcache\ArrayCache;
 use OC\Notification\Manager;
+use OC\Security\Bruteforce\Throttler;
 use OC\Security\CertificateManager;
 use OC\Security\CSP\ContentSecurityPolicyManager;
 use OC\Security\Crypto;
@@ -503,6 +504,14 @@ class Server extends ServerContainer implements IServerContainer {
 		$this->registerService('TrustedDomainHelper', function ($c) {
 			return new TrustedDomainHelper($this->getConfig());
 		});
+		$this->registerService('Throttler', function(Server $c) {
+			return new Throttler(
+				$c->getDatabaseConnection(),
+				new TimeFactory(),
+				$c->getLogger(),
+				$c->getConfig()
+			);
+		});
 		$this->registerService('IntegrityCodeChecker', function (Server $c) {
 			// IConfig and IAppManager requires a working database. This code
 			// might however be called when ownCloud is not yet setup.
@@ -1330,6 +1339,13 @@ class Server extends ServerContainer implements IServerContainer {
 		return $this->query('CsrfTokenManager');
 	}
 
+	/**
+	 * @return Throttler
+	 */
+	public function getBruteForceThrottler() {
+		return $this->query('Throttler');
+	}
+
 	/**
 	 * @return IContentSecurityPolicyManager
 	 */
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index dcc2e66c6c382ba80b0debbd3d61b20e3f91efec..8d12982dd1a664f9d0a3b22c5ace08d97f6d8270 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -95,7 +95,11 @@ class Session implements IUserSession, Emitter {
 	 * @param IProvider $tokenProvider
 	 * @param IConfig $config
 	 */
-	public function __construct(IUserManager $manager, ISession $session, ITimeFactory $timeFacory, $tokenProvider, IConfig $config) {
+	public function __construct(IUserManager $manager,
+								ISession $session,
+								ITimeFactory $timeFacory,
+								$tokenProvider,
+								IConfig $config) {
 		$this->manager = $manager;
 		$this->session = $session;
 		$this->timeFacory = $timeFacory;
@@ -280,7 +284,6 @@ class Session implements IUserSession, Emitter {
 	 */
 	public function login($uid, $password) {
 		$this->session->regenerateId();
-
 		if ($this->validateToken($password, $uid)) {
 			return $this->loginWithToken($password);
 		} else {
@@ -298,11 +301,18 @@ class Session implements IUserSession, Emitter {
 	 * @param string $user
 	 * @param string $password
 	 * @param IRequest $request
+	 * @param OC\Security\Bruteforce\Throttler $throttler
 	 * @throws LoginException
 	 * @throws PasswordLoginForbiddenException
 	 * @return boolean
 	 */
-	public function logClientIn($user, $password, IRequest $request) {
+	public function logClientIn($user,
+								$password,
+								IRequest $request,
+								OC\Security\Bruteforce\Throttler $throttler) {
+		$currentDelay = $throttler->getDelay($request->getRemoteAddress());
+		$throttler->sleepDelay($request->getRemoteAddress());
+
 		$isTokenPassword = $this->isTokenPassword($password);
 		if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
 			throw new PasswordLoginForbiddenException();
@@ -315,6 +325,11 @@ class Session implements IUserSession, Emitter {
 			if (count($users) === 1) {
 				return $this->login($users[0]->getUID(), $password);
 			}
+
+			$throttler->registerAttempt('login', $request->getRemoteAddress(), ['uid' => $user]);
+			if($currentDelay === 0) {
+				$throttler->sleepDelay($request->getRemoteAddress());
+			}
 			return false;
 		}
 
@@ -391,10 +406,11 @@ class Session implements IUserSession, Emitter {
 	 * @param IRequest $request
 	 * @return boolean if the login was successful
 	 */
-	public function tryBasicAuthLogin(IRequest $request) {
+	public function tryBasicAuthLogin(IRequest $request,
+									  OC\Security\Bruteforce\Throttler $throttler) {
 		if (!empty($request->server['PHP_AUTH_USER']) && !empty($request->server['PHP_AUTH_PW'])) {
 			try {
-				if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request)) {
+				if ($this->logClientIn($request->server['PHP_AUTH_USER'], $request->server['PHP_AUTH_PW'], $request, $throttler)) {
 					/**
 					 * Add DAV authenticated. This should in an ideal world not be
 					 * necessary but the iOS App reads cookies from anywhere instead
diff --git a/lib/private/legacy/api.php b/lib/private/legacy/api.php
index 024f3c0fb635eaed44095a83443f41ba695c16e8..88eb7b09a789a5be3a8b51a2f6e419a920fb4ced 100644
--- a/lib/private/legacy/api.php
+++ b/lib/private/legacy/api.php
@@ -364,7 +364,7 @@ class OC_API {
 		try {
 			$loginSuccess = $userSession->tryTokenLogin($request);
 			if (!$loginSuccess) {
-				$loginSuccess = $userSession->tryBasicAuthLogin($request);
+				$loginSuccess = $userSession->tryBasicAuthLogin($request, \OC::$server->getBruteForceThrottler());
 			}
 		} catch (\OC\User\LoginException $e) {
 			return false;
diff --git a/tests/Core/Controller/LoginControllerTest.php b/tests/Core/Controller/LoginControllerTest.php
index d6fa772d38bee577b33692c99dffef72801d77cd..f09f3c9811889713cf99abbf899a0e8409578eba 100644
--- a/tests/Core/Controller/LoginControllerTest.php
+++ b/tests/Core/Controller/LoginControllerTest.php
@@ -23,6 +23,7 @@ namespace Tests\Core\Controller;
 
 use OC\Authentication\TwoFactorAuth\Manager;
 use OC\Core\Controller\LoginController;
+use OC\Security\Bruteforce\Throttler;
 use OCP\AppFramework\Http\RedirectResponse;
 use OCP\AppFramework\Http\TemplateResponse;
 use OCP\IConfig;
@@ -51,6 +52,8 @@ class LoginControllerTest extends TestCase {
 	private $urlGenerator;
 	/** @var Manager | \PHPUnit_Framework_MockObject_MockObject */
 	private $twoFactorManager;
+	/** @var Throttler */
+	private $throttler;
 
 	public function setUp() {
 		parent::setUp();
@@ -65,6 +68,9 @@ class LoginControllerTest extends TestCase {
 		$this->twoFactorManager = $this->getMockBuilder('\OC\Authentication\TwoFactorAuth\Manager')
 			->disableOriginalConstructor()
 			->getMock();
+		$this->throttler = $this->getMockBuilder('\OC\Security\Bruteforce\Throttler')
+			->disableOriginalConstructor()
+			->getMock();
 
 		$this->loginController = new LoginController(
 			'core',
@@ -74,7 +80,8 @@ class LoginControllerTest extends TestCase {
 			$this->session,
 			$this->userSession,
 			$this->urlGenerator,
-			$this->twoFactorManager
+			$this->twoFactorManager,
+			$this->throttler
 		);
 	}
 
@@ -277,10 +284,27 @@ class LoginControllerTest extends TestCase {
 	}
 
 	public function testLoginWithInvalidCredentials() {
-		$user = $this->getMock('\OCP\IUser');
+		$user = 'MyUserName';
 		$password = 'secret';
 		$loginPageUrl = 'some url';
 
+		$this->request
+			->expects($this->exactly(4))
+			->method('getRemoteAddress')
+			->willReturn('192.168.0.1');
+		$this->throttler
+			->expects($this->exactly(2))
+			->method('sleepDelay')
+			->with('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('getDelay')
+			->with('192.168.0.1')
+			->willReturn(0);
+		$this->throttler
+			->expects($this->once())
+			->method('registerAttempt')
+			->with('login', '192.168.0.1', ['user' => 'MyUserName']);
 		$this->userManager->expects($this->once())
 			->method('checkPassword')
 			->will($this->returnValue(false));
@@ -302,6 +326,19 @@ class LoginControllerTest extends TestCase {
 		$password = 'secret';
 		$indexPageUrl = 'some url';
 
+		$this->request
+			->expects($this->exactly(2))
+			->method('getRemoteAddress')
+			->willReturn('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('sleepDelay')
+			->with('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('getDelay')
+			->with('192.168.0.1')
+			->willReturn(200);
 		$this->userManager->expects($this->once())
 			->method('checkPassword')
 			->will($this->returnValue($user));
@@ -334,6 +371,19 @@ class LoginControllerTest extends TestCase {
 		$originalUrl = 'another%20url';
 		$redirectUrl = 'http://localhost/another url';
 
+		$this->request
+			->expects($this->exactly(2))
+			->method('getRemoteAddress')
+			->willReturn('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('sleepDelay')
+			->with('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('getDelay')
+			->with('192.168.0.1')
+			->willReturn(200);
 		$this->userManager->expects($this->once())
 			->method('checkPassword')
 			->with('Jane', $password)
@@ -363,6 +413,19 @@ class LoginControllerTest extends TestCase {
 		$password = 'secret';
 		$challengeUrl = 'challenge/url';
 
+		$this->request
+			->expects($this->exactly(2))
+			->method('getRemoteAddress')
+			->willReturn('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('sleepDelay')
+			->with('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('getDelay')
+			->with('192.168.0.1')
+			->willReturn(200);
 		$this->userManager->expects($this->once())
 			->method('checkPassword')
 			->will($this->returnValue($user));
@@ -412,6 +475,23 @@ class LoginControllerTest extends TestCase {
 			->method('linkToRoute')
 			->with('core.login.showLoginForm', ['user' => 'john@doe.com'])
 			->will($this->returnValue(''));
+		$this->request
+			->expects($this->exactly(3))
+			->method('getRemoteAddress')
+			->willReturn('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('getDelay')
+			->with('192.168.0.1')
+			->willReturn(200);
+		$this->throttler
+			->expects($this->once())
+			->method('sleepDelay')
+			->with('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('registerAttempt')
+			->with('login', '192.168.0.1', ['user' => 'john@doe.com']);
 
 		$expected = new RedirectResponse('');
 		$this->assertEquals($expected, $this->loginController->tryLogin('john@doe.com', 'just wrong', null));
diff --git a/tests/lib/AppFramework/DependencyInjection/DIContainerTest.php b/tests/lib/AppFramework/DependencyInjection/DIContainerTest.php
index 0edf96dd5a4bd1647207c0a47b6b4f13e27eb24c..2e450d897bd112624b4d877ed08c18f8155af4ea 100644
--- a/tests/lib/AppFramework/DependencyInjection/DIContainerTest.php
+++ b/tests/lib/AppFramework/DependencyInjection/DIContainerTest.php
@@ -29,6 +29,9 @@ namespace Test\AppFramework\DependencyInjection;
 
 use \OC\AppFramework\Http\Request;
 
+/**
+ * @group DB
+ */
 class DIContainerTest extends \Test\TestCase {
 
 	private $container;
@@ -74,7 +77,6 @@ class DIContainerTest extends \Test\TestCase {
 		$this->assertEquals('name', $this->container['AppName']);
 	}
 
-
 	public function testMiddlewareDispatcherIncludesSecurityMiddleware(){
 		$this->container['Request'] = new Request(
 			['method' => 'GET'],
diff --git a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php
index a0dbcc6872a266129ab7adc6219a37113d032c14..d0096d43f3dccf6f9a8680464582efb413a40d7c 100644
--- a/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php
+++ b/tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php
@@ -16,6 +16,7 @@ use OC\AppFramework\Http\Request;
 use OC\AppFramework\Middleware\Security\CORSMiddleware;
 use OC\AppFramework\Utility\ControllerMethodReflector;
 use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
+use OC\Security\Bruteforce\Throttler;
 use OCP\AppFramework\Http\JSONResponse;
 use OCP\AppFramework\Http\Response;
 
@@ -24,6 +25,8 @@ class CORSMiddlewareTest extends \Test\TestCase {
 
 	private $reflector;
 	private $session;
+	/** @var Throttler */
+	private $throttler;
 
 	protected function setUp() {
 		parent::setUp();
@@ -31,6 +34,9 @@ class CORSMiddlewareTest extends \Test\TestCase {
 		$this->session = $this->getMockBuilder('\OC\User\Session')
 			->disableOriginalConstructor()
 			->getMock();
+		$this->throttler =  $this->getMockBuilder('\OC\Security\Bruteforce\Throttler')
+			->disableOriginalConstructor()
+			->getMock();
 	}
 
 	/**
@@ -47,7 +53,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			$this->getMockBuilder('\OCP\IConfig')->getMock()
 		);
 		$this->reflector->reflect($this, __FUNCTION__);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 
 		$response = $middleware->afterController($this, __FUNCTION__, new Response());
 		$headers = $response->getHeaders();
@@ -65,7 +71,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			$this->getMockBuilder('\OCP\Security\ISecureRandom')->getMock(),
 			$this->getMockBuilder('\OCP\IConfig')->getMock()
 		);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 
 		$response = $middleware->afterController($this, __FUNCTION__, new Response());
 		$headers = $response->getHeaders();
@@ -83,7 +89,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			$this->getMockBuilder('\OCP\IConfig')->getMock()
 		);
 		$this->reflector->reflect($this, __FUNCTION__);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 
 		$response = $middleware->afterController($this, __FUNCTION__, new Response());
 		$headers = $response->getHeaders();
@@ -106,7 +112,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			$this->getMockBuilder('\OCP\IConfig')->getMock()
 		);
 		$this->reflector->reflect($this, __FUNCTION__);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 
 		$response = new Response();
 		$response->addHeader('AcCess-control-Allow-Credentials ', 'TRUE');
@@ -124,7 +130,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			$this->getMockBuilder('\OCP\IConfig')->getMock()
 		);
 		$this->reflector->reflect($this, __FUNCTION__);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 		$this->session->expects($this->never())
 			->method('logout');
 		$this->session->expects($this->never())
@@ -155,7 +161,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			->with($this->equalTo('user'), $this->equalTo('pass'))
 			->will($this->returnValue(true));
 		$this->reflector->reflect($this, __FUNCTION__);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 
 		$middleware->beforeController($this, __FUNCTION__, new Response());
 	}
@@ -180,7 +186,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			->with($this->equalTo('user'), $this->equalTo('pass'))
 			->will($this->throwException(new \OC\Authentication\Exceptions\PasswordLoginForbiddenException));
 		$this->reflector->reflect($this, __FUNCTION__);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 
 		$middleware->beforeController($this, __FUNCTION__, new Response());
 	}
@@ -205,7 +211,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			->with($this->equalTo('user'), $this->equalTo('pass'))
 			->will($this->returnValue(false));
 		$this->reflector->reflect($this, __FUNCTION__);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 
 		$middleware->beforeController($this, __FUNCTION__, new Response());
 	}
@@ -219,7 +225,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			$this->getMockBuilder('\OCP\Security\ISecureRandom')->getMock(),
 			$this->getMockBuilder('\OCP\IConfig')->getMock()
 		);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 		$response = $middleware->afterException($this, __FUNCTION__, new SecurityException('A security exception'));
 
 		$expected = new JSONResponse(['message' => 'A security exception'], 500);
@@ -235,7 +241,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			$this->getMockBuilder('\OCP\Security\ISecureRandom')->getMock(),
 			$this->getMockBuilder('\OCP\IConfig')->getMock()
 		);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 		$response = $middleware->afterException($this, __FUNCTION__, new SecurityException('A security exception', 501));
 
 		$expected = new JSONResponse(['message' => 'A security exception'], 501);
@@ -255,7 +261,7 @@ class CORSMiddlewareTest extends \Test\TestCase {
 			$this->getMockBuilder('\OCP\Security\ISecureRandom')->getMock(),
 			$this->getMockBuilder('\OCP\IConfig')->getMock()
 		);
-		$middleware = new CORSMiddleware($request, $this->reflector, $this->session);
+		$middleware = new CORSMiddleware($request, $this->reflector, $this->session, $this->throttler);
 		$middleware->afterException($this, __FUNCTION__, new \Exception('A regular exception'));
 	}
 
diff --git a/tests/lib/Security/Bruteforce/ThrottlerTest.php b/tests/lib/Security/Bruteforce/ThrottlerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9b7a47ceec89b79ccd866c3d1f75a2a5e7cb8d2e
--- /dev/null
+++ b/tests/lib/Security/Bruteforce/ThrottlerTest.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 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\Bruteforce;
+
+use OC\AppFramework\Utility\TimeFactory;
+use OC\Security\Bruteforce\Throttler;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\ILogger;
+use Test\TestCase;
+
+/**
+ * Based on the unit tests from Paragonie's Airship CMS
+ * Ref: https://github.com/paragonie/airship/blob/7e5bad7e3c0fbbf324c11f963fd1f80e59762606/test/unit/Engine/Security/AirBrakeTest.php
+ *
+ * @package Test\Security\Bruteforce
+ */
+class ThrottlerTest extends TestCase {
+	/** @var Throttler */
+	private $throttler;
+	/** @var IDBConnection */
+	private $dbConnection;
+	/** @var ILogger */
+	private $logger;
+	/** @var IConfig */
+	private $config;
+
+	public function setUp() {
+		$this->dbConnection = $this->getMock('\OCP\IDBConnection');
+		$this->logger = $this->getMock('\OCP\ILogger');
+		$this->config = $this->getMock('\OCP\IConfig');
+
+		$this->throttler = new Throttler(
+			$this->dbConnection,
+			new TimeFactory(),
+			$this->logger,
+			$this->config
+		);
+		return parent::setUp();
+	}
+
+	public function testCutoff() {
+		// precisely 31 second shy of 12 hours
+		$cutoff = $this->invokePrivate($this->throttler, 'getCutoff', [43169]);
+		$this->assertSame(0, $cutoff->y);
+		$this->assertSame(0, $cutoff->m);
+		$this->assertSame(0, $cutoff->d);
+		$this->assertSame(11, $cutoff->h);
+		$this->assertSame(59, $cutoff->i);
+		$this->assertSame(29, $cutoff->s);
+		$cutoff = $this->invokePrivate($this->throttler, 'getCutoff', [86401]);
+		$this->assertSame(0, $cutoff->y);
+		$this->assertSame(0, $cutoff->m);
+		$this->assertSame(1, $cutoff->d);
+		$this->assertSame(0, $cutoff->h);
+		$this->assertSame(0, $cutoff->i);
+		// Leap second tolerance:
+		$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])
+		);
+	}
+}
diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php
index 9bde2c664b6015814463e38318c1ad20a904ba17..379c7e39442f89c4bfe8ca92c71780aaac5558a5 100644
--- a/tests/lib/User/SessionTest.php
+++ b/tests/lib/User/SessionTest.php
@@ -9,6 +9,7 @@
 
 namespace Test\User;
 
+use OC\Security\Bruteforce\Throttler;
 use OC\Session\Memory;
 use OC\User\User;
 
@@ -17,15 +18,14 @@ use OC\User\User;
  * @package Test\User
  */
 class SessionTest extends \Test\TestCase {
-
 	/** @var \OCP\AppFramework\Utility\ITimeFactory */
 	private $timeFactory;
-
 	/** @var \OC\Authentication\Token\DefaultTokenProvider */
 	protected $tokenProvider;
-
 	/** @var \OCP\IConfig */
 	private $config;
+	/** @var Throttler */
+	private $throttler;
 
 	protected function setUp() {
 		parent::setUp();
@@ -36,6 +36,8 @@ class SessionTest extends \Test\TestCase {
 			->will($this->returnValue(10000));
 		$this->tokenProvider = $this->getMock('\OC\Authentication\Token\IProvider');
 		$this->config = $this->getMock('\OCP\IConfig');
+		$this->throttler = $this->getMockBuilder('\OC\Security\Bruteforce\Throttler')
+			->disableOriginalConstructor()->getMock();
 	}
 
 	public function testGetUser() {
@@ -353,7 +355,6 @@ class SessionTest extends \Test\TestCase {
 			->getMock();
 		$session = $this->getMock('\OCP\ISession');
 		$request = $this->getMock('\OCP\IRequest');
-		$user = $this->getMock('\OCP\IUser');
 
 		/** @var \OC\User\Session $userSession */
 		$userSession = $this->getMockBuilder('\OC\User\Session')
@@ -369,8 +370,21 @@ class SessionTest extends \Test\TestCase {
 			->method('getSystemValue')
 			->with('token_auth_enforced', false)
 			->will($this->returnValue(true));
-
-		$userSession->logClientIn('john', 'doe', $request);
+		$request
+			->expects($this->exactly(2))
+			->method('getRemoteAddress')
+			->willReturn('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('sleepDelay')
+			->with('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('getDelay')
+			->with('192.168.0.1')
+			->willReturn(0);
+
+		$userSession->logClientIn('john', 'doe', $request, $this->throttler);
 	}
 
 	public function testLogClientInWithTokenPassword() {
@@ -379,7 +393,6 @@ class SessionTest extends \Test\TestCase {
 			->getMock();
 		$session = $this->getMock('\OCP\ISession');
 		$request = $this->getMock('\OCP\IRequest');
-		$user = $this->getMock('\OCP\IUser');
 
 		/** @var \OC\User\Session $userSession */
 		$userSession = $this->getMockBuilder('\OC\User\Session')
@@ -398,8 +411,21 @@ class SessionTest extends \Test\TestCase {
 		$session->expects($this->once())
 			->method('set')
 			->with('app_password', 'I-AM-AN-APP-PASSWORD');
-
-		$this->assertTrue($userSession->logClientIn('john', 'I-AM-AN-APP-PASSWORD', $request));
+		$request
+			->expects($this->exactly(2))
+			->method('getRemoteAddress')
+			->willReturn('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('sleepDelay')
+			->with('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('getDelay')
+			->with('192.168.0.1')
+			->willReturn(0);
+
+		$this->assertTrue($userSession->logClientIn('john', 'I-AM-AN-APP-PASSWORD', $request, $this->throttler));
 	}
 
 	/**
@@ -410,7 +436,6 @@ class SessionTest extends \Test\TestCase {
 			->disableOriginalConstructor()
 			->getMock();
 		$session = $this->getMock('\OCP\ISession');
-		$user = $this->getMock('\OCP\IUser');
 		$request = $this->getMock('\OCP\IRequest');
 
 		/** @var \OC\User\Session $userSession */
@@ -433,7 +458,21 @@ class SessionTest extends \Test\TestCase {
 			->with('john')
 			->will($this->returnValue(true));
 
-		$userSession->logClientIn('john', 'doe', $request);
+		$request
+			->expects($this->exactly(2))
+			->method('getRemoteAddress')
+			->willReturn('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('sleepDelay')
+			->with('192.168.0.1');
+		$this->throttler
+			->expects($this->once())
+			->method('getDelay')
+			->with('192.168.0.1')
+			->willReturn(0);
+
+		$userSession->logClientIn('john', 'doe', $request, $this->throttler);
 	}
 
 	public function testRememberLoginValidToken() {
diff --git a/version.php b/version.php
index 15400c4f4e5509f256a8c691e8e6df1505e151d8..5f88d29b155162082f07ebdad1db1e4b8be56f25 100644
--- a/version.php
+++ b/version.php
@@ -25,7 +25,7 @@
 // We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
 // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
 // when updating major/minor version number.
-$OC_Version = array(9, 1, 0, 12);
+$OC_Version = array(9, 1, 0, 13);
 
 // The human readable string
 $OC_VersionString = '9.1.0 RC1';