MDL-35332 lib: Improve security of hashed passwords
authorSimon Coggins <simon.coggins@totaralms.com>
Sun, 30 Sep 2012 07:58:13 +0000 (20:58 +1300)
committerSimon Coggins <simon.coggins@totaralms.com>
Fri, 8 Feb 2013 17:47:57 +0000 (06:47 +1300)
27 files changed:
admin/tool/uploaduser/index.php
auth/db/auth.php
auth/email/auth.php
auth/ldap/auth.php
auth/manual/auth.php
auth/none/auth.php
auth/webservice/auth.php
backup/util/dbops/restore_dbops.class.php
config-dist.php
lib/cronlib.php
lib/db/install.xml
lib/db/upgrade.php
lib/installlib.php
lib/moodlelib.php
lib/password_compat/lib/password.php [new file with mode: 0644]
lib/password_compat/readme_moodle.txt [new file with mode: 0644]
lib/password_compat/tests/PasswordGetInfoTest.php [new file with mode: 0644]
lib/password_compat/tests/PasswordHashTest.php [new file with mode: 0644]
lib/password_compat/tests/PasswordNeedsRehashTest.php [new file with mode: 0644]
lib/password_compat/tests/PasswordVerifyTest.php [new file with mode: 0644]
lib/phpunit/bootstrap.php
lib/setuplib.php
lib/tests/moodlelib_test.php
phpunit.xml.dist
report/security/lang/en/report_security.php
report/security/locallib.php
version.php

index f249f1d..084d8dc 100644 (file)
@@ -618,7 +618,7 @@ if ($formdata = $mform2->is_cancelled()) {
                 // Do not mess with passwords of remote users.
 
             } else if (!$isinternalauth) {
-                $existinguser->password = 'not cached';
+                $existinguser->password = AUTH_PASSWORD_NOT_CACHED;
                 $upt->track('password', '-', 'normal', false);
                 // clean up prefs
                 unset_user_preference('create_password', $existinguser);
@@ -626,6 +626,8 @@ if ($formdata = $mform2->is_cancelled()) {
 
             } else if (!empty($user->password)) {
                 if ($updatepasswords) {
+                    // Check for passwords that we want to force users to reset next
+                    // time they log in.
                     $errmsg = null;
                     $weak = !check_password_policy($user->password, $errmsg);
                     if ($resetpasswords == UU_PWRESET_ALL or ($resetpasswords == UU_PWRESET_WEAK and $weak)) {
@@ -638,7 +640,12 @@ if ($formdata = $mform2->is_cancelled()) {
                         unset_user_preference('auth_forcepasswordchange', $existinguser);
                     }
                     unset_user_preference('create_password', $existinguser); // no need to create password any more
-                    $existinguser->password = hash_internal_user_password($user->password);
+
+                    // Use a low cost factor when generating bcrypt hash otherwise
+                    // hashing would be slow when uploading lots of users. Hashes
+                    // will be automatically updated to a higher cost factor the first
+                    // time the user logs in.
+                    $existinguser->password = hash_internal_user_password($user->password, true);
                     $upt->track('password', $user->password, 'normal', false);
                 } else {
                     // do not print password when not changed
@@ -771,10 +778,14 @@ if ($formdata = $mform2->is_cancelled()) {
                         }
                         $forcechangepassword = true;
                     }
-                    $user->password = hash_internal_user_password($user->password);
+                    // Use a low cost factor when generating bcrypt hash otherwise
+                    // hashing would be slow when uploading lots of users. Hashes
+                    // will be automatically updated to a higher cost factor the first
+                    // time the user logs in.
+                    $user->password = hash_internal_user_password($user->password, true);
                 }
             } else {
-                $user->password = 'not cached';
+                $user->password = AUTH_PASSWORD_NOT_CACHED;
                 $upt->track('password', '-', 'normal', false);
             }
 
index 57c46e4..f245d60 100644 (file)
@@ -221,6 +221,9 @@ class auth_plugin_db extends auth_plugin_base {
 
         if ($this->is_internal()) {
             $puser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
+            // This will also update the stored hash to the latest algorithm
+            // if the existing hash is using an out-of-date algorithm (or the
+            // legacy md5 algorithm).
             if (update_internal_user_password($puser, $newpassword)) {
                 $user->password = $puser->password;
                 return true;
index e50c09e..777fe9f 100644 (file)
@@ -59,6 +59,9 @@ class auth_plugin_email extends auth_plugin_base {
      */
     function user_update_password($user, $newpassword) {
         $user = get_complete_user_data('id', $user->id);
+        // This will also update the stored hash to the latest algorithm
+        // if the existing hash is using an out-of-date algorithm (or the
+        // legacy md5 algorithm).
         return update_internal_user_password($user, $newpassword);
     }
 
index 6cd7fd7..9bdd5b3 100644 (file)
@@ -529,6 +529,9 @@ class auth_plugin_ldap extends auth_plugin_base {
         profile_save_data($user);
 
         $this->update_user_record($user->username);
+        // This will also update the stored hash to the latest algorithm
+        // if the existing hash is using an out-of-date algorithm (or the
+        // legacy md5 algorithm).
         update_internal_user_password($user, $plainslashedpassword);
 
         $user = $DB->get_record('user', array('id'=>$user->id));
index 29cb59a..0c521e8 100644 (file)
@@ -82,6 +82,9 @@ class auth_plugin_manual extends auth_plugin_base {
      */
     function user_update_password($user, $newpassword) {
         $user = get_complete_user_data('id', $user->id);
+        // This will also update the stored hash to the latest algorithm
+        // if the existing hash is using an out-of-date algorithm (or the
+        // legacy md5 algorithm).
         return update_internal_user_password($user, $newpassword);
     }
 
index f0771d8..00eaf04 100644 (file)
@@ -59,6 +59,9 @@ class auth_plugin_none extends auth_plugin_base {
      */
     function user_update_password($user, $newpassword) {
         $user = get_complete_user_data('id', $user->id);
+        // This will also update the stored hash to the latest algorithm
+        // if the existing hash is using an out-of-date algorithm (or the
+        // legacy md5 algorithm).
         return update_internal_user_password($user, $newpassword);
     }
 
index 15a7598..59e1603 100644 (file)
@@ -85,6 +85,9 @@ class auth_plugin_webservice extends auth_plugin_base {
      */
     function user_update_password($user, $newpassword) {
         $user = get_complete_user_data('id', $user->id);
+        // This will also update the stored hash to the latest algorithm
+        // if the existing hash is using an out-of-date algorithm (or the
+        // legacy md5 algorithm).
         return update_internal_user_password($user, $newpassword);
     }
 
index 82c4897..148afa3 100644 (file)
@@ -1052,7 +1052,7 @@ abstract class restore_dbops {
 
                 // Most external plugins do not store passwords locally
                 if (!empty($userauth->preventpassindb)) {
-                    $user->password = 'not cached';
+                    $user->password = AUTH_PASSWORD_NOT_CACHED;
 
                 // If Moodle is responsible for storing/validating pwd and reset functionality is available, mark
                 } else if ($userauth->isinternal and $userauth->canresetpwd) {
index 88a2c5b..0cdcc5f 100644 (file)
@@ -63,28 +63,7 @@ $CFG->dboptions = array(
 
 
 //=========================================================================
-// 2. SECRET PASSWORD SALT
-//=========================================================================
-// User password salt is very important security feature, it is created
-// automatically in installer, you have to uncomment and modify value
-// on the next line if you are creating config.php manually.
-//
-// $CFG->passwordsaltmain = 'a_very_long_random_string_of_characters#@6&*1';
-//
-// After changing the main salt you have to copy old value into one
-// of the following settings - this allows migration to the new salt
-// during the next login of each user.
-//
-// $CFG->passwordsaltalt1 = '';
-// $CFG->passwordsaltalt2 = '';
-// $CFG->passwordsaltalt3 = '';
-// ....
-// $CFG->passwordsaltalt19 = '';
-// $CFG->passwordsaltalt20 = '';
-
-
-//=========================================================================
-// 3. WEB SITE LOCATION
+// 2. WEB SITE LOCATION
 //=========================================================================
 // Now you need to tell Moodle where it is located. Specify the full
 // web address to where moodle has been installed.  If your web site
@@ -98,7 +77,7 @@ $CFG->wwwroot   = 'http://example.com/moodle';
 
 
 //=========================================================================
-// 4. DATA FILES LOCATION
+// 3. DATA FILES LOCATION
 //=========================================================================
 // Now you need a place where Moodle can save uploaded files.  This
 // directory should be readable AND WRITEABLE by the web server user
@@ -114,7 +93,7 @@ $CFG->dataroot  = '/home/example/moodledata';
 
 
 //=========================================================================
-// 5. DATA FILES PERMISSIONS
+// 4. DATA FILES PERMISSIONS
 //=========================================================================
 // The following parameter sets the permissions of new directories
 // created by Moodle within the data directory.  The format is in
@@ -128,7 +107,7 @@ $CFG->directorypermissions = 02777;
 
 
 //=========================================================================
-// 6. DIRECTORY LOCATION  (most people can just ignore this setting)
+// 5. DIRECTORY LOCATION  (most people can just ignore this setting)
 //=========================================================================
 // A very few webhosts use /admin as a special URL for you to access a
 // control panel or something.  Unfortunately this conflicts with the
@@ -140,7 +119,7 @@ $CFG->admin = 'admin';
 
 
 //=========================================================================
-// 7. OTHER MISCELLANEOUS SETTINGS (ignore these for new installations)
+// 6. OTHER MISCELLANEOUS SETTINGS (ignore these for new installations)
 //=========================================================================
 //
 // These are additional tweaks for which no GUI exists in Moodle yet.
@@ -471,7 +450,7 @@ $CFG->admin = 'admin';
 //      $CFG->svgicons = false;
 //
 //=========================================================================
-// 8. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
+// 7. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
 //=========================================================================
 //
 // Force a debugging mode regardless the settings in the site administration
@@ -512,7 +491,7 @@ $CFG->admin = 'admin';
 // $CFG->showcrondebugging = true;
 //
 //=========================================================================
-// 9. FORCED SETTINGS
+// 8. FORCED SETTINGS
 //=========================================================================
 // It is possible to specify normal admin settings here, the point is that
 // they can not be changed through the standard admin settings pages any more.
@@ -527,12 +506,35 @@ $CFG->admin = 'admin';
 //                                        'otherplugin' => array('mysetting' => 'myvalue', 'thesetting' => 'thevalue'));
 //
 //=========================================================================
-// 10. PHPUNIT SUPPORT
+// 9. PHPUNIT SUPPORT
 //=========================================================================
 // $CFG->phpunit_prefix = 'phpu_';
 // $CFG->phpunit_dataroot = '/home/example/phpu_moodledata';
 // $CFG->phpunit_directorypermissions = 02777; // optional
 //
+//
+//=========================================================================
+// 10. SECRET PASSWORD SALT
+//=========================================================================
+// A single site-wide password salt is no longer required *unless* you are
+// upgrading an older version of Moodle (prior to 2.5), or if you are using
+// a PHP version below 5.3.7. If upgrading, keep any values from your old
+// config.php file. If you are using PHP < 5.3.7 set to a long random string
+// below:
+//
+// $CFG->passwordsaltmain = 'a_very_long_random_string_of_characters#@6&*1';
+//
+// You may also have some alternative salts to allow migration from previously
+// used salts.
+//
+// $CFG->passwordsaltalt1 = '';
+// $CFG->passwordsaltalt2 = '';
+// $CFG->passwordsaltalt3 = '';
+// ....
+// $CFG->passwordsaltalt19 = '';
+// $CFG->passwordsaltalt20 = '';
+//
+//
 //=========================================================================
 // 11. BEHAT SUPPORT
 //=========================================================================
index 79e808b..0ae0ae3 100644 (file)
@@ -216,7 +216,11 @@ function cron_run() {
 
         // note: we can not send emails to suspended accounts
         foreach ($newusers as $newuser) {
-            if (setnew_password_and_mail($newuser)) {
+            // Use a low cost factor when generating bcrypt hash otherwise
+            // hashing would be slow when emailing lots of users. Hashes
+            // will be automatically updated to a higher cost factor the first
+            // time the user logs in.
+            if (setnew_password_and_mail($newuser, true)) {
                 unset_user_preference('create_password', $newuser);
                 set_user_preference('auth_forcepasswordchange', 1, $newuser);
             } else {
index d4eb4fc..1602999 100644 (file)
         <FIELD NAME="suspended" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="suspended flag prevents users to log in"/>
         <FIELD NAME="mnethostid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="username" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="password" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="password" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="idnumber" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="firstname" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="lastname" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
index 3dcc1d1..12efe34 100644 (file)
@@ -1564,6 +1564,18 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2012120300.07);
     }
 
+    if ($oldversion < 2013020900.00) {
+
+        // Changing precision of field password on table user to (255).
+        $table = new xmldb_table('user');
+        $field = new xmldb_field('password', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'username');
+
+        // Launch change of precision for field password.
+        $dbman->change_field_precision($table, $field);
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2013020900.00);
+    }
 
     return true;
 }
index 7b8cca2..30e1297 100644 (file)
@@ -233,7 +233,10 @@ function install_generate_configphp($database, $cfg) {
     }
     $configphp .= '$CFG->directorypermissions = ' . $chmod . ';' . PHP_EOL . PHP_EOL;
 
-    $configphp .= '$CFG->passwordsaltmain = '.var_export(complex_random_string(), true) . ';' . PHP_EOL . PHP_EOL;
+    // A site-wide salt is only needed if bcrypt is not properly supported by the current version of PHP.
+    if (password_compat_not_supported()) {
+        $configphp .= '$CFG->passwordsaltmain = '.var_export(complex_random_string(), true) . ';' . PHP_EOL . PHP_EOL;
+    }
 
     $configphp .= 'require_once(dirname(__FILE__) . \'/lib/setup.php\');' . PHP_EOL . PHP_EOL;
     $configphp .= '// There is no php closing tag in this file,' . PHP_EOL;
index cd7b102..0df4307 100644 (file)
@@ -493,6 +493,11 @@ define('USER_CAN_IGNORE_FILE_SIZE_LIMITS', -1);
 define('COURSE_DISPLAY_SINGLEPAGE', 0); // display all sections on one page
 define('COURSE_DISPLAY_MULTIPAGE', 1); // split pages into a page per section
 
+/**
+ * Authentication constants.
+ */
+define('AUTH_PASSWORD_NOT_CACHED', 'not cached'); // String used in password field when password is not stored.
+
 /// PARAMETER HANDLING ////////////////////////////////////////////////////
 
 /**
@@ -3845,6 +3850,7 @@ function create_user_record($username, $password, $auth = 'manual') {
     if (!empty($CFG->{'auth_'.$newuser->auth.'_forcechangepassword'})){
         set_user_preference('auth_forcepasswordchange', 1, $user);
     }
+    // Set the password.
     update_internal_user_password($user, $password);
 
     // fetch full user record for the event, the complete user data contains too much info
@@ -4197,7 +4203,10 @@ function authenticate_user_login($username, $password, $ignorelockout=false, &$f
                 $user->auth = $auth;
             }
 
-            update_internal_user_password($user, $password); // just in case salt or encoding were changed (magic quotes too one day)
+            // If the existing hash is using an out-of-date algorithm (or the
+            // legacy md5 algorithm), then we should update to the current
+            // hash algorithm while we have access to the user's password.
+            update_internal_user_password($user, $password);
 
             if ($authplugin->is_synchronised_with_external()) { // update user record from external DB
                 $user = update_user_record($username);
@@ -4307,28 +4316,81 @@ function complete_user_login($user) {
 }
 
 /**
- * Compare password against hash stored in internal user table.
- * If necessary it also updates the stored hash to new format.
+ * Check a password hash to see if it was hashed using the
+ * legacy hash algorithm (md5).
+ *
+ * @param string $password String to check.
+ * @return boolean True if the $password matches the format of an md5 sum.
+ */
+function password_is_legacy_hash($password) {
+    return (bool) preg_match('/^[0-9a-f]{32}$/', $password);
+}
+
+/**
+ * Checks whether the password compatibility library will work with the current
+ * version of PHP. This cannot be done using PHP version numbers since the fix
+ * has been backported to earlier versions in some distributions.
+ *
+ * See https://github.com/ircmaxell/password_compat/issues/10 for
+ * more details.
+ *
+ * @return bool True if the library is NOT supported.
+ */
+function password_compat_not_supported() {
+
+    $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
+
+    // Create a one off application cache to store bcrypt support status as
+    // the support status doesn't change and crypt() is slow.
+    $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'core', 'password_compat');
+
+    if (!$bcryptsupport = $cache->get('bcryptsupport')) {
+        $test = crypt('password', $hash);
+        // Cache string instead of boolean to avoid MDL-37472.
+        if ($test == $hash) {
+            $bcryptsupport = 'supported';
+        } else {
+            $bcryptsupport = 'not supported';
+        }
+        $cache->set('bcryptsupport', $bcryptsupport);
+    }
+
+    // Return true if bcrypt *not* supported.
+    return ($bcryptsupport !== 'supported');
+}
+
+/**
+ * Compare password against hash stored in user object to determine if it is valid.
+ *
+ * If necessary it also updates the stored hash to the current format.
  *
- * @param stdClass $user (password property may be updated)
- * @param string $password plain text password
- * @return bool is password valid?
+ * @param stdClass $user (Password property may be updated).
+ * @param string $password Plain text password.
+ * @return bool True if password is valid.
  */
 function validate_internal_user_password($user, $password) {
     global $CFG;
+    require_once($CFG->libdir.'/password_compat/lib/password.php');
 
-    if (!isset($CFG->passwordsaltmain)) {
-        $CFG->passwordsaltmain = '';
+    if ($user->password === AUTH_PASSWORD_NOT_CACHED) {
+        // Internal password is not used at all, it can not validate.
+        return false;
     }
 
-    $validated = false;
+    // If hash isn't a legacy (md5) hash, validate using the library function.
+    if (!password_is_legacy_hash($user->password)) {
+        return password_verify($password, $user->password);
+    }
 
-    if ($user->password === 'not cached') {
-        // internal password is not used at all, it can not validate
+    // Otherwise we need to check for a legacy (md5) hash instead. If the hash
+    // is valid we can then update it to the new algorithm.
 
-    } else if ($user->password === md5($password.$CFG->passwordsaltmain)
+    $sitesalt = isset($CFG->passwordsaltmain) ? $CFG->passwordsaltmain : '';
+    $validated = false;
+
+    if ($user->password === md5($password.$sitesalt)
             or $user->password === md5($password)
-            or $user->password === md5(addslashes($password).$CFG->passwordsaltmain)
+            or $user->password === md5(addslashes($password).$sitesalt)
             or $user->password === md5(addslashes($password))) {
         // note: we are intentionally using the addslashes() here because we
         //       need to accept old password hashes of passwords with magic quotes
@@ -4347,7 +4409,8 @@ function validate_internal_user_password($user, $password) {
     }
 
     if ($validated) {
-        // force update of password hash using latest main password salt and encoding if needed
+        // If the password matches the existing md5 hash, update to the
+        // current hash algorithm while we have access to the user's password.
         update_internal_user_password($user, $password);
     }
 
@@ -4355,39 +4418,85 @@ function validate_internal_user_password($user, $password) {
 }
 
 /**
- * Calculate hashed value from password using current hash mechanism.
+ * Calculate hash for a plain text password.
+ *
+ * @param string $password Plain text password to be hashed.
+ * @param bool $fasthash If true, use a low cost factor when generating the hash
+ *                       This is much faster to generate but makes the hash
+ *                       less secure. It is used when lots of hashes need to
+ *                       be generated quickly.
+ * @return string The hashed password.
  *
- * @param string $password
- * @return string password hash
+ * @throws moodle_exception If a problem occurs while generating the hash.
  */
-function hash_internal_user_password($password) {
+function hash_internal_user_password($password, $fasthash = false) {
     global $CFG;
+    require_once($CFG->libdir.'/password_compat/lib/password.php');
 
-    if (isset($CFG->passwordsaltmain)) {
-        return md5($password.$CFG->passwordsaltmain);
-    } else {
-        return md5($password);
+    // Use the legacy hashing algorithm (md5) if PHP is not new enough
+    // to support bcrypt properly
+    if (password_compat_not_supported()) {
+        if (isset($CFG->passwordsaltmain)) {
+            return md5($password.$CFG->passwordsaltmain);
+        } else {
+            return md5($password);
+        }
+    }
+
+    // Set the cost factor to 4 for fast hashing, otherwise use default cost.
+    $options = ($fasthash) ? array('cost' => 4) : array();
+
+    $generatedhash = password_hash($password, PASSWORD_DEFAULT, $options);
+
+    if ($generatedhash === false) {
+        throw new moodle_exception('Failed to generate password hash.');
     }
+
+    return $generatedhash;
 }
 
 /**
- * Update password hash in user object.
+ * Update password hash in user object (if necessary).
  *
- * @param stdClass $user (password property may be updated)
- * @param string $password plain text password
- * @return bool always returns true
+ * The password is updated if:
+ * 1. The password has changed (the hash of $user->password is different
+ *    to the hash of $password).
+ * 2. The existing hash is using an out-of-date algorithm (or the legacy
+ *    md5 algorithm).
+ *
+ * Updating the password will modify the $user object and the database
+ * record to use the current hashing algorithm.
+ *
+ * @param stdClass $user User object (password property may be updated).
+ * @param string $password Plain text password.
+ * @return bool Always returns true.
  */
 function update_internal_user_password($user, $password) {
-    global $DB;
+    global $CFG, $DB;
+    require_once($CFG->libdir.'/password_compat/lib/password.php');
+
+    // Use the legacy hashing algorithm (md5) if PHP doesn't support
+    // bcrypt properly.
+    $legacyhash = password_compat_not_supported();
 
+    // Figure out what the hashed password should be.
     $authplugin = get_auth_plugin($user->auth);
     if ($authplugin->prevent_local_passwords()) {
-        $hashedpassword = 'not cached';
+        $hashedpassword = AUTH_PASSWORD_NOT_CACHED;
     } else {
         $hashedpassword = hash_internal_user_password($password);
     }
 
-    if ($user->password !== $hashedpassword) {
+    if ($legacyhash) {
+        $passwordchanged = ($user->password !== $hashedpassword);
+        $algorithmchanged = false;
+    } else {
+        // If verification fails then it means the password has changed.
+        $passwordchanged = !password_verify($password, $user->password);
+        $algorithmchanged = password_needs_rehash($user->password, PASSWORD_DEFAULT);
+    }
+
+    if ($passwordchanged || $algorithmchanged) {
         $DB->set_field('user', 'password',  $hashedpassword, array('id'=>$user->id));
         $user->password = $hashedpassword;
     }
@@ -5588,9 +5697,10 @@ function generate_email_supportuser() {
  * @global object
  * @global object
  * @param user $user A {@link $USER} object
+ * @param boolean $fasthash If true, use a low cost factor when generating the hash for speed.
  * @return boolean|string Returns "true" if mail was sent OK and "false" if there was an error
  */
-function setnew_password_and_mail($user) {
+function setnew_password_and_mail($user, $fasthash = false) {
     global $CFG, $DB;
 
     // we try to send the mail in language the user understands,
@@ -5604,7 +5714,8 @@ function setnew_password_and_mail($user) {
 
     $newpassword = generate_password();
 
-    $DB->set_field('user', 'password', hash_internal_user_password($newpassword), array('id'=>$user->id));
+    $hashedpassword = hash_internal_user_password($newpassword, $fasthash);
+    $DB->set_field('user', 'password', $hashedpassword, array('id'=>$user->id));
 
     $a = new stdClass();
     $a->firstname   = fullname($user, true);
diff --git a/lib/password_compat/lib/password.php b/lib/password_compat/lib/password.php
new file mode 100644 (file)
index 0000000..6732e9f
--- /dev/null
@@ -0,0 +1,220 @@
+<?php
+/**
+ * A Compatibility library with PHP 5.5's simplified password hashing API.
+ *
+ * @author Anthony Ferrara <ircmaxell@php.net>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @copyright 2012 The Authors
+ */
+
+if (!defined('PASSWORD_BCRYPT')) {
+
+       define('PASSWORD_BCRYPT', 1);
+       define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
+
+       /**
+        * Hash the password using the specified algorithm
+        *
+        * @param string $password The password to hash
+        * @param int    $algo     The algorithm to use (Defined by PASSWORD_* constants)
+        * @param array  $options  The options for the algorithm to use
+        *
+        * @return string|false The hashed password, or false on error.
+        */
+       function password_hash($password, $algo, array $options = array()) {
+               if (!function_exists('crypt')) {
+                       trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
+                       return null;
+               }
+               if (!is_string($password)) {
+                       trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
+                       return null;
+               }
+               if (!is_int($algo)) {
+                       trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
+                       return null;
+               }
+               switch ($algo) {
+                       case PASSWORD_BCRYPT:
+                               // Note that this is a C constant, but not exposed to PHP, so we don't define it here.
+                               $cost = 10;
+                               if (isset($options['cost'])) {
+                                       $cost = $options['cost'];
+                                       if ($cost < 4 || $cost > 31) {
+                                               trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
+                                               return null;
+                                       }
+                               }
+                               $required_salt_len = 22;
+                               $hash_format = sprintf("$2y$%02d$", $cost);
+                               break;
+                       default:
+                               trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
+                               return null;
+               }
+               if (isset($options['salt'])) {
+                       switch (gettype($options['salt'])) {
+                               case 'NULL':
+                               case 'boolean':
+                               case 'integer':
+                               case 'double':
+                               case 'string':
+                                       $salt = (string) $options['salt'];
+                                       break;
+                               case 'object':
+                                       if (method_exists($options['salt'], '__tostring')) {
+                                               $salt = (string) $options['salt'];
+                                               break;
+                                       }
+                               case 'array':
+                               case 'resource':
+                               default:
+                                       trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
+                                       return null;
+                       }
+                       if (strlen($salt) < $required_salt_len) {
+                               trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
+                               return null;
+                       } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
+                               $salt = str_replace('+', '.', base64_encode($salt));
+                       }
+               } else {
+                       $buffer = '';
+                       $raw_length = (int) ($required_salt_len * 3 / 4 + 1);
+                       $buffer_valid = false;
+                       if (function_exists('mcrypt_create_iv')) {
+                               $buffer = mcrypt_create_iv($raw_length, MCRYPT_DEV_URANDOM);
+                               if ($buffer) {
+                                       $buffer_valid = true;
+                               }
+                       }
+                       if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
+                               $buffer = openssl_random_pseudo_bytes($raw_length);
+                               if ($buffer) {
+                                       $buffer_valid = true;
+                               }
+                       }
+                       if (!$buffer_valid && file_exists('/dev/urandom')) {
+                               $f = @fopen('/dev/urandom', 'r');
+                               if ($f) {
+                                       $read = strlen($buffer);
+                                       while ($read < $raw_length) {
+                                               $buffer .= fread($f, $raw_length - $read);
+                                               $read = strlen($buffer);
+                                       }
+                                       fclose($f);
+                                       if ($read >= $raw_length) {
+                                               $buffer_valid = true;
+                                       }
+                               }
+                       }
+                       if (!$buffer_valid || strlen($buffer) < $raw_length) {
+                               $bl = strlen($buffer);
+                               for ($i = 0; $i < $raw_length; $i++) {
+                                       if ($i < $bl) {
+                                               $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
+                                       } else {
+                                               $buffer .= chr(mt_rand(0, 255));
+                                       }
+                               }
+                       }
+                       $salt = str_replace('+', '.', base64_encode($buffer));
+
+               }
+               $salt = substr($salt, 0, $required_salt_len);
+
+               $hash = $hash_format . $salt;
+
+               $ret = crypt($password, $hash);
+
+               if (!is_string($ret) || strlen($ret) <= 13) {
+                       return false;
+               }
+
+               return $ret;
+       }
+
+       /**
+        * Get information about the password hash. Returns an array of the information
+        * that was used to generate the password hash.
+        *
+        * array(
+        *    'algo' => 1,
+        *    'algoName' => 'bcrypt',
+        *    'options' => array(
+        *        'cost' => 10,
+        *    ),
+        * )
+        *
+        * @param string $hash The password hash to extract info from
+        *
+        * @return array The array of information about the hash.
+        */
+       function password_get_info($hash) {
+               $return = array(
+                       'algo' => 0,
+                       'algoName' => 'unknown',
+                       'options' => array(),
+               );
+               if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) {
+                       $return['algo'] = PASSWORD_BCRYPT;
+                       $return['algoName'] = 'bcrypt';
+                       list($cost) = sscanf($hash, "$2y$%d$");
+                       $return['options']['cost'] = $cost;
+               }
+               return $return;
+       }
+
+       /**
+        * Determine if the password hash needs to be rehashed according to the options provided
+        *
+        * If the answer is true, after validating the password using password_verify, rehash it.
+        *
+        * @param string $hash    The hash to test
+        * @param int    $algo    The algorithm used for new password hashes
+        * @param array  $options The options array passed to password_hash
+        *
+        * @return boolean True if the password needs to be rehashed.
+        */
+       function password_needs_rehash($hash, $algo, array $options = array()) {
+               $info = password_get_info($hash);
+               if ($info['algo'] != $algo) {
+                       return true;
+               }
+               switch ($algo) {
+                       case PASSWORD_BCRYPT:
+                               $cost = isset($options['cost']) ? $options['cost'] : 10;
+                               if ($cost != $info['options']['cost']) {
+                                       return true;
+                               }
+                               break;
+               }
+               return false;
+       }
+
+       /**
+        * Verify a password against a hash using a timing attack resistant approach
+        *
+        * @param string $password The password to verify
+        * @param string $hash     The hash to verify against
+        *
+        * @return boolean If the password matches the hash
+        */
+    function password_verify($password, $hash) {
+               if (!function_exists('crypt')) {
+                       trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
+                       return false;
+               }
+               $ret = crypt($password, $hash);
+               if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
+                       return false;
+               }
+
+               $status = 0;
+               for ($i = 0; $i < strlen($ret); $i++) {
+                       $status |= (ord($ret[$i]) ^ ord($hash[$i]));
+               }
+
+               return $status === 0;
+       }
+}
diff --git a/lib/password_compat/readme_moodle.txt b/lib/password_compat/readme_moodle.txt
new file mode 100644 (file)
index 0000000..9740c2e
--- /dev/null
@@ -0,0 +1,37 @@
+Description of password_compat import into Moodle:
+==================================================
+
+Imported from: https://github.com/ircmaxell/password_compat/commit/2a7b6355d27c65f7e0de1fbbc0016b5b6cd8226b
+Copyright: (c) 2012 Anthony Ferrara
+License: MIT License
+
+Removed:
+* README.md, LICENSE.md and composer.json files.
+* bootstrap.php and phpunit.xml.dist files from test directory.
+
+Added:
+* None.
+
+Our changes:
+* Moved tests from test/Unit/ to tests/ directory.
+* Removed tabs and trailing whitespace from test files.
+* Added markTestSkipped() check to tests so they only run if password_compat is supported
+
+Moodle commit history:
+======================
+
+MDL-35332   Initial commit
+
+
+Library description:
+====================
+
+Compatibility with the password_* functions being worked on for PHP 5.5.
+
+This library requires PHP >= 5.3.7 due to a PHP security issue prior to that
+version.
+
+See the RFC (https://wiki.php.net/rfc/password_hash) for more information.
+
+Latest code available from https://github.com/ircmaxell/password_compat/
+under MIT license.
diff --git a/lib/password_compat/tests/PasswordGetInfoTest.php b/lib/password_compat/tests/PasswordGetInfoTest.php
new file mode 100644 (file)
index 0000000..215cd3c
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+global $CFG;
+require_once($CFG->dirroot . '/lib/password_compat/lib/password.php');
+
+class PasswordGetInfoTest extends PHPUnit_Framework_TestCase {
+
+    protected function setUp() {
+        if (password_compat_not_supported()) {
+            // Skip test if password_compat is not supported.
+            $this->markTestSkipped('password_compat not supported');
+        }
+    }
+
+    public static function provideInfo() {
+        return array(
+            array('foo', array('algo' => 0, 'algoName' => 'unknown', 'options' => array())),
+            array('$2y$', array('algo' => 0, 'algoName' => 'unknown', 'options' => array())),
+            array('$2y$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', array('algo' => PASSWORD_BCRYPT, 'algoName' => 'bcrypt', 'options' => array('cost' => 7))),
+            array('$2y$10$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', array('algo' => PASSWORD_BCRYPT, 'algoName' => 'bcrypt', 'options' => array('cost' => 10))),
+
+        );
+    }
+
+    public function testFuncExists() {
+        $this->assertTrue(function_exists('password_get_info'));
+    }
+
+    /**
+     * @dataProvider provideInfo
+     */
+    public function testInfo($hash, $info) {
+        $this->assertEquals($info, password_get_info($hash));
+    }
+
+}
diff --git a/lib/password_compat/tests/PasswordHashTest.php b/lib/password_compat/tests/PasswordHashTest.php
new file mode 100644 (file)
index 0000000..010ea34
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+
+global $CFG;
+require_once($CFG->dirroot . '/lib/password_compat/lib/password.php');
+
+class PasswordHashTest extends PHPUnit_Framework_TestCase {
+
+    protected function setUp() {
+        if (password_compat_not_supported()) {
+            // Skip test if password_compat is not supported.
+            $this->markTestSkipped('password_compat not supported');
+        }
+    }
+
+    public function testFuncExists() {
+        $this->assertTrue(function_exists('password_hash'));
+    }
+
+    public function testStringLength() {
+        $this->assertEquals(60, strlen(password_hash('foo', PASSWORD_BCRYPT)));
+    }
+
+    public function testHash() {
+        $hash = password_hash('foo', PASSWORD_BCRYPT);
+        $this->assertEquals($hash, crypt('foo', $hash));
+    }
+
+    public function testKnownSalt() {
+        $hash = password_hash("rasmuslerdorf", PASSWORD_BCRYPT, array("cost" => 7, "salt" => "usesomesillystringforsalt"));
+        $this->assertEquals('$2y$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', $hash);
+    }
+
+    public function testRawSalt() {
+        $hash = password_hash("test", PASSWORD_BCRYPT, array("salt" => "123456789012345678901" . chr(0)));
+        $this->assertEquals('$2y$10$MTIzNDU2Nzg5MDEyMzQ1Nej0NmcAWSLR.oP7XOR9HD/vjUuOj100y', $hash);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInvalidAlgo() {
+        password_hash('foo', array());
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInvalidAlgo2() {
+        password_hash('foo', 2);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInvalidPassword() {
+        password_hash(array(), 1);
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInvalidSalt() {
+        password_hash('foo', PASSWORD_BCRYPT, array('salt' => array()));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInvalidBcryptCostLow() {
+        password_hash('foo', PASSWORD_BCRYPT, array('cost' => 3));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInvalidBcryptCostHigh() {
+        password_hash('foo', PASSWORD_BCRYPT, array('cost' => 32));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInvalidBcryptCostInvalid() {
+        password_hash('foo', PASSWORD_BCRYPT, array('cost' => 'foo'));
+    }
+
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testInvalidBcryptSaltShort() {
+        password_hash('foo', PASSWORD_BCRYPT, array('salt' => 'abc'));
+    }
+
+}
diff --git a/lib/password_compat/tests/PasswordNeedsRehashTest.php b/lib/password_compat/tests/PasswordNeedsRehashTest.php
new file mode 100644 (file)
index 0000000..f6fa0e3
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+global $CFG;
+require_once($CFG->dirroot . '/lib/password_compat/lib/password.php');
+
+class PasswordNeedsRehashTest extends PHPUnit_Framework_TestCase {
+
+    protected function setUp() {
+        if (password_compat_not_supported()) {
+            // Skip test if password_compat is not supported.
+            $this->markTestSkipped('password_compat not supported');
+        }
+    }
+
+    public static function provideCases() {
+        return array(
+            array('foo', 0, array(), false),
+            array('foo', 1, array(), true),
+            array('$2y$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', PASSWORD_BCRYPT, array(), true),
+            array('$2y$07$usesomesillystringfore2udlvp1ii2e./u9c8sbjqp8i90dh6hi', PASSWORD_BCRYPT, array('cost' => 7), false),
+            array('$2y$07$usesomesillystringfore2udlvp1ii2e./u9c8sbjqp8i90dh6hi', PASSWORD_BCRYPT, array('cost' => 5), true),
+        );
+    }
+
+    public function testFuncExists() {
+        $this->assertTrue(function_exists('password_needs_rehash'));
+    }
+
+    /**
+     * @dataProvider provideCases
+     */
+    public function testCases($hash, $algo, $options, $valid) {
+        $this->assertEquals($valid, password_needs_rehash($hash, $algo, $options));
+    }
+
+}
diff --git a/lib/password_compat/tests/PasswordVerifyTest.php b/lib/password_compat/tests/PasswordVerifyTest.php
new file mode 100644 (file)
index 0000000..80b007b
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+global $CFG;
+require_once($CFG->dirroot . '/lib/password_compat/lib/password.php');
+
+class PasswordVerifyTest extends PHPUnit_Framework_TestCase {
+
+    protected function setUp() {
+        if (password_compat_not_supported()) {
+            // Skip test if password_compat is not supported.
+            $this->markTestSkipped('password_compat not supported');
+        }
+    }
+
+    public function testFuncExists() {
+        $this->assertTrue(function_exists('password_verify'));
+    }
+
+    public function testFailedType() {
+        $this->assertFalse(password_verify(123, 123));
+    }
+
+    public function testSaltOnly() {
+        $this->assertFalse(password_verify('foo', '$2a$07$usesomesillystringforsalt$'));
+    }
+
+    public function testInvalidPassword() {
+        $this->assertFalse(password_verify('rasmusler', '$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi'));
+    }
+
+    public function testValidPassword() {
+        $this->assertTrue(password_verify('rasmuslerdorf', '$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi'));
+    }
+
+    public function testInValidHash() {
+        $this->assertFalse(password_verify('rasmuslerdorf', '$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hj'));
+    }
+
+}
index 94031b9..560d7fe 100644 (file)
@@ -189,8 +189,6 @@ error_reporting($CFG->debug);
 ini_set('display_errors', '1');
 ini_set('log_errors', '1');
 
-$CFG->passwordsaltmain = 'phpunit'; // makes login via normal UI impossible
-
 $CFG->noemailever = true; // better not mail anybody from tests, override temporarily if necessary
 $CFG->cachetext = 0; // disable this very nasty setting
 
index 88fd5fd..68c63b7 100644 (file)
@@ -1140,7 +1140,7 @@ function disable_output_buffering() {
  */
 function redirect_if_major_upgrade_required() {
     global $CFG;
-    $lastmajordbchanges = 2012110201;
+    $lastmajordbchanges = 2013020900;
     if (empty($CFG->version) or (int)$CFG->version < $lastmajordbchanges or
             during_initial_install() or !empty($CFG->adminsetuppending)) {
         try {
index 45f1fa4..703d20e 100644 (file)
@@ -2245,4 +2245,118 @@ class moodlelib_testcase extends advanced_testcase {
         set_config('phpunit_test_get_config_4', 'test c', 'mod_forum');
         $this->assertFalse($cache->get('mod_forum'));
     }
+
+    /**
+     * Test function password_is_legacy_hash().
+     */
+    public function test_password_is_legacy_hash() {
+        // Well formed md5s should be matched.
+        foreach (array('some', 'strings', 'to_check!') as $string) {
+            $md5 = md5($string);
+            $this->assertTrue(password_is_legacy_hash($md5));
+        }
+        // Strings that are not md5s should not be matched.
+        foreach (array('', AUTH_PASSWORD_NOT_CACHED, 'IPW8WTcsWNgAWcUS1FBVHegzJnw5M2jOmYkmfc8z.xdBOyC4Caeum') as $notmd5) {
+            $this->assertFalse(password_is_legacy_hash($notmd5));
+        }
+    }
+
+    /**
+     * Test function validate_internal_user_password().
+     */
+    public function test_validate_internal_user_password() {
+        if (password_compat_not_supported()) {
+            // If bcrypt is not properly supported test legacy md5 hashes instead.
+            // Can't hardcode these as we don't know the site's password salt.
+            $validhashes = array(
+                'pw' => hash_internal_user_password('pw'),
+                'abc' => hash_internal_user_password('abc'),
+                'C0mP1eX_&}<?@*&%` |\"' => hash_internal_user_password('C0mP1eX_&}<?@*&%` |\"'),
+                'ĩńťėŕňăţĩōŋāĹ' => hash_internal_user_password('ĩńťėŕňăţĩōŋāĹ')
+            );
+        } else {
+            // Otherwise test bcrypt hashes.
+            $validhashes = array(
+                'pw' => '$2y$10$LOSDi5eaQJhutSRun.OVJ.ZSxQZabCMay7TO1KmzMkDMPvU40zGXK',
+                'abc' => '$2y$10$VWTOhVdsBbWwtdWNDRHSpewjd3aXBQlBQf5rBY/hVhw8hciarFhXa',
+                'C0mP1eX_&}<?@*&%` |\"' => '$2y$10$3PJf.q.9ywNJlsInPbqc8.IFeSsvXrGvQLKRFBIhVu1h1I3vpIry6',
+                'ĩńťėŕňăţĩōŋāĹ' => '$2y$10$3A2Y8WpfRAnP3czJiSv6N.6Xp0T8hW3QZz2hUCYhzyWr1kGP1yUve'
+            );
+        }
+
+        foreach ($validhashes as $password => $hash) {
+            $user = new stdClass();
+            $user->auth = 'manual';
+            $user->password = $hash;
+            // The correct password should be validated.
+            $this->assertTrue(validate_internal_user_password($user, $password));
+            // An incorrect password should not be validated.
+            $this->assertFalse(validate_internal_user_password($user, 'badpw'));
+        }
+    }
+
+    /**
+     * Test function hash_internal_user_password().
+     */
+    public function test_hash_internal_user_password() {
+        $passwords = array('pw', 'abc123', 'C0mP1eX_&}<?@*&%` |\"', 'ĩńťėŕňăţĩōŋāĹ');
+
+        // Check that some passwords that we convert to hashes can
+        // be validated.
+        foreach ($passwords as $password) {
+            $hash = hash_internal_user_password($password);
+            $fasthash = hash_internal_user_password($password, true);
+            $user = new stdClass();
+            $user->auth = 'manual';
+            $user->password = $hash;
+            $this->assertTrue(validate_internal_user_password($user, $password));
+
+            if (password_compat_not_supported()) {
+                // If bcrypt is not properly supported make sure the passwords are in md5 format.
+                $this->assertTrue(password_is_legacy_hash($hash));
+            } else {
+                // Otherwise they should not be in md5 format.
+                $this->assertFalse(password_is_legacy_hash($hash));
+
+                // Check that cost factor in hash is correctly set.
+                $this->assertRegExp('/\$10\$/', $hash);
+                $this->assertRegExp('/\$04\$/', $fasthash);
+            }
+        }
+    }
+
+    /**
+     * Test function update_internal_user_password().
+     */
+    public function test_update_internal_user_password() {
+        global $DB;
+        $this->resetAfterTest();
+        $passwords = array('password', '1234', 'changeme', '****');
+        foreach ($passwords as $password) {
+            $user = $this->getDataGenerator()->create_user(array('auth'=>'manual'));
+            update_internal_user_password($user, $password);
+            // The user object should have been updated.
+            $this->assertTrue(validate_internal_user_password($user, $password));
+            // The database field for the user should also have been updated to the
+            // same value.
+            $this->assertEquals($user->password, $DB->get_field('user', 'password', array('id' => $user->id)));
+        }
+
+        $user = $this->getDataGenerator()->create_user(array('auth'=>'manual'));
+        // Manually set the user's password to the md5 of the string 'password'.
+        $DB->set_field('user', 'password', '5f4dcc3b5aa765d61d8327deb882cf99', array('id' => $user->id));
+
+        // Update the password.
+        update_internal_user_password($user, 'password');
+
+        if (password_compat_not_supported()) {
+            // If bcrypt not properly supported the password should remain as an md5 hash.
+            $expected_hash = hash_internal_user_password('password', true);
+            $this->assertEquals($user->password, $expected_hash);
+            $this->assertTrue(password_is_legacy_hash($user->password));
+        } else {
+            // Otherwise password should have been updated to a bcrypt hash.
+            $this->assertFalse(password_is_legacy_hash($user->password));
+        }
+    }
 }
index d1aebe2..e6c34b6 100644 (file)
@@ -38,6 +38,7 @@
             <directory suffix="_test.php">lib/tests</directory>
             <directory suffix="_test.php">lib/ajax/tests</directory>
             <directory suffix="_test.php">lib/form/tests</directory>
+            <directory>lib/password_compat/tests</directory>
         </testsuite>
         <testsuite name="core_files">
             <directory suffix="_test.php">lib/filestorage/tests</directory>
index 8411092..71cdb0a 100644 (file)
@@ -94,15 +94,6 @@ Do not make the requirements too strict though, as this can result in users not
 $string['check_passwordpolicy_error'] = 'Password policy not set.';
 $string['check_passwordpolicy_name'] = 'Password policy';
 $string['check_passwordpolicy_ok'] = 'Password policy enabled.';
-$string['check_passwordsaltmain_details'] = '<p>Setting a password salt greatly reduces the risk of password theft.</p>
-<p>To set a password salt, add the following line to your config.php file:</p>
-<code>$CFG->passwordsaltmain = \'some long random string here with lots of characters\';</code>
-<p>The random string of characters should be a mix of letters, numbers and other characters. A string length of at least 40 characters is recommended.</p>
-<p>Please refer to the <a href="{$a}" target="_blank">password salting documentation</a> if you wish to change the password salt. Once set, do NOT delete your password salt otherwise you will no longer be able to login to your site!</p>';
-$string['check_passwordsaltmain_name'] = 'Password salt';
-$string['check_passwordsaltmain_ok'] = 'Password salt is OK';
-$string['check_passwordsaltmain_warning'] = 'No password salt has been set';
-$string['check_passwordsaltmain_weak'] = 'Password salt is weak';
 $string['check_riskadmin_detailsok'] = '<p>Please verify the following list of system administrators:</p>{$a}';
 $string['check_riskadmin_detailswarning'] = '<p>Please verify the following list of system administrators:</p>{$a->admins}
 <p>It is recommended to assign administrator role in the system context only. The following users have (unsupported) admin role assignments in other contexts:</p>{$a->unsupported}';
index a4e16d6..d6e6bef 100644 (file)
@@ -48,7 +48,6 @@ function report_security_get_issue_list() {
         'report_security_check_openprofiles',
         'report_security_check_google',
         'report_security_check_passwordpolicy',
-        'report_security_check_passwordsaltmain',
         'report_security_check_emailchangeconfirmation',
         'report_security_check_cookiesecure',
         'report_security_check_configrw',
@@ -471,35 +470,6 @@ function report_security_check_configrw($detailed=false) {
     return $result;
 }
 
-function report_security_check_passwordsaltmain($detailed=false) {
-    global $CFG;
-
-    $result = new stdClass();
-    $result->issue   = 'report_security_check_passwordsaltmain';
-    $result->name    = get_string('check_passwordsaltmain_name', 'report_security');
-    $result->info    = null;
-    $result->details = null;
-    $result->status  = null;
-    $result->link    = null;
-
-    if (empty($CFG->passwordsaltmain)) {
-        $result->status = REPORT_SECURITY_WARNING;
-        $result->info   = get_string('check_passwordsaltmain_warning', 'report_security');
-    } else if ($CFG->passwordsaltmain === 'some long random string here with lots of characters'
-            || trim($CFG->passwordsaltmain) === '' || preg_match('/^([a-z0-9]{0,10})$/i', $CFG->passwordsaltmain)) {
-        $result->status = REPORT_SECURITY_WARNING;
-        $result->info   = get_string('check_passwordsaltmain_weak', 'report_security');
-    } else {
-        $result->status = REPORT_SECURITY_OK;
-        $result->info   = get_string('check_passwordsaltmain_ok', 'report_security');
-    }
-
-    if ($detailed) {
-        $result->details = get_string('check_passwordsaltmain_details', 'report_security', get_docs_url('report/security/report_security_check_passwordsaltmain'));
-    }
-
-    return $result;
-}
 
 /**
  * Lists all users with XSS risk, it would be great to combine this with risk trusts in user table,
index b867e43..2c24264 100644 (file)
@@ -30,7 +30,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 
-$version  = 2013020800.00;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2013020900.00;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes