Skip to content
Snippets Groups Projects
Unverified Commit e66bc4a8 authored by Joas Schilling's avatar Joas Schilling
Browse files

Send "429 Too Many Requests" in case of brute force protection

parent 4ff492a4
No related branches found
No related tags found
No related merge requests found
<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>
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
*
......@@ -26,9 +27,15 @@ namespace OC\AppFramework\Middleware\Security;
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
......@@ -66,7 +73,7 @@ class BruteForceMiddleware extends Middleware {
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);
}
}
......@@ -83,4 +90,23 @@ class BruteForceMiddleware extends Middleware {
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;
}
}
......@@ -34,6 +34,7 @@ use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\ILogger;
use OCP\Security\Bruteforce\MaxDelayReached;
/**
* Class Throttler implements the bruteforce protection for security actions in
......@@ -50,6 +51,7 @@ use OCP\ILogger;
*/
class Throttler {
public const LOGIN_ACTION = 'login';
public const MAX_DELAY = 25;
/** @var IDBConnection */
private $db;
......@@ -241,7 +243,7 @@ class Throttler {
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
......@@ -308,4 +310,22 @@ class Throttler {
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;
}
}
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
*
* @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();
}
}
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
*
* @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 {
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment