diff --git a/plugins/change-password/index.php b/plugins/change-password/index.php index 26805067a..d0d49bab7 100644 --- a/plugins/change-password/index.php +++ b/plugins/change-password/index.php @@ -7,9 +7,9 @@ class ChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin { const NAME = 'Change Password', - VERSION = '2.37', - RELEASE = '2024-03-29', - REQUIRED = '2.36.0', + VERSION = '2.38', + RELEASE = '2024-04-22', + REQUIRED = '2.36.1', CATEGORY = 'Security', DESCRIPTION = 'Extension to allow users to change their passwords'; @@ -18,7 +18,8 @@ class ChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin CouldNotSaveNewPassword = 130, CurrentPasswordIncorrect = 131, NewPasswordShort = 132, - NewPasswordWeak = 133; + NewPasswordWeak = 133, + NewPasswordHibp = 134; public function Init() : void { @@ -103,18 +104,23 @@ class ChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin public function configMapping() : array { $result = [ - \RainLoop\Plugins\Property::NewInstance("pass_min_length") + \RainLoop\Plugins\Property::NewInstance('pass_min_length') ->SetLabel('Password minimum length') ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) ->SetDescription('Minimum length of the password') ->SetDefaultValue(10) ->SetAllowedInJs(true), - \RainLoop\Plugins\Property::NewInstance("pass_min_strength") + \RainLoop\Plugins\Property::NewInstance('pass_min_strength') ->SetLabel('Password minimum strength') ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) ->SetDescription('Minimum strength of the password in %') ->SetDefaultValue(70) ->SetAllowedInJs(true), + \RainLoop\Plugins\Property::NewInstance('check_hibp') + ->SetLabel('Check Have I Been Pwned') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) + ->SetDescription('Check if new passphrase is in a data breach') + ->SetDefaultValue(false), ]; foreach ($this->getSupportedDrivers(true) as $name => $class) { $group = new \RainLoop\Plugins\PropertyCollection($name); @@ -160,6 +166,9 @@ class ChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin throw new ClientException(static::NewPasswordWeak, null, $oActions->StaticI18N('NOTIFICATIONS/NEW_PASSWORD_WEAK')); } $oNewPassword = new \SnappyMail\SensitiveString($sNewPassword); + if ($this->Config()->Get('plugin', 'check_hibp', false) && \SnappyMail\Hibp::password($oNewPassword)) { + throw new ClientException(static::NewPasswordHibp, null, $oActions->StaticI18N('NOTIFICATIONS/NEW_PASSWORD_HIBP')); + } $bResult = false; $oConfig = $this->Config(); diff --git a/plugins/change-password/langs/de.ini b/plugins/change-password/langs/de.ini index 583cd5783..71360c000 100644 --- a/plugins/change-password/langs/de.ini +++ b/plugins/change-password/langs/de.ini @@ -10,3 +10,4 @@ CURRENT_PASSWORD_INCORRECT = "Aktuelles Passwort falsch" CURRENT_PASSWORD_INCORRECT = "Aktuelles Passwort falsch" NEW_PASSWORD_SHORT = "Passwort ist zu kurz" NEW_PASSWORD_WEAK = "Passwort ist zu einfach" +NEW_PASSWORD_HIBP = "Passwort gefunden in Have I Been Pwned" diff --git a/plugins/change-password/langs/en.ini b/plugins/change-password/langs/en.ini index 8d3db7374..4a4a6ea12 100644 --- a/plugins/change-password/langs/en.ini +++ b/plugins/change-password/langs/en.ini @@ -10,3 +10,4 @@ COULD_NOT_SAVE_NEW_PASSWORD = "Could not save new password" CURRENT_PASSWORD_INCORRECT = "Current password incorrect" NEW_PASSWORD_SHORT = "Password is too short" NEW_PASSWORD_WEAK = "Password is too easy" +NEW_PASSWORD_HIBP = "Password found in Have I Been Pwned" diff --git a/plugins/change-password/langs/en_GB.ini b/plugins/change-password/langs/en_GB.ini index 8d3db7374..4a4a6ea12 100644 --- a/plugins/change-password/langs/en_GB.ini +++ b/plugins/change-password/langs/en_GB.ini @@ -10,3 +10,4 @@ COULD_NOT_SAVE_NEW_PASSWORD = "Could not save new password" CURRENT_PASSWORD_INCORRECT = "Current password incorrect" NEW_PASSWORD_SHORT = "Password is too short" NEW_PASSWORD_WEAK = "Password is too easy" +NEW_PASSWORD_HIBP = "Password found in Have I Been Pwned" diff --git a/plugins/change-password/langs/en_US.ini b/plugins/change-password/langs/en_US.ini index 8d3db7374..4a4a6ea12 100644 --- a/plugins/change-password/langs/en_US.ini +++ b/plugins/change-password/langs/en_US.ini @@ -10,3 +10,4 @@ COULD_NOT_SAVE_NEW_PASSWORD = "Could not save new password" CURRENT_PASSWORD_INCORRECT = "Current password incorrect" NEW_PASSWORD_SHORT = "Password is too short" NEW_PASSWORD_WEAK = "Password is too easy" +NEW_PASSWORD_HIBP = "Password found in Have I Been Pwned" diff --git a/plugins/change-password/langs/es.ini b/plugins/change-password/langs/es.ini index f545a2505..47bc3ecee 100644 --- a/plugins/change-password/langs/es.ini +++ b/plugins/change-password/langs/es.ini @@ -10,3 +10,4 @@ COULD_NOT_SAVE_NEW_PASSWORD = "No se puede guardar la nueva contraseña" CURRENT_PASSWORD_INCORRECT = "La contraseña actual es incorrecta" NEW_PASSWORD_SHORT = "La contraseña es muy corta" NEW_PASSWORD_WEAK = "La contraseña es muy fácil" +NEW_PASSWORD_HIBP = "Contraseña encontrada en Have I Been Pwned" diff --git a/plugins/change-password/langs/fr.ini b/plugins/change-password/langs/fr.ini index 60c449b2c..ecb563f83 100644 --- a/plugins/change-password/langs/fr.ini +++ b/plugins/change-password/langs/fr.ini @@ -10,3 +10,4 @@ COULD_NOT_SAVE_NEW_PASSWORD = "Impossible d'enregistrer le nouveau mot de passe" CURRENT_PASSWORD_INCORRECT = "Le mot de passe actuel est incorrect" NEW_PASSWORD_SHORT = "Le mot de passe est trop court" NEW_PASSWORD_WEAK = "Le mot de passe n'est pas assez fort" +NEW_PASSWORD_HIBP = "Mot de passe trouvé dans Have I Been Pwned" diff --git a/plugins/change-password/langs/it.ini b/plugins/change-password/langs/it.ini index e3495e465..62507ce53 100644 --- a/plugins/change-password/langs/it.ini +++ b/plugins/change-password/langs/it.ini @@ -10,3 +10,4 @@ COULD_NOT_SAVE_NEW_PASSWORD = "Non è stato possibile salvare la nuova password" CURRENT_PASSWORD_INCORRECT = "La password attuale non è corretta" NEW_PASSWORD_SHORT = "La password scelta è troppo breve" NEW_PASSWORD_WEAK = "La password scelta non è abbastanza complessa" +NEW_PASSWORD_HIBP = "Password trovata in Have I Been Pwned" diff --git a/plugins/change-password/langs/nl.ini b/plugins/change-password/langs/nl.ini index 9eff931ad..9b6078e1d 100644 --- a/plugins/change-password/langs/nl.ini +++ b/plugins/change-password/langs/nl.ini @@ -10,3 +10,4 @@ COULD_NOT_SAVE_NEW_PASSWORD = "Nieuwe wachtwoord kon niet opgeslagen worden" CURRENT_PASSWORD_INCORRECT = "Huidig wachtwoord onjuist" NEW_PASSWORD_SHORT = "Wachtwoord is te kort" NEW_PASSWORD_WEAK = "Wachtwoord is te makkelijk" +NEW_PASSWORD_HIBP = "Wachtwoord gevonden in Have I Been Pwned" diff --git a/plugins/change-password/langs/zh.ini b/plugins/change-password/langs/zh.ini index 9aab9f805..9f902096b 100644 --- a/plugins/change-password/langs/zh.ini +++ b/plugins/change-password/langs/zh.ini @@ -10,3 +10,4 @@ COULD_NOT_SAVE_NEW_PASSWORD = "无法保存新密码" CURRENT_PASSWORD_INCORRECT = "当前密码不正确" NEW_PASSWORD_SHORT = "密码太短" NEW_PASSWORD_WEAK = "密码过于简单" +NEW_PASSWORD_HIBP = "在 Have I Been Pwned 中找到密码" diff --git a/plugins/haveibeenpwned/index.php b/plugins/haveibeenpwned/index.php index ed0048555..7e22f707d 100644 --- a/plugins/haveibeenpwned/index.php +++ b/plugins/haveibeenpwned/index.php @@ -3,14 +3,8 @@ * https://haveibeenpwned.com/API/v3 */ -use RainLoop\Model\Account; -use MailSo\Imap\ImapClient; -use MailSo\Imap\Settings as ImapSettings; -use MailSo\Sieve\SieveClient; -use MailSo\Sieve\Settings as SieveSettings; -use MailSo\Smtp\SmtpClient; -use MailSo\Smtp\Settings as SmtpSettings; -use MailSo\Mime\Message as MimeMessage; +use SnappyMail\Hibp; +use SnappyMail\SensitiveString; class HaveibeenpwnedPlugin extends \RainLoop\Plugins\AbstractPlugin { @@ -22,7 +16,7 @@ class HaveibeenpwnedPlugin extends \RainLoop\Plugins\AbstractPlugin URL = 'https://snappymail.eu/', VERSION = '0.1', RELEASE = '2024-04-22', - REQUIRED = '2.14.0', + REQUIRED = '2.36.1', CATEGORY = 'General', LICENSE = 'MIT', DESCRIPTION = 'Check if your passphrase or email address is in a data breach'; @@ -40,37 +34,14 @@ class HaveibeenpwnedPlugin extends \RainLoop\Plugins\AbstractPlugin $oAccount = $this->Manager()->Actions()->getAccountFromToken(); // $oAccount = \RainLoop\Api::Actions()->getAccountFromToken(); - $HTTP = \SnappyMail\HTTP\Request::factory(); - - $breached = null; $api_key = \trim($this->Config()->Get('plugin', 'hibp-api-key', '')); - if ($api_key) { - $breached = $HTTP->doRequest('GET', "https://haveibeenpwned.com/api/v3/breachedaccount/{$oAccount->Email()}", null, [ - 'hibp-api-key' => $api_key - ]); - } + $breaches = $api_key ? Hibp::account($api_key, $oAccount->Email()) : null; - $pass = \sha1($oAccount->ImapPass()); - $prefix = \substr($pass, 0, 5); - $suffix = \substr($pass, 5); - $response = $HTTP->doRequest('GET', "https://api.pwnedpasswords.com/range/{$prefix}"); - $passwords = []; - foreach (\preg_split('/\\R/', $response->body) as $entry) { - if ($entry) { - $entry = \explode(':', $entry); - $passwords[$entry[0]] = (int) $entry[1]; - } - } + $pwned = Hibp::password(new SensitiveString($oAccount->ImapPass())); return $this->jsonResponse(__FUNCTION__, array( - 'pwned' => isset($passwords[$suffix]) ? $passwords[$suffix] : 0, - 'breached' => $breached ? [ - 'request_uri' => $breached->request_uri, - 'final_uri' => $breached->final_uri, - 'status' => $breached->status, - 'headers' => $breached->headers, - 'body' => $breached->body - ] : [] + 'pwned' => $pwned, + 'breaches' => $breaches )); } diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/hibp.php b/snappymail/v/0.0.0/app/libraries/snappymail/hibp.php new file mode 100644 index 000000000..106968502 --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/snappymail/hibp.php @@ -0,0 +1,45 @@ +doRequest('GET', "https://api.pwnedpasswords.com/range/{$prefix}"); + if (200 !== $response->status) { + throw new HTTP\Exception('Hibp', $response->status); + } + foreach (\preg_split('/\\R/', $response->body) as $entry) { + if ($entry) { + $entry = \explode(':', $entry); + if ($entry[0] === $suffix) { + return (int) $entry[1]; + } + } + } + return 0; + } + + public static function account(string $api_key, string $email): ?array + { + if ($api_key) { + $email = \rawurlencode(IDN::emailToAscii($email)); + $response = HTTP\Request::factory()->doRequest('GET', "https://haveibeenpwned.com/api/v3/breachedaccount/{$email}", null, [ + 'hibp-api-key' => $api_key + ]); + if (200 !== $response->status) { + throw new HTTP\Exception('Hibp', $response->status); + } + return \json_decode($response->body, true); + } + return null; + } +}