Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Giehl <stefan@matomo.org>2019-07-17 07:33:11 +0300
committerThomas Steur <tsteur@users.noreply.github.com>2019-07-17 07:33:11 +0300
commitffd09877f0d9ecd56f4baf436979272cba9abbc9 (patch)
tree7290868f8f3880a8602c6a25ce758ae8ed7488e3 /plugins/Login
parent4506fb9110d920104ab6d4251454b63a3812816a (diff)
Implements a rate limit for password resets (#13934)
* Implements a rate limit for password resets * improve error message * allow up to three requests within one hour * adds some tests
Diffstat (limited to 'plugins/Login')
-rw-r--r--plugins/Login/PasswordResetter.php26
-rw-r--r--plugins/Login/lang/en.json1
-rw-r--r--plugins/Login/tests/Integration/PasswordResetterTest.php57
3 files changed, 83 insertions, 1 deletions
diff --git a/plugins/Login/PasswordResetter.php b/plugins/Login/PasswordResetter.php
index f9bd72c476..f1e73cfeee 100644
--- a/plugins/Login/PasswordResetter.php
+++ b/plugins/Login/PasswordResetter.php
@@ -457,13 +457,37 @@ class PasswordResetter
* @param string $login The user login for whom a password change was requested.
* @param string $newPassword The new password to set.
* @param string $keySuffix The suffix used in generating a token.
+ *
+ * @throws Exception if a password reset was already requested within one hour
*/
private function savePasswordResetInfo($login, $newPassword, $keySuffix)
{
- $optionName = $this->getPasswordResetInfoOptionName($login);
+ $optionName = self::getPasswordResetInfoOptionName($login);
+
+ $existingResetInfo = Option::get($optionName);
+
+ $time = time();
+ $count = 0;
+
+ if ($existingResetInfo) {
+ $existingResetInfo = json_decode($existingResetInfo, true);
+
+ if (isset($existingResetInfo['timestamp']) && $existingResetInfo['timestamp'] > time()-3600) {
+ $time = $existingResetInfo['timestamp'];
+ $count = !empty($existingResetInfo['requests']) ? $existingResetInfo['requests'] : $count;
+
+ if(isset($existingResetInfo['requests']) && $existingResetInfo['requests'] > 2) {
+ throw new Exception(Piwik::translate('Login_PasswordResetAlreadySent'));
+ }
+ }
+ }
+
+
$optionData = [
'hash' => $this->passwordHelper->hash(UsersManager::getPasswordHash($newPassword)),
'keySuffix' => $keySuffix,
+ 'timestamp' => $time,
+ 'requests' => $count+1
];
$optionData = json_encode($optionData);
diff --git a/plugins/Login/lang/en.json b/plugins/Login/lang/en.json
index e37014a0fd..939941328e 100644
--- a/plugins/Login/lang/en.json
+++ b/plugins/Login/lang/en.json
@@ -36,6 +36,7 @@
"PasswordChanged": "Your password has been changed.",
"PasswordRepeat": "Password (repeat)",
"PasswordsDoNotMatch": "Passwords do not match.",
+ "PasswordResetAlreadySent": "You have requested too many password resets shortly. A new request can be made in one hour. If you have problems resetting your password, please contact you administrator for help.",
"WrongPasswordEntered": "Please enter your correct password.",
"ConfirmPasswordToContinue": "Confirm your password to continue",
"PluginDescription": "Provides authentication via username and password as well as password reset functionality. Authentication method can be changed by using another Login plugin such as LoginLdap available on the Marketplace.",
diff --git a/plugins/Login/tests/Integration/PasswordResetterTest.php b/plugins/Login/tests/Integration/PasswordResetterTest.php
index f168b15663..2b0af6ac70 100644
--- a/plugins/Login/tests/Integration/PasswordResetterTest.php
+++ b/plugins/Login/tests/Integration/PasswordResetterTest.php
@@ -13,6 +13,7 @@ use Piwik\Access;
use Piwik\Auth;
use Piwik\Container\StaticContainer;
use Piwik\Mail;
+use Piwik\Option;
use Piwik\Plugin\Manager;
use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
use Piwik\Plugins\Login\PasswordResetter;
@@ -67,6 +68,62 @@ class PasswordResetterTest extends IntegrationTestCase
$this->checkPasswordIs(self::NEWPASSWORD);
}
+ public function tests_passwordReset_worksUpToThreeTimesInAnHour()
+ {
+ $this->passwordResetter->initiatePasswordResetProcess('superUserLogin', self::NEWPASSWORD);
+
+ $this->assertNotEmpty($this->capturedToken);
+
+ $token = $this->capturedToken;
+ $this->passwordResetter->initiatePasswordResetProcess('superUserLogin', self::NEWPASSWORD);
+ $this->assertNotEquals($token, $this->capturedToken);
+
+ $token = $this->capturedToken;
+ $this->passwordResetter->initiatePasswordResetProcess('superUserLogin', self::NEWPASSWORD);
+ $this->assertNotEquals($token, $this->capturedToken);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage You have requested too many password resets shortly. A new request can be made in one hour. If you have problems resetting your password, please contact you administrator for help.
+ */
+ public function test_passwordReset_notAllowedMoreThanThreeTimesInAnHour()
+ {
+ $this->passwordResetter->initiatePasswordResetProcess('superUserLogin', self::NEWPASSWORD);
+
+ $this->assertNotEmpty($this->capturedToken);
+
+ $token = $this->capturedToken;
+ $this->passwordResetter->initiatePasswordResetProcess('superUserLogin', self::NEWPASSWORD);
+ $this->assertNotEquals($token, $this->capturedToken);
+
+ $token = $this->capturedToken;
+ $this->passwordResetter->initiatePasswordResetProcess('superUserLogin', self::NEWPASSWORD);
+ $this->assertNotEquals($token, $this->capturedToken);
+
+ $this->passwordResetter->initiatePasswordResetProcess('superUserLogin', self::NEWPASSWORD);
+ }
+
+ public function test_passwordReset_newRequestAllowedAfterAnHour()
+ {
+ $this->passwordResetter->initiatePasswordResetProcess('superUserLogin', self::NEWPASSWORD);
+
+ $optionName = $this->passwordResetter->getPasswordResetInfoOptionName('superUserLogin');
+ $data = json_decode(Option::get($optionName), true);
+
+ $data['timestamp'] = time()-3601;
+ $data['requests'] = 3;
+
+ Option::set($optionName, json_encode($data));
+
+ $this->passwordResetter->initiatePasswordResetProcess('superUserLogin', self::NEWPASSWORD);
+
+ $optionName = $this->passwordResetter->getPasswordResetInfoOptionName('superUserLogin');
+ $data = json_decode(Option::get($optionName), true);
+
+ $this->assertEquals(1, $data['requests']);
+ }
+
/**
* @expectedException \Exception
* @expectedExceptionMessage Token is invalid or has expired