diff --git a/.eslintignore b/.eslintignore index 4c89fdbe7c6..2ec29c42873 100644 --- a/.eslintignore +++ b/.eslintignore @@ -24,7 +24,6 @@ lib/htmlpurifier/ lib/jabber/ lib/minify/ lib/flowplayer/ -lib/password_compat/ lib/pear/Auth/RADIUS.php lib/pear/Crypt/CHAP.php lib/pear/HTML/Common.php diff --git a/auth/db/auth.php b/auth/db/auth.php index b1fa0918490..8048a3039c6 100644 --- a/auth/db/auth.php +++ b/auth/db/auth.php @@ -136,7 +136,6 @@ class auth_plugin_db extends auth_plugin_base { } else if ($this->config->passtype === 'sha1') { return (strtolower($fromdb) == sha1($extpassword)); } else if ($this->config->passtype === 'saltedcrypt') { - require_once($CFG->libdir.'/password_compat/lib/password.php'); return password_verify($extpassword, $fromdb); } else { return false; diff --git a/auth/db/tests/db_test.php b/auth/db/tests/db_test.php index f1471cf0714..09a287e706e 100644 --- a/auth/db/tests/db_test.php +++ b/auth/db/tests/db_test.php @@ -308,7 +308,6 @@ class auth_db_testcase extends advanced_testcase { $DB->update_record('auth_db_users', $user3); $this->assertTrue($auth->user_login('u3', 'heslo')); - require_once($CFG->libdir.'/password_compat/lib/password.php'); set_config('passtype', 'saltedcrypt', 'auth/db'); $auth->config->passtype = 'saltedcrypt'; $user3->pass = password_hash('heslo', PASSWORD_BCRYPT); diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 4a15f63d16c..411499f1ae5 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -4376,7 +4376,6 @@ function password_is_legacy_hash($password) { */ function validate_internal_user_password($user, $password) { global $CFG; - require_once($CFG->libdir.'/password_compat/lib/password.php'); if ($user->password === AUTH_PASSWORD_NOT_CACHED) { // Internal password is not used at all, it can not validate. @@ -4437,7 +4436,6 @@ function validate_internal_user_password($user, $password) { */ function hash_internal_user_password($password, $fasthash = false) { global $CFG; - require_once($CFG->libdir.'/password_compat/lib/password.php'); // Set the cost factor to 4 for fast hashing, otherwise use default cost. $options = ($fasthash) ? array('cost' => 4) : array(); @@ -4473,7 +4471,6 @@ function hash_internal_user_password($password, $fasthash = false) { */ function update_internal_user_password($user, $password, $fasthash = false) { global $CFG, $DB; - require_once($CFG->libdir.'/password_compat/lib/password.php'); // Figure out what the hashed password should be. if (!isset($user->auth)) { diff --git a/lib/password_compat/lib/password.php b/lib/password_compat/lib/password.php index cc6896c1d0e..d5b178e7968 100644 --- a/lib/password_compat/lib/password.php +++ b/lib/password_compat/lib/password.php @@ -1,314 +1,29 @@ . + /** - * A Compatibility library with PHP 5.5's simplified password hashing API. + * Deprecation notice for password_compat. * - * @author Anthony Ferrara - * @license http://www.opensource.org/licenses/mit-license.html MIT License - * @copyright 2012 The Authors + * @package core + * @copyright 2016 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace { +defined('MOODLE_INTERNAL') || die(); - if (!defined('PASSWORD_BCRYPT')) { - /** - * PHPUnit Process isolation caches constants, but not function declarations. - * So we need to check if the constants are defined separately from - * the functions to enable supporting process isolation in userland - * code. - */ - define('PASSWORD_BCRYPT', 1); - define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); - define('PASSWORD_BCRYPT_DEFAULT_COST', 10); - } - - if (!function_exists('password_hash')) { - - /** - * 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_null($password) || is_int($password)) { - $password = (string) $password; - } - 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; - } - $resultLength = 0; - switch ($algo) { - case PASSWORD_BCRYPT: - $cost = PASSWORD_BCRYPT_DEFAULT_COST; - 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; - } - } - // The length of salt to generate - $raw_salt_len = 16; - // The length required in the final serialization - $required_salt_len = 22; - $hash_format = sprintf("$2y$%02d$", $cost); - // The expected length of the final crypt() output - $resultLength = 60; - break; - default: - trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); - return null; - } - $salt_requires_encoding = false; - 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 (PasswordCompat\binary\_strlen($salt) < $required_salt_len) { - trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING); - return null; - } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { - $salt_requires_encoding = true; - } - } else { - $buffer = ''; - $buffer_valid = false; - if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { - $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); - if ($buffer) { - $buffer_valid = true; - } - } - if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { - $buffer = openssl_random_pseudo_bytes($raw_salt_len); - if ($buffer) { - $buffer_valid = true; - } - } - if (!$buffer_valid && @is_readable('/dev/urandom')) { - $f = fopen('/dev/urandom', 'r'); - $read = PasswordCompat\binary\_strlen($buffer); - while ($read < $raw_salt_len) { - $buffer .= fread($f, $raw_salt_len - $read); - $read = PasswordCompat\binary\_strlen($buffer); - } - fclose($f); - if ($read >= $raw_salt_len) { - $buffer_valid = true; - } - } - if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) { - $bl = PasswordCompat\binary\_strlen($buffer); - for ($i = 0; $i < $raw_salt_len; $i++) { - if ($i < $bl) { - $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); - } else { - $buffer .= chr(mt_rand(0, 255)); - } - } - } - $salt = $buffer; - $salt_requires_encoding = true; - } - if ($salt_requires_encoding) { - // encode string with the Base64 variant used by crypt - $base64_digits = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - $bcrypt64_digits = - './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - - $base64_string = base64_encode($salt); - $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits); - } - $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len); - - $hash = $hash_format . $salt; - - $ret = crypt($password, $hash); - - if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) { - 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' => PASSWORD_BCRYPT_DEFAULT_COST, - * ), - * ) - * - * @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 (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_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'] : PASSWORD_BCRYPT_DEFAULT_COST; - 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) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) { - return false; - } - - $status = 0; - for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) { - $status |= (ord($ret[$i]) ^ ord($hash[$i])); - } - - return $status === 0; - } - } - -} - -namespace PasswordCompat\binary { - - if (!function_exists('PasswordCompat\\binary\\_strlen')) { - - /** - * Count the number of bytes in a string - * - * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension. - * In this case, strlen() will count the number of *characters* based on the internal encoding. A - * sequence of bytes might be regarded as a single multibyte character. - * - * @param string $binary_string The input string - * - * @internal - * @return int The number of bytes - */ - function _strlen($binary_string) { - if (function_exists('mb_strlen')) { - return mb_strlen($binary_string, '8bit'); - } - return strlen($binary_string); - } - - /** - * Get a substring based on byte limits - * - * @see _strlen() - * - * @param string $binary_string The input string - * @param int $start - * @param int $length - * - * @internal - * @return string The substring - */ - function _substr($binary_string, $start, $length) { - if (function_exists('mb_substr')) { - return mb_substr($binary_string, $start, $length, '8bit'); - } - return substr($binary_string, $start, $length); - } - - /** - * Check if current PHP version is compatible with the library - * - * @return boolean the check result - */ - function check() { - static $pass = NULL; - - if (is_null($pass)) { - if (function_exists('crypt')) { - $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG'; - $test = crypt("password", $hash); - $pass = $test == $hash; - } else { - $pass = false; - } - } - return $pass; - } - - } -} \ No newline at end of file +debugging('password_compat is now standard in all versions of PHP that Moodle supports. ' + . 'You no longer need to include the password_compat polyfill.', + DEBUG_DEVELOPER); diff --git a/lib/password_compat/readme_moodle.txt b/lib/password_compat/readme_moodle.txt deleted file mode 100644 index 4e0707234e0..00000000000 --- a/lib/password_compat/readme_moodle.txt +++ /dev/null @@ -1,33 +0,0 @@ -Description of password_compat import into Moodle: -================================================== - -Imported from: https://github.com/ircmaxell/password_compat/releases/tag/v1.0.4 -Copyright: (c) 2012 Anthony Ferrara -License: MIT License - -Files used from the library: -* lib/password.php > lib/password.php -* test/Unit/* > tests/ - -Added: -* None. - -Our changes: -* Added the following require_once() to the test files: - global $CFG; - require_once($CFG->dirroot . '/lib/password_compat/lib/password.php'); -* tests/PasswordHashTest.php supresses debugging from using salt in password_hash() - see MDL-52283 - -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 deleted file mode 100644 index e74567416b5..00000000000 --- a/lib/password_compat/tests/PasswordGetInfoTest.php +++ /dev/null @@ -1,29 +0,0 @@ -dirroot . '/lib/password_compat/lib/password.php'); - -class PasswordGetInfoTest extends PHPUnit_Framework_TestCase { - - 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 deleted file mode 100644 index 261d6df368e..00000000000 --- a/lib/password_compat/tests/PasswordHashTest.php +++ /dev/null @@ -1,101 +0,0 @@ -dirroot . '/lib/password_compat/lib/password.php'); - -class PasswordHashTest extends PHPUnit_Framework_TestCase { - - 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))); - if (version_compare(PHP_VERSION, '5.5.0', '<')) { - $this->assertEquals('$2y$10$KRGxLBS0Lxe3KBCwKxOzLexLDeu0ZfqJAKTubOfy7O/yL2hjimw3u', $hash); - } else { - $this->assertEquals('$2y$10$MTIzNDU2Nzg5MDEyMzQ1Nej0NmcAWSLR.oP7XOR9HD/vjUuOj100y', $hash); - } - } - - public function testNullBehavior() { - $hash = @password_hash(null, PASSWORD_BCRYPT, array("salt" => "1234567890123456789012345678901234567890")); - $this->assertEquals('$2y$10$123456789012345678901uhihPb9QpE2n03zMu9TDdvO34jDn6mO.', $hash); - } - - public function testIntegerBehavior() { - $hash = @password_hash(12345, PASSWORD_BCRYPT, array("salt" => "1234567890123456789012345678901234567890")); - $this->assertEquals('$2y$10$123456789012345678901ujczD5TiARVFtc68bZCAlbEg1fCIexfO', $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 deleted file mode 100644 index 1d003606829..00000000000 --- a/lib/password_compat/tests/PasswordNeedsRehashTest.php +++ /dev/null @@ -1,29 +0,0 @@ -dirroot . '/lib/password_compat/lib/password.php'); - -class PasswordNeedsRehashTest extends PHPUnit_Framework_TestCase { - - 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 deleted file mode 100644 index 3175e8901f3..00000000000 --- a/lib/password_compat/tests/PasswordVerifyTest.php +++ /dev/null @@ -1,32 +0,0 @@ -dirroot . '/lib/password_compat/lib/password.php'); - -class PasswordVerifyTest extends PHPUnit_Framework_TestCase { - - 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')); - } - -} diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index 6440a734fea..602599ebe41 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -84,13 +84,6 @@ 3.2.18 3.0+ - - password_compat - Compatible password hashing - MIT - 1.0.4 - - pear/Auth/RADIUS.php Pear_Auth_Radius diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 91d8d51fef0..7a542f68a9c 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -23,6 +23,7 @@ information provided here is intended especially for developers. * The following functions have been deprecated and are not used any more: - get_records_csv() Please use csv_import_reader::load_csv_content() instead. - put_records_csv() Please use download_as_dataformat (lib/dataformatlib.php) instead. +* The password_compat library was removed as it is no longer required. === 3.1 === diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9e305afadf6..f0488d80f90 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -44,7 +44,6 @@ lib/tests lib/ajax/tests - lib/password_compat/tests lib/form/tests diff --git a/user/lib.php b/user/lib.php index 01655353029..910bf5cf180 100644 --- a/user/lib.php +++ b/user/lib.php @@ -953,7 +953,6 @@ function user_get_user_navigation_info($user, $page, $options = array()) { */ function user_add_password_history($userid, $password) { global $CFG, $DB; - require_once($CFG->libdir.'/password_compat/lib/password.php'); if (empty($CFG->passwordreuselimit) or $CFG->passwordreuselimit < 0) { return; @@ -992,7 +991,6 @@ function user_add_password_history($userid, $password) { */ function user_is_previously_used_password($userid, $password) { global $CFG, $DB; - require_once($CFG->libdir.'/password_compat/lib/password.php'); if (empty($CFG->passwordreuselimit) or $CFG->passwordreuselimit < 0) { return false;