Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Send "429 Too Many Requests" in case of brute force protection
Signed-off-by: Joas Schilling <[email protected]>
  • Loading branch information
nickvergessen committed Aug 19, 2020
commit e66bc4a8a74ad6071569ea707e986a0e21aca66d
4 changes: 4 additions & 0 deletions core/templates/429.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="body-login-container update">
<h2><?php p($l->t('Too many requests')); ?></h2>
<p class="infogroup"><?php p($l->t('There were too many requests from your network. Retry later or contact your administrator if this is an error.')); ?></p>
</div>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2017 Lukas Reschke <[email protected]>
*
Expand Down Expand Up @@ -26,9 +27,15 @@

use OC\AppFramework\Utility\ControllerMethodReflector;
use OC\Security\Bruteforce\Throttler;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TooManyRequestsResponse;
use OCP\AppFramework\Middleware;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\Security\Bruteforce\MaxDelayReached;

/**
* Class BruteForceMiddleware performs the bruteforce protection for controllers
Expand Down Expand Up @@ -66,7 +73,7 @@ public function beforeController($controller, $methodName) {

if ($this->reflector->hasAnnotation('BruteForceProtection')) {
$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
$this->throttler->sleepDelay($this->request->getRemoteAddress(), $action);
$this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action);
}
}

Expand All @@ -83,4 +90,23 @@ public function afterController($controller, $methodName, Response $response) {

return parent::afterController($controller, $methodName, $response);
}

/**
* @param Controller $controller
* @param string $methodName
* @param \Exception $exception
* @throws \Exception
* @return Response
*/
public function afterException($controller, $methodName, \Exception $exception): Response {
if ($exception instanceof MaxDelayReached) {
if ($controller instanceof OCSController) {
throw new OCSException($exception->getMessage(), Http::STATUS_TOO_MANY_REQUESTS);
}

return new TooManyRequestsResponse();
}

throw $exception;
}
}
22 changes: 21 additions & 1 deletion lib/private/Security/Bruteforce/Throttler.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\ILogger;
use OCP\Security\Bruteforce\MaxDelayReached;

/**
* Class Throttler implements the bruteforce protection for security actions in
Expand All @@ -50,6 +51,7 @@
*/
class Throttler {
public const LOGIN_ACTION = 'login';
public const MAX_DELAY = 25;

/** @var IDBConnection */
private $db;
Expand Down Expand Up @@ -241,7 +243,7 @@ public function getDelay($ip, $action = '') {
return 0;
}

$maxDelay = 25;
$maxDelay = self::MAX_DELAY;
$firstDelay = 0.1;
if ($attempts > (8 * PHP_INT_SIZE - 1)) {
// Don't ever overflow. Just assume the maxDelay time:s
Expand Down Expand Up @@ -308,4 +310,22 @@ public function sleepDelay($ip, $action = '') {
usleep($delay * 1000);
return $delay;
}

/**
* Will sleep for the defined amount of time unless maximum is reached
* In case of maximum a "429 Too Many Request" response is thrown
*
* @param string $ip
* @param string $action optionally filter by action
* @return int the time spent sleeping
* @throws MaxDelayReached when reached the maximum
*/
public function sleepDelayOrThrowOnMax($ip, $action = '') {
$delay = $this->getDelay($ip, $action);
if ($delay === self::MAX_DELAY * 1000) {
throw new MaxDelayReached();
}
usleep($delay * 1000);
return $delay;
}
}
51 changes: 51 additions & 0 deletions lib/public/AppFramework/Http/TooManyRequestsResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Joas Schilling <[email protected]>
*
* @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 OCP\AppFramework\Http;

use OCP\Template;

/**
* A generic 429 response showing an 404 error page as well to the end-user
* @since 19.0.0
*/
class TooManyRequestsResponse extends Response {

/**
* @since 19.0.0
*/
public function __construct() {
parent::__construct();

$this->setContentSecurityPolicy(new ContentSecurityPolicy());
$this->setStatus(429);
}

/**
* @return string
* @since 19.0.0
*/
public function render() {
$template = new Template('core', '429', 'blank');
return $template->fetchPage();
}
}
30 changes: 30 additions & 0 deletions lib/public/Security/Bruteforce/MaxDelayReached.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Joas Schilling <[email protected]>
*
* @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 OCP\Security\Bruteforce;

/**
* Class MaxDelayReached
* @since 19.0
*/
class MaxDelayReached extends \RuntimeException {
}