diff --git a/core/Controller/LostController.php b/core/Controller/LostController.php index 87a629b9ee807..00a37ce01f4c5 100644 --- a/core/Controller/LostController.php +++ b/core/Controller/LostController.php @@ -37,6 +37,8 @@ use OC\Authentication\TwoFactorAuth\Manager; use OC\Core\Exception\ResetPasswordException; +use OC\Security\RateLimiting\Exception\RateLimitExceededException; +use OC\Security\RateLimiting\Limiter; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\TemplateResponse; @@ -91,6 +93,8 @@ class LostController extends Controller { private $initialStateService; /** @var IVerificationToken */ private $verificationToken; + /** @var Limiter */ + private $limiter; public function __construct( $appName, @@ -106,7 +110,8 @@ public function __construct( ILogger $logger, Manager $twoFactorManager, IInitialStateService $initialStateService, - IVerificationToken $verificationToken + IVerificationToken $verificationToken, + Limiter $limiter ) { parent::__construct($appName, $request); $this->urlGenerator = $urlGenerator; @@ -121,6 +126,7 @@ public function __construct( $this->twoFactorManager = $twoFactorManager; $this->initialStateService = $initialStateService; $this->verificationToken = $verificationToken; + $this->limiter = $limiter; } /** @@ -294,6 +300,12 @@ protected function sendEmail($input) { throw new ResetPasswordException('Could not send reset e-mail since there is no email for username ' . $input); } + try { + $this->limiter->registerUserRequest('lostpasswordemail', 5, 1800, $user); + } catch (RateLimitExceededException $e) { + throw new ResetPasswordException('Could not send reset e-mail, 5 of them were already sent in the last 30 minutes', 0, $e); + } + // Generate the token. It is stored encrypted in the database with the // secret being the users' email address appended with the system secret. // This makes the token automatically invalidate once the user changes diff --git a/lib/private/Security/RateLimiting/Limiter.php b/lib/private/Security/RateLimiting/Limiter.php index 91657452d9916..7848a5b75a708 100644 --- a/lib/private/Security/RateLimiting/Limiter.php +++ b/lib/private/Security/RateLimiting/Limiter.php @@ -45,7 +45,7 @@ public function __construct(IBackend $backend) { /** * @param string $methodIdentifier * @param string $userIdentifier - * @param int $period + * @param int $period in seconds * @param int $limit * @throws RateLimitExceededException */ @@ -66,7 +66,7 @@ private function register(string $methodIdentifier, * * @param string $identifier * @param int $anonLimit - * @param int $anonPeriod + * @param int $anonPeriod in seconds * @param string $ip * @throws RateLimitExceededException */ @@ -85,7 +85,7 @@ public function registerAnonRequest(string $identifier, * * @param string $identifier * @param int $userLimit - * @param int $userPeriod + * @param int $userPeriod in seconds * @param IUser $user * @throws RateLimitExceededException */ diff --git a/tests/Core/Controller/LostControllerTest.php b/tests/Core/Controller/LostControllerTest.php index e860c8080146a..b43f176466246 100644 --- a/tests/Core/Controller/LostControllerTest.php +++ b/tests/Core/Controller/LostControllerTest.php @@ -24,6 +24,7 @@ use OC\Authentication\TwoFactorAuth\Manager; use OC\Core\Controller\LostController; use OC\Mail\Message; +use OC\Security\RateLimiting\Limiter; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\TemplateResponse; use OCP\Defaults; @@ -41,6 +42,7 @@ use OCP\Mail\IMailer; use OCP\Security\VerificationToken\InvalidTokenException; use OCP\Security\VerificationToken\IVerificationToken; +use PHPUnit\Framework\MockObject\MockObject; /** * Class LostControllerTest @@ -48,7 +50,6 @@ * @package OC\Core\Controller */ class LostControllerTest extends \Test\TestCase { - /** @var LostController */ private $lostController; /** @var IUser */ @@ -77,6 +78,8 @@ class LostControllerTest extends \Test\TestCase { private $initialStateService; /** @var IVerificationToken|\PHPUnit\Framework\MockObject\MockObject */ private $verificationToken; + /** @var Limiter|MockObject */ + private $limiter; protected function setUp(): void { parent::setUp(); @@ -129,6 +132,7 @@ protected function setUp(): void { $this->twofactorManager = $this->createMock(Manager::class); $this->initialStateService = $this->createMock(IInitialStateService::class); $this->verificationToken = $this->createMock(IVerificationToken::class); + $this->limiter = $this->createMock(Limiter::class); $this->lostController = new LostController( 'Core', $this->request, @@ -143,7 +147,8 @@ protected function setUp(): void { $this->logger, $this->twofactorManager, $this->initialStateService, - $this->verificationToken + $this->verificationToken, + $this->limiter ); }