From 331604ae7679516a233e0b99faf2b97ce96392c0 Mon Sep 17 00:00:00 2001 From: Renaud Lemaire Date: Tue, 9 Nov 2021 10:35:49 +0100 Subject: [PATCH 1/3] detection of user password encryption from database, grant login despite configured encryption and re-encrypt password with configured encryption --- main/inc/lib/usermanager.lib.php | 56 +++++++++++++++++++ main/inc/lib/webservices/WebService.class.php | 5 +- main/inc/local.inc.php | 8 +++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/main/inc/lib/usermanager.lib.php b/main/inc/lib/usermanager.lib.php index 3d6da07de9..ff4e2b2a53 100755 --- a/main/inc/lib/usermanager.lib.php +++ b/main/inc/lib/usermanager.lib.php @@ -126,6 +126,62 @@ class UserManager return $encoder->isPasswordValid($encoded, $raw, $salt); } + /** + * Validates the password. + * + * @param $encoded + * @param $salt + * + * @return bool + */ + public static function detectPasswordEncryption($encoded, $salt) + { + + $encryption = false; + + $length = strlen($encoded); + + $pattern = '/^\$2y\$04\$[A-Za-z0-9\.\/]{53}$/'; + + if ( $length == 60 && preg_match($pattern, $encoded)) { + $encryption = 'bcrypt'; + } + elseif ( $length == 32 && ctype_xdigit($encoded) ) { + $encryption = 'md5'; + } + elseif ( $length == 40 && ctype_xdigit($encoded) ) { + $encryption = 'sha1'; + } + else { + $start = strpos($encoded, '{'); + if ($start !== false && substr($encoded, -1, 1) == '}') { + if (substr($encoded,$start + 1,-1) == $salt) { + $encryption = 'none'; + } + } + } + + return $encryption; + } + + public static function checkPassword($encoded, $raw, $salt, $userId) + { + $result = false; + + $detectedEncryption = self::detectPasswordEncryption($encoded, $salt); + if (self::getPasswordEncryption() != $detectedEncryption) { + $encoder = new \Chamilo\UserBundle\Security\Encoder($detectedEncryption); + $result = $encoder->isPasswordValid($encoded, $raw, $salt); + if ($result) { + self::updatePassword($userId, $raw); + } + } + else { + return self::isPasswordValid($encoded, $raw, $salt); + } + + return $result; + } /** * @param string $raw * diff --git a/main/inc/lib/webservices/WebService.class.php b/main/inc/lib/webservices/WebService.class.php index 3347523ae8..39c740669f 100644 --- a/main/inc/lib/webservices/WebService.class.php +++ b/main/inc/lib/webservices/WebService.class.php @@ -103,10 +103,11 @@ class WebService return false; } - return UserManager::isPasswordValid( + return UserManager::checkPassword( $user->getPassword(), $password, - $user->getSalt() + $user->getSalt(), + $user->getId() ); } diff --git a/main/inc/local.inc.php b/main/inc/local.inc.php index 897c75db6b..f014f5495c 100755 --- a/main/inc/local.inc.php +++ b/main/inc/local.inc.php @@ -476,11 +476,19 @@ if (!empty($_SESSION['_user']['user_id']) && !($login || $logout)) { if ($uData['auth_source'] == PLATFORM_AUTH_SOURCE || $uData['auth_source'] == CAS_AUTH_SOURCE ) { + /* $validPassword = isset($password) && UserManager::isPasswordValid( $uData['password'], $password, $uData['salt'] ); + */ + $validPassword = isset($password) && UserManager::checkPassword( + $uData['password'], + $password, + $uData['salt'], + $uData['user_id'] + ); $checkUserFromExternalWebservice = false; // If user can't connect directly to chamilo then check the webservice setting From 68d72ab38d14d39d18c22cc7522bde7aefd742db Mon Sep 17 00:00:00 2001 From: Renaud Lemaire Date: Tue, 3 May 2022 14:22:45 +0200 Subject: [PATCH 2/3] cblue#426850 chamilo#4063 add a configuration option for password conversion during login if password encryption has changed --- main/inc/lib/usermanager.lib.php | 2 +- main/install/configuration.dist.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/main/inc/lib/usermanager.lib.php b/main/inc/lib/usermanager.lib.php index ff4e2b2a53..295687d160 100755 --- a/main/inc/lib/usermanager.lib.php +++ b/main/inc/lib/usermanager.lib.php @@ -169,7 +169,7 @@ class UserManager $result = false; $detectedEncryption = self::detectPasswordEncryption($encoded, $salt); - if (self::getPasswordEncryption() != $detectedEncryption) { + if (api_get_configuration_value('password_conversion') && self::getPasswordEncryption() != $detectedEncryption) { $encoder = new \Chamilo\UserBundle\Security\Encoder($detectedEncryption); $result = $encoder->isPasswordValid($encoded, $raw, $salt); if ($result) { diff --git a/main/install/configuration.dist.php b/main/install/configuration.dist.php index c9b6ca7f95..1f3cdacf59 100755 --- a/main/install/configuration.dist.php +++ b/main/install/configuration.dist.php @@ -163,6 +163,8 @@ $_configuration['cdn'] = [ $_configuration['security_key'] = '{SECURITY_KEY}'; // Hash function method $_configuration['password_encryption'] = '{ENCRYPT_PASSWORD}'; +// allow to convert passwords after login if password_encryption has changed since last login +$_configuration['password_conversion'] = false; // You may have to restart your web server if you change this $_configuration['session_stored_in_db'] = false; // Session lifetime From 6b1eb331f6529dfa3417ce8aed8f45b12a7edf7f Mon Sep 17 00:00:00 2001 From: Yannick Warnier Date: Wed, 4 May 2022 10:29:08 +0200 Subject: [PATCH 3/3] Internal: Improve code for automated password encryption change - refs GH#4063 --- main/auth/profile.php | 5 +- main/inc/lib/usermanager.lib.php | 53 +++++++++++-------- main/inc/local.inc.php | 9 +--- main/install/configuration.dist.php | 5 +- .../whispeakauth/ajax/authentify_password.php | 2 +- 5 files changed, 40 insertions(+), 34 deletions(-) diff --git a/main/auth/profile.php b/main/auth/profile.php index 535d1ee4f8..6a296a3200 100755 --- a/main/auth/profile.php +++ b/main/auth/profile.php @@ -426,10 +426,11 @@ if ($form->validate()) { api_get_setting('profile', 'email') == 'true') ) { $passwordWasChecked = true; - $validPassword = UserManager::isPasswordValid( + $validPassword = UserManager::checkPassword( $user->getPassword(), $user_data['password0'], - $user->getSalt() + $user->getSalt(), + $user->getId() ); if ($validPassword) { diff --git a/main/inc/lib/usermanager.lib.php b/main/inc/lib/usermanager.lib.php index 65c766951c..9774f91285 100755 --- a/main/inc/lib/usermanager.lib.php +++ b/main/inc/lib/usermanager.lib.php @@ -127,14 +127,13 @@ class UserManager } /** - * Validates the password. - * - * @param $encoded - * @param $salt + * Detects and returns the type of encryption of the given encrypted + * password. * - * @return bool + * @param string $encoded The encrypted password + * @param string $salt The user salt, if any */ - public static function detectPasswordEncryption($encoded, $salt) + public static function detectPasswordEncryption(string $encoded, string $salt): bool { $encryption = false; @@ -164,30 +163,41 @@ class UserManager return $encryption; } - public static function checkPassword($encoded, $raw, $salt, $userId) + /** + * Checks if the password is correct for this user. + * If the password_conversion setting is true, also update the password + * in the database to a new encryption method. + * + * @param string $encoded Encrypted password + * @param string $raw Clear password given through login form + * @param string $salt User salt, if any + * @param int $userId The user's internal ID + */ + public static function checkPassword(string $encoded, string $raw, string $salt, int $userId): bool { $result = false; - $detectedEncryption = self::detectPasswordEncryption($encoded, $salt); - if (api_get_configuration_value('password_conversion') && self::getPasswordEncryption() != $detectedEncryption) { - $encoder = new \Chamilo\UserBundle\Security\Encoder($detectedEncryption); - $result = $encoder->isPasswordValid($encoded, $raw, $salt); - if ($result) { - self::updatePassword($userId, $raw); + if (true === api_get_configuration_value('password_conversion')) { + $detectedEncryption = self::detectPasswordEncryption($encoded, $salt); + if (self::getPasswordEncryption() != $detectedEncryption) { + $encoder = new \Chamilo\UserBundle\Security\Encoder($detectedEncryption); + $result = $encoder->isPasswordValid($encoded, $raw, $salt); + if ($result) { + self::updatePassword($userId, $raw); + } } - } - else { + } else { return self::isPasswordValid($encoded, $raw, $salt); } return $result; } /** - * @param string $raw + * Encrypt the password using the current encoder * - * @return string + * @param string $raw The clear password */ - public static function encryptPassword($raw, User $user) + public static function encryptPassword(string $raw, User $user): string { $encoder = self::getEncoder($user); @@ -198,10 +208,11 @@ class UserManager } /** - * @param int $userId - * @param string $password + * Update the password of the given user to the given (in-clear) password + * @param int $userId Internal user ID + * @param string $password Password in clear */ - public static function updatePassword($userId, $password) + public static function updatePassword(int $userId, string $password): void { $repository = self::getRepository(); /** @var User $user */ diff --git a/main/inc/local.inc.php b/main/inc/local.inc.php index 20178ed328..abb5deb32a 100755 --- a/main/inc/local.inc.php +++ b/main/inc/local.inc.php @@ -511,18 +511,11 @@ if (!empty($_SESSION['_user']['user_id']) && !($login || $logout)) { if ($uData['auth_source'] == PLATFORM_AUTH_SOURCE || $uData['auth_source'] == CAS_AUTH_SOURCE ) { - /* - $validPassword = isset($password) && UserManager::isPasswordValid( - $uData['password'], - $password, - $uData['salt'] - ); - */ $validPassword = isset($password) && UserManager::checkPassword( $uData['password'], $password, $uData['salt'], - $uData['user_id'] + $uData['id'] ); $checkUserFromExternalWebservice = false; diff --git a/main/install/configuration.dist.php b/main/install/configuration.dist.php index 1488787e49..0ffdf1a913 100755 --- a/main/install/configuration.dist.php +++ b/main/install/configuration.dist.php @@ -163,8 +163,9 @@ $_configuration['cdn'] = [ $_configuration['security_key'] = '{SECURITY_KEY}'; // Hash function method $_configuration['password_encryption'] = '{ENCRYPT_PASSWORD}'; -// allow to convert passwords after login if password_encryption has changed since last login -$_configuration['password_conversion'] = false; +// Set to true to allow automated password conversion after login if +// password_encryption has changed since last login. See GH#4063 for details. +//$_configuration['password_conversion'] = false; // You may have to restart your web server if you change this $_configuration['session_stored_in_db'] = false; // Session lifetime diff --git a/plugin/whispeakauth/ajax/authentify_password.php b/plugin/whispeakauth/ajax/authentify_password.php index ef75d7b675..1e583ea361 100644 --- a/plugin/whispeakauth/ajax/authentify_password.php +++ b/plugin/whispeakauth/ajax/authentify_password.php @@ -42,7 +42,7 @@ $lpItemInfo = ChamiloSession::read(WhispeakAuthPlugin::SESSION_LP_ITEM, []); /** @var array $quizQuestionInfo */ $quizQuestionInfo = ChamiloSession::read(WhispeakAuthPlugin::SESSION_QUIZ_QUESTION, []); -$isValidPassword = UserManager::isPasswordValid($user->getPassword(), $password, $user->getSalt()); +$isValidPassword = UserManager::checkPassword($user->getPassword(), $password, $user->getSalt(), $user->getId()); $isActive = $user->isActive(); $isExpired = empty($user->getExpirationDate()) || $user->getExpirationDate() > api_get_utc_datetime(null, false, true);