? cron-timing-131536-34.patch
? fieldset_html_in_title.patch
? node_delete_link.patch
? node_welcome_page-126221-17.patch
? node_welcome_page-126221-18.patch
? pluggable-pass-smtp-259103-27.patch
? pluggable-pass-smtp-259103-27a.patch
? sites/default/files
? sites/default/settings.php
Index: includes/mail.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/mail.inc,v
retrieving revision 1.16
diff -u -p -r1.16 mail.inc
--- includes/mail.inc 6 Oct 2008 11:00:01 -0000 1.16
+++ includes/mail.inc 30 Oct 2008 01:32:31 -0000
@@ -9,7 +9,7 @@
* appropriate places in the template. Processed e-mail templates are
* requested from hook_mail() from the module sending the e-mail. Any module
* can modify the composed e-mail message array using hook_mail_alter().
- * Finally drupal_mail_send() sends the e-mail, which can be reused
+ * Finally drupal_smtp()->send() sends the e-mail, which can be reused
* if the exact same composed e-mail is to be sent to multiple recipients.
*
* Finding out what language to send the e-mail with needs some consideration.
@@ -72,7 +72,7 @@
* @param $from
* Sets From, Reply-To, Return-Path and Error-To to this value, if given.
* @param $send
- * Send the message directly, without calling drupal_mail_send() manually.
+ * Send the message directly, without a manual call to drupal_smtp()->send().
* @return
* The $message array structure containing all details of the
* message. If already sent ($send = TRUE), then the 'result' element
@@ -127,7 +127,7 @@ function drupal_mail($module, $key, $to,
// Optionally send e-mail.
if ($send) {
- $message['result'] = drupal_mail_send($message);
+ $message['result'] = drupal_smtp($module, $key)->send($message);
// Log errors
if (!$message['result']) {
@@ -139,11 +139,104 @@ function drupal_mail($module, $key, $to,
return $message;
}
+class DrupalMailSend implements DrupalSmtpInterface {
+ /**
+ * Send an e-mail message, using Drupal variables and default settings.
+ * More information in the
+ * PHP function reference for mail(). See drupal_mail() for information on
+ * how $message is composed.
+ *
+ * @param $message
+ * Message array with at least the following elements:
+ * - id
+ * A unique identifier of the e-mail type. Examples: 'contact_user_copy',
+ * 'user_password_reset'.
+ * - to
+ * The mail address or addresses where the message will be sent to. The
+ * formatting of this string must comply with RFC 2822. Some examples are:
+ * user@example.com
+ * user@example.com, anotheruser@example.com
+ * User
+ * User , Another User
+ * - subject
+ * Subject of the e-mail to be sent. This must not contain any newline
+ * characters, or the mail may not be sent properly.
+ * - body
+ * Message to be sent. Accepts both CRLF and LF line-endings.
+ * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for
+ * smart plain text wrapping.
+ * - headers
+ * Associative array containing all mail headers.
+ * @return
+ * Returns TRUE if the mail was successfully accepted for delivery,
+ * FALSE otherwise.
+ */
+ public function send($message) {
+ $mimeheaders = array();
+ foreach ($message['headers'] as $name => $value) {
+ $mimeheaders[] = $name . ': ' . mime_header_encode($value);
+ }
+ return mail(
+ $message['to'],
+ mime_header_encode($message['subject']),
+ // Note: e-mail uses CRLF for line-endings, but PHP's API requires LF.
+ // They will appear correctly in the actual e-mail that is sent.
+ str_replace("\r", '', $message['body']),
+ // For headers, PHP's API suggests that we use CRLF normally,
+ // but some MTAs incorrecly replace LF with CRLF. See #234403.
+ join("\n", $mimeheaders)
+ );
+ }
+}
+
/**
- * Send an e-mail message, using Drupal variables and default settings.
- * More information in the
- * PHP function reference for mail(). See drupal_mail() for information on
- * how $message is composed.
+ * Returns an object that implements DrupalSmtpInterface..
+ *
+ * Allows for one or more custom mail backends to send mail messages
+ * composed using drupal_mail().
+ *
+ * @param $module
+ * The module name which was used by drupal_mail() to invoke hook_mail().
+ * @param $key
+ * A key to identify the e-mail sent. The final e-mail id for the e-mail
+ * alter hook in drupal_mail() would have been {$module}_{$key}.
+ */
+function drupal_smtp($module, $key) {
+ static $instance = array();
+
+ $id = $module . '_' . $key;
+ $config = variable_get('smtp_system', array('default' => 'DrupalMailSend'));
+
+ // Look for overrides for the default class, starting from the most specific
+ // id, and falling back to the module name.
+ if (isset($config[$id])) {
+ $class = $config[$id];
+ }
+ elseif (isset($config[$module])) {
+ $class = $config[$module];
+ }
+ else {
+ $class = $config['default'];
+ }
+
+ if (empty($instance[$class])) {
+ $interfaces = class_implements($class);
+ if (isset($interfaces['DrupalSmtpInterface'])) {
+ $instance[$class] = new $class;
+ }
+ else {
+ throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'DrupalSmtpInterface')));
+ }
+ }
+ return $instance[$class];
+}
+
+/**
+ * An interface for pluggable mail back-ends.
+ */
+interface DrupalSmtpInterface {
+/**
+ * Send an e-mail message composed by drupal_mail().
*
* @param $message
* Message array with at least the following elements:
@@ -170,28 +263,7 @@ function drupal_mail($module, $key, $to,
* Returns TRUE if the mail was successfully accepted for delivery,
* FALSE otherwise.
*/
-function drupal_mail_send($message) {
- // Allow for a custom mail backend.
- if (variable_get('smtp_library', '') && file_exists(variable_get('smtp_library', ''))) {
- include_once DRUPAL_ROOT . '/' . variable_get('smtp_library', '');
- return drupal_mail_wrapper($message);
- }
- else {
- $mimeheaders = array();
- foreach ($message['headers'] as $name => $value) {
- $mimeheaders[] = $name . ': ' . mime_header_encode($value);
- }
- return mail(
- $message['to'],
- mime_header_encode($message['subject']),
- // Note: e-mail uses CRLF for line-endings, but PHP's API requires LF.
- // They will appear correctly in the actual e-mail that is sent.
- str_replace("\r", '', $message['body']),
- // For headers, PHP's API suggests that we use CRLF normally,
- // but some MTAs incorrecly replace LF with CRLF. See #234403.
- join("\n", $mimeheaders)
- );
- }
+ public function send($message);
}
/**
Index: includes/password.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/password.inc,v
retrieving revision 1.3
diff -u -p -r1.3 password.inc
--- includes/password.inc 26 May 2008 17:12:54 -0000 1.3
+++ includes/password.inc 30 Oct 2008 01:32:32 -0000
@@ -9,235 +9,228 @@
* @see http://www.openwall.com/phpass/
*
* An alternative or custom version of this password hashing API may be
- * used by setting the variable password_inc to the name of the PHP file
- * containing replacement user_hash_password(), user_check_password(), and
- * user_needs_new_hash() functions.
- */
-
-/**
- * The standard log2 number of iterations for password stretching. This should
- * increase by 1 at least every other Drupal version in order to counteract
- * increases in the speed and power of computers available to crack the hashes.
- */
-define('DRUPAL_HASH_COUNT', 14);
-
-/**
- * The minimum allowed log2 number of iterations for password stretching.
- */
-define('DRUPAL_MIN_HASH_COUNT', 7);
-
-/**
- * The maximum allowed log2 number of iterations for password stretching.
- */
-define('DRUPAL_MAX_HASH_COUNT', 30);
-
-/**
- * Returns a string for mapping an int to the corresponding base 64 character.
- */
-function _password_itoa64() {
- return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
-}
+ * used by setting the variable password_system to the name of a class
+ * that implements interface PasswordInterface, with hash(), check(),
+ * needs_new_hash(), and set_hash_strength() methods.
+ */
+class UserPassword implements PasswordInterface {
+ /**
+ * The standard log2 number of iterations for password stretching. This should
+ * increase by 1 at least every other Drupal version in order to counteract
+ * increases in the speed and power of computers available to crack the hashes.
+ */
+ const DEFAULT_HASH_COUNT = 14;
+
+ /**
+ * The minimum allowed log2 number of iterations for password stretching.
+ */
+ const MIN_HASH_COUNT = 7;
+
+ /**
+ * The maximum allowed log2 number of iterations for password stretching.
+ */
+ const MAX_HASH_COUNT = 30;
+
+ /**
+ * Returns a string for mapping an int to the corresponding base 64 character.
+ */
+ protected $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+
+ /**
+ * The log2 number of iterations to use when generating a hash.
+ */
+ protected $countLog2Setting = self::DEFAULT_HASH_COUNT;
+
+ /**
+ * Encode bytes into printable base 64 using the *nix standard from crypt().
+ *
+ * @param $input
+ * The string containing bytes to encode.
+ * @param $count
+ * The number of characters (bytes) to encode.
+ *
+ * @return
+ * Encoded string
+ */
+ private function base64Encode($input, $count) {
+ $output = '';
+ $i = 0;
+ do {
+ $value = ord($input[$i++]);
+ $output .= $this->itoa64[$value & 0x3f];
+ if ($i < $count) {
+ $value |= ord($input[$i]) << 8;
+ }
+ $output .= $this->itoa64[($value >> 6) & 0x3f];
+ if ($i++ >= $count) {
+ break;
+ }
+ if ($i < $count) {
+ $value |= ord($input[$i]) << 16;
+ }
+ $output .= $this->itoa64[($value >> 12) & 0x3f];
+ if ($i++ >= $count) {
+ break;
+ }
+ $output .= $this->itoa64[($value >> 18) & 0x3f];
+ } while ($i < $count);
+
+ return $output;
+ }
+
+ /**
+ * Generates a random base 64-encoded salt prefixed with settings for the hash.
+ *
+ * Proper use of salts may defeat a number of attacks, including:
+ * - The ability to try candidate passwords against multiple hashes at once.
+ * - The ability to use pre-hashed lists of candidate passwords.
+ * - The ability to determine whether two users have the same (or different)
+ * password without actually having to guess one of the passwords.
+ *
+ * @return
+ * A 12 character string containing the iteration count and a random salt.
+ */
+ private function generateSalt() {
+ $output = '$P$';
+ // Minimum log2 iterations is DRUPAL_MIN_HASH_COUNT.
+ $count_log2 = max($this->countLog2Setting, self::MIN_HASH_COUNT);
+ // Maximum log2 iterations is DRUPAL_MAX_HASH_COUNT.
+ // We encode the final log2 iteration count in base 64.
+ $output .= $this->itoa64[min($count_log2, self::MAX_HASH_COUNT)];
+ // 6 bytes is the standard salt for a portable phpass hash.
+ $output .= $this->base64Encode(drupal_random_bytes(6), 6);
+ return $output;
+ }
+
+ /**
+ * Hash a password using a secure stretched hash.
+ *
+ * By using a salt and repeated hashing the password is "stretched". Its
+ * security is increased because it becomes much more computationally costly
+ * for an attacker to try to break the hash by brute-force computation of the
+ * hashes of a large number of plain-text words or strings to find a match.
+ *
+ * @param $password
+ * The plain-text password to hash.
+ * @param $setting
+ * An existing hash or the output of generate_salt().
+ *
+ * @return
+ * A string containing the hashed password (and salt) or FALSE on failure.
+ */
+ private function passwordCrypt($password, $setting) {
+ // The first 12 characters of an existing hash are its setting string.
+ $setting = substr($setting, 0, 12);
-/**
- * Encode bytes into printable base 64 using the *nix standard from crypt().
- *
- * @param $input
- * The string containing bytes to encode.
- * @param $count
- * The number of characters (bytes) to encode.
- *
- * @return
- * Encoded string
- */
-function _password_base64_encode($input, $count) {
- $output = '';
- $i = 0;
- $itoa64 = _password_itoa64();
- do {
- $value = ord($input[$i++]);
- $output .= $itoa64[$value & 0x3f];
- if ($i < $count) {
- $value |= ord($input[$i]) << 8;
- }
- $output .= $itoa64[($value >> 6) & 0x3f];
- if ($i++ >= $count) {
- break;
- }
- if ($i < $count) {
- $value |= ord($input[$i]) << 16;
- }
- $output .= $itoa64[($value >> 12) & 0x3f];
- if ($i++ >= $count) {
- break;
+ if (substr($setting, 0, 3) != '$P$') {
+ return FALSE;
+ }
+ $count_log2 = $this->getCountLog2($setting);
+ // Hashes may be imported from elsewhere, so we allow != DEFAULT_HASH_COUNT
+ if ($count_log2 < self::MIN_HASH_COUNT || $count_log2 > self::MAX_HASH_COUNT) {
+ return FALSE;
+ }
+ $salt = substr($setting, 4, 8);
+ // Hashes must have an 8 character salt.
+ if (strlen($salt) != 8) {
+ return FALSE;
}
- $output .= $itoa64[($value >> 18) & 0x3f];
- } while ($i < $count);
-
- return $output;
-}
-
-/**
- * Generates a random base 64-encoded salt prefixed with settings for the hash.
- *
- * Proper use of salts may defeat a number of attacks, including:
- * - The ability to try candidate passwords against multiple hashes at once.
- * - The ability to use pre-hashed lists of candidate passwords.
- * - The ability to determine whether two users have the same (or different)
- * password without actually having to guess one of the passwords.
- *
- * @param $count_log2
- * Integer that determines the number of iterations used in the hashing
- * process. A larger value is more secure, but takes more time to complete.
- *
- * @return
- * A 12 character string containing the iteration count and a random salt.
- */
-function _password_generate_salt($count_log2) {
- $output = '$P$';
- // Minimum log2 iterations is DRUPAL_MIN_HASH_COUNT.
- $count_log2 = max($count_log2, DRUPAL_MIN_HASH_COUNT);
- // Maximum log2 iterations is DRUPAL_MAX_HASH_COUNT.
- // We encode the final log2 iteration count in base 64.
- $itoa64 = _password_itoa64();
- $output .= $itoa64[min($count_log2, DRUPAL_MAX_HASH_COUNT)];
- // 6 bytes is the standard salt for a portable phpass hash.
- $output .= _password_base64_encode(drupal_random_bytes(6), 6);
- return $output;
-}
-
-/**
- * Hash a password using a secure stretched hash.
- *
- * By using a salt and repeated hashing the password is "stretched". Its
- * security is increased because it becomes much more computationally costly
- * for an attacker to try to break the hash by brute-force computation of the
- * hashes of a large number of plain-text words or strings to find a match.
- *
- * @param $password
- * The plain-text password to hash.
- * @param $setting
- * An existing hash or the output of _password_generate_salt().
- *
- * @return
- * A string containing the hashed password (and salt) or FALSE on failure.
- */
-function _password_crypt($password, $setting) {
- // The first 12 characters of an existing hash are its setting string.
- $setting = substr($setting, 0, 12);
-
- if (substr($setting, 0, 3) != '$P$') {
- return FALSE;
- }
- $count_log2 = _password_get_count_log2($setting);
- // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
- if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) {
- return FALSE;
- }
- $salt = substr($setting, 4, 8);
- // Hashes must have an 8 character salt.
- if (strlen($salt) != 8) {
- return FALSE;
- }
-
- // We must use md5() or sha1() here since they are the only cryptographic
- // primitives always available in PHP 5. To implement our own low-level
- // cryptographic function in PHP would result in much worse performance and
- // consequently in lower iteration counts and hashes that are quicker to crack
- // (by non-PHP code).
-
- $count = 1 << $count_log2;
-
- $hash = md5($salt . $password, TRUE);
- do {
- $hash = md5($hash . $password, TRUE);
- } while (--$count);
-
- $output = $setting . _password_base64_encode($hash, 16);
- // _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
- return (strlen($output) == 34) ? $output : FALSE;
-}
-
-/**
- * Parse the log2 iteration count from a stored hash or setting string.
- */
-function _password_get_count_log2($setting) {
- $itoa64 = _password_itoa64();
- return strpos($itoa64, $setting[3]);
-}
-
-/**
- * Hash a password using a secure hash.
- *
- * @param $password
- * A plain-text password.
- * @param $count_log2
- * Optional integer to specify the iteration count. Generally used only during
- * mass operations where a value less than the default is needed for speed.
- *
- * @return
- * A string containing the hashed password (and a salt), or FALSE on failure.
- */
-function user_hash_password($password, $count_log2 = 0) {
- if (empty($count_log2)) {
- // Use the standard iteration count.
- $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT);
- }
- return _password_crypt($password, _password_generate_salt($count_log2));
-}
-/**
- * Check whether a plain text password matches a stored hashed password.
- *
- * Alternative implementations of this function may use other data in the
- * $account object, for example the uid to look up the hash in a custom table
- * or remote database.
- *
- * @param $password
- * A plain-text password
- * @param $account
- * A user object with at least the fields from the {users} table.
- *
- * @return
- * TRUE or FALSE.
- */
-function user_check_password($password, $account) {
- if (substr($account->pass, 0, 3) == 'U$P') {
- // This may be an updated password from user_update_7000(). Such hashes
- // have 'U' added as the first character and need an extra md5().
- $stored_hash = substr($account->pass, 1);
- $password = md5($password);
- }
- else {
- $stored_hash = $account->pass;
+ // We must use md5() or sha1() here since they are the only cryptographic
+ // primitives always available in PHP 5. To implement our own low-level
+ // cryptographic function in PHP would result in much worse performance and
+ // consequently in lower iteration counts and hashes that are quicker to crack
+ // (by non-PHP code).
+
+ $count = 1 << $count_log2;
+
+ $hash = md5($salt . $password, TRUE);
+ do {
+ $hash = md5($hash . $password, TRUE);
+ } while (--$count);
+
+ $output = $setting . $this->base64Encode($hash, 16);
+ // password_base64_encode() of a 16 byte MD5 will always be 22 characters.
+ return (strlen($output) == 34) ? $output : FALSE;
+ }
+
+ /**
+ * Parse the log2 iteration count from a stored hash or setting string.
+ */
+ private function getCountLog2($setting) {
+ return strpos($this->itoa64, $setting[3]);
+ }
+
+ /**
+ * Set the relative hash strength as compared to the default.
+ *
+ * @param $strength
+ * A positive number; 1.0 for the default. Typical range is 0.01 - 1000.0.
+ */
+ public function setHashStrength($strength) {
+ // We accept a linear scale strength and convert it to a base 2 logarithm.
+ $this->countLog2Setting = (int)(log($strength, 2) + self::DEFAULT_HASH_COUNT);
+ }
+
+ /**
+ * Hash a password using a secure hash.
+ *
+ * @param $password
+ * A plain-text password.
+ *
+ * @return
+ * A string containing the hashed password (and a salt), or FALSE on failure.
+ */
+ public function hash($password) {
+ return $this->passwordCrypt($password, $this->generateSalt());
+ }
+
+ /**
+ * Check whether a plain text password matches a stored hashed password.
+ *
+ * @param $password
+ * A plain-text password
+ * @param $account
+ * A user object with at least the fields from the {users} table.
+ *
+ * @return
+ * TRUE or FALSE.
+ */
+ public function check($password, $account) {
+ if (substr($account->pass, 0, 3) == 'U$P') {
+ // This may be an updated password from user_update_7000(). Such hashes
+ // have 'U' added as the first character and need an extra md5().
+ $stored_hash = substr($account->pass, 1);
+ $password = md5($password);
+ }
+ else {
+ $stored_hash = $account->pass;
+ }
+ $hash = $this->passwordCrypt($password, $stored_hash);
+ return ($hash && $stored_hash == $hash);
}
- $hash = _password_crypt($password, $stored_hash);
- return ($hash && $stored_hash == $hash);
-}
-/**
- * Check whether a user's hashed password needs to be replaced with a new hash.
- *
- * This is typically called during the login process when the plain text
- * password is available. A new hash is needed when the desired iteration count
- * has changed through a change in the variable password_count_log2 or
- * DRUPAL_HASH_COUNT or if the user's password hash was generated in an update
- * like user_update_7000().
- *
- * Alternative implementations of this function might use other criteria based
- * on the fields in $account.
- *
- * @param $account
- * A user object with at least the fields from the {users} table.
- *
- * @return
- * TRUE or FALSE.
- */
-function user_needs_new_hash($account) {
- // Check whether this was an updated password.
- if ((substr($account->pass, 0, 3) != '$P$') || (strlen($account->pass) != 34)) {
- return TRUE;
+ /**
+ * Check whether a user's hashed password needs to be replaced with a new hash.
+ *
+ * This is typically called during the login process when the plain text
+ * password is available. A new hash is needed when the desired iteration count
+ * has changed through a change in the variable password_hash_strength or
+ * DEFAULT_HASH_COUNT or if the user's password hash was generated in an update
+ * like user_update_7000().
+ *
+ * @param $account
+ * A user object with at least the fields from the {users} table.
+ *
+ * @return
+ * TRUE or FALSE.
+ */
+ public function needsNewHash($account) {
+ // Check whether this was an updated password.
+ if ((substr($account->pass, 0, 3) != '$P$') || (strlen($account->pass) != 34)) {
+ return TRUE;
+ }
+ // Check whether the iteration count used differs from the standard number.
+ return ($this->getCountLog2($account->pass) != $this->countLog2Setting);
}
- // Check whether the iteration count used differs from the standard number.
- return (_password_get_count_log2($account->pass) != variable_get('password_count_log2', DRUPAL_HASH_COUNT));
}
-
Index: modules/dblog/dblog.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/dblog/dblog.test,v
retrieving revision 1.9
diff -u -p -r1.9 dblog.test
--- modules/dblog/dblog.test 17 Sep 2008 07:11:56 -0000 1.9
+++ modules/dblog/dblog.test 30 Oct 2008 01:32:32 -0000
@@ -180,7 +180,7 @@ class DBLogTestCase extends DrupalWebTes
private function doUser() {
// Set user variables.
$name = $this->randomName();
- $pass = user_password();
+ $pass = user_generate_password();
// Add user using form to generate add user event (which is not triggered by drupalCreateUser).
$edit = array();
$edit['name'] = $name;
Index: modules/openid/openid.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/openid/openid.module,v
retrieving revision 1.32
diff -u -p -r1.32 openid.module
--- modules/openid/openid.module 26 Oct 2008 18:06:38 -0000 1.32
+++ modules/openid/openid.module 30 Oct 2008 01:32:32 -0000
@@ -120,7 +120,7 @@ function openid_form_alter(&$form, $form
// with random password to avoid confusion.
if (!variable_get('user_email_verification', TRUE)) {
$form['pass']['#type'] = 'hidden';
- $form['pass']['#value'] = user_password();
+ $form['pass']['#value'] = user_generate_password();
}
$form['auth_openid'] = array('#type' => 'hidden', '#value' => $_SESSION['openid']['values']['auth_openid']);
}
@@ -399,7 +399,7 @@ function openid_authentication($response
$form_state['redirect'] = NULL;
$form_state['values']['name'] = (empty($response['openid.sreg.nickname'])) ? $identity : $response['openid.sreg.nickname'];
$form_state['values']['mail'] = (empty($response['openid.sreg.email'])) ? '' : $response['openid.sreg.email'];
- $form_state['values']['pass'] = user_password();
+ $form_state['values']['pass'] = user_generate_password();
$form_state['values']['status'] = variable_get('user_register', 1) == 1;
$form_state['values']['response'] = $response;
$form = drupal_retrieve_form('user_register', $form_state);
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.52
diff -u -p -r1.52 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php 20 Oct 2008 13:06:15 -0000 1.52
+++ modules/simpletest/drupal_web_test_case.php 30 Oct 2008 01:32:32 -0000
@@ -542,7 +542,7 @@ class DrupalWebTestCase {
$edit['name'] = $this->randomName();
$edit['mail'] = $edit['name'] . '@example.com';
$edit['roles'] = array($rid => $rid);
- $edit['pass'] = user_password();
+ $edit['pass'] = user_generate_password();
$edit['status'] = 1;
$account = user_save('', $edit);
Index: modules/user/user.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.install,v
retrieving revision 1.13
diff -u -p -r1.13 user.install
--- modules/user/user.install 20 Sep 2008 20:22:25 -0000 1.13
+++ modules/user/user.install 30 Oct 2008 01:32:32 -0000
@@ -242,8 +242,7 @@ function user_schema() {
*/
function user_update_7000(&$sandbox) {
$ret = array('#finished' => 0);
- // Lower than DRUPAL_HASH_COUNT to make the update run at a reasonable speed.
- $hash_count_log2 = 11;
+
// Multi-part update.
if (!isset($sandbox['user_from'])) {
db_change_field($ret, 'users', 'pass', 'pass', array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''));
@@ -251,15 +250,16 @@ function user_update_7000(&$sandbox) {
$sandbox['user_count'] = db_result(db_query("SELECT COUNT(uid) FROM {users}"));
}
else {
- require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
// Hash again all current hashed passwords.
+ // Lower than default strength to make the update run at a better speed.
+ user_password()->set_hash_strength(0.1);
$has_rows = FALSE;
// Update this many per page load.
$count = 1000;
$result = db_query_range("SELECT uid, pass FROM {users} WHERE uid > 0 ORDER BY uid", $sandbox['user_from'], $count);
while ($account = db_fetch_array($result)) {
$has_rows = TRUE;
- $new_hash = user_hash_password($account['pass'], $hash_count_log2);
+ $new_hash = user_password()->hash($account['pass']);
if ($new_hash) {
// Indicate an updated password.
$new_hash = 'U' . $new_hash;
@@ -270,7 +270,7 @@ function user_update_7000(&$sandbox) {
$sandbox['user_from'] += $count;
if (!$has_rows) {
$ret['#finished'] = 1;
- $ret[] = array('success' => TRUE, 'query' => "UPDATE {users} SET pass = 'U' . user_hash_password(pass) WHERE uid > 0");
+ $ret[] = array('success' => TRUE, 'query' => "UPDATE {users} SET pass = 'U' . user_password()->hash(pass) WHERE uid > 0");
}
}
return $ret;
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.930
diff -u -p -r1.930 user.module
--- modules/user/user.module 26 Oct 2008 18:06:39 -0000 1.930
+++ modules/user/user.module 30 Oct 2008 01:32:33 -0000
@@ -221,9 +221,7 @@ function user_save($account, $edit = arr
$user_fields = $table['fields'];
if (!empty($edit['pass'])) {
- // Allow alternate password hashing schemes.
- require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
- $edit['pass'] = user_hash_password(trim($edit['pass']));
+ $edit['pass'] = user_password()->hash(trim($edit['pass']));
// Abort if the hashing failed and returned FALSE.
if (!$edit['pass']) {
return FALSE;
@@ -427,7 +425,7 @@ function user_validate_picture(&$form, &
/**
* Generate a random alphanumeric password.
*/
-function user_password($length = 10) {
+function user_generate_password($length = 10) {
// This variable contains the list of allowable characters for the
// password. Note that the number 0 and the letter 'O' have been
// removed to avoid confusion between the two. The same is true
@@ -1341,11 +1339,9 @@ function user_authenticate($form_values
if (!empty($form_values['name']) && !empty($password)) {
$account = db_fetch_object(db_query("SELECT * FROM {users} WHERE name = '%s' AND status = 1", $form_values['name']));
if ($account) {
- // Allow alternate password hashing schemes.
- require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
- if (user_check_password($password, $account)) {
- if (user_needs_new_hash($account)) {
- $new_hash = user_hash_password($password);
+ if (user_password()->check($password, $account)) {
+ if (user_password()->needsNewHash($account)) {
+ $new_hash = user_password()->hash($password);
if ($new_hash) {
db_query("UPDATE {users} SET pass = '%s' WHERE uid = %d", $new_hash, $account->uid);
}
@@ -1409,7 +1405,7 @@ function user_external_login_register($n
// Register this new user.
$userinfo = array(
'name' => $name,
- 'pass' => user_password(),
+ 'pass' => user_generate_password(),
'init' => $name,
'status' => 1,
'access' => REQUEST_TIME
@@ -2129,7 +2125,7 @@ function user_preferred_language($accoun
* @param $language
* Optional language to use for the notification, overriding account language.
* @return
- * The return value from drupal_mail_send(), if ends up being called.
+ * The return value from drupal_smtp()->send(), if it is called.
*/
function _user_mail_notify($op, $account, $language = NULL) {
// By default, we always notify except for deleted and blocked.
@@ -2261,7 +2257,7 @@ function user_register_submit($form, &$f
$pass = $form_state['values']['pass'];
}
else {
- $pass = user_password();
+ $pass = user_generate_password();
};
$notify = isset($form_state['values']['notify']) ? $form_state['values']['notify'] : NULL;
$from = variable_get('site_mail', ini_get('sendmail_from'));
@@ -2447,3 +2443,85 @@ function _user_forms(&$edit, $account, $
return empty($groups) ? FALSE : $groups;
}
+
+/**
+ * User module interfaces and their corresponding factory functions.
+ */
+
+/**
+ * Returns a password hashing object that implements PasswordInterface.
+ */
+function user_password() {
+ static $instance;
+
+ if (empty($instance)) {
+ $class = variable_get('password_system', 'UserPassword');
+ $interfaces = class_implements($class);
+ if (isset($interfaces['PasswordInterface'])) {
+ $instance = new $class;
+ // Set a default hash strength.
+ $instance->setHashStrength(variable_get('password_hash_strength', 1.0));
+ }
+ else {
+ throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'PasswordInterface')));
+ }
+ }
+ return $instance;
+}
+
+interface PasswordInterface {
+ /**
+ * Set the relative hash strength as compared to the default.
+ *
+ * @param $strength
+ * A positive number; 1.0 for the default. Typical range is 0.01 - 1000.0.
+ * Generally used only during mass operations where a value less than
+ * the default is needed for speed.
+ */
+ public function setHashStrength($strength);
+
+ /**
+ * Hash a password using a secure hash.
+ *
+ * @param $password
+ * A plain-text password.
+ *
+ * @return
+ * A string containing the hashed password, or FALSE on failure.
+ */
+ public function hash($password);
+
+ /**
+ * Check whether a plain text password matches a stored hashed password.
+ *
+ * Implementations of this function may use a variety of data in the
+ * $account object, for example the uid to look up the hash in a custom table
+ * or remote database.
+ *
+ * @param $password
+ * A plain-text password
+ * @param $account
+ * A user object with at least the fields from the {users} table.
+ *
+ * @return
+ * TRUE or FALSE.
+ */
+ public function check($password, $account);
+
+ /**
+ * Check whether a user's hashed password needs to be replaced with a new hash.
+ *
+ * This is typically called during the login process when the plain text
+ * password is available. A new hash is needed, for example, when the desired
+ * iteration count has changed. Implementations of this function might use a
+ * variety of criteria based on the fields in $account.
+ *
+ * @param $account
+ * A user object with at least the fields from the {users} table.
+ *
+ * @return
+ * TRUE or FALSE.
+ */
+ public function needsNewHash($account);
+}
+
Index: modules/user/user.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.test,v
retrieving revision 1.17
diff -u -p -r1.17 user.test
--- modules/user/user.test 10 Oct 2008 07:49:49 -0000 1.17
+++ modules/user/user.test 30 Oct 2008 01:32:33 -0000
@@ -67,7 +67,7 @@ class UserRegistrationTestCase extends D
$this->assertText(t('You have just used your one-time login link. It is no longer necessary to use this link to login. Please change your password.'), t('This link is no longer valid.'));
// Change user password.
- $new_pass = user_password();
+ $new_pass = user_generate_password();
$edit = array();
$edit['pass[pass1]'] = $new_pass;
$edit['pass[pass2]'] = $new_pass;
@@ -75,10 +75,8 @@ class UserRegistrationTestCase extends D
$this->assertText(t('The changes have been saved.'), t('Password changed to @password', array('@password' => $new_pass)));
// Make sure password changes are present in database.
- require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
-
$user = user_load(array('uid' => $user->uid));
- $this->assertTrue(user_check_password($new_pass, $user), t('Correct password in database.'));
+ $this->assertTrue(user_password()->check($new_pass, $user), t('Correct password in database.'));
// Logout of user account.
$this->clickLink(t('Log out'));
Index: scripts/password-hash.sh
===================================================================
RCS file: /cvs/drupal/drupal/scripts/password-hash.sh,v
retrieving revision 1.2
diff -u -p -r1.2 password-hash.sh
--- scripts/password-hash.sh 20 Sep 2008 20:22:25 -0000 1.2
+++ scripts/password-hash.sh 30 Oct 2008 01:32:33 -0000
@@ -85,11 +85,12 @@ while ($param = array_shift($_SERVER['ar
define('DRUPAL_ROOT', getcwd());
-include_once DRUPAL_ROOT . '/includes/password.inc';
+include_once DRUPAL_ROOT . '/modules/user/user.module';
include_once DRUPAL_ROOT . '/includes/common.inc';
+include_once DRUPAL_ROOT . '/includes/password.inc';
foreach ($passwords as $password) {
- print("\npassword: $password \t\thash: ". user_hash_password($password) ."\n");
+ print("\npassword: $password \t\thash: ". user_password()->hash($password) ."\n");
}
print("\n");