X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FUser.php;h=b09e4e456e8b3a1c46cd9bc1026029baf4b36a28;hb=b3acd4fb5d1263de5735c714a11f7a170777d1f1;hp=75649a788144dbb3d424519ea9f95ea633e47d9f;hpb=ebffed29d7034e3f1a1734dc8f8280d3ec84b5d7;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/User.php b/includes/User.php index 75649a7881..b09e4e456e 100644 --- a/includes/User.php +++ b/includes/User.php @@ -28,7 +28,7 @@ define( 'EDIT_TOKEN_SUFFIX', '+\\' ); /** * The User object encapsulates all of the user-specific settings (user_id, - * name, rights, password, email address, options, last login time). Client + * name, rights, email address, options, last login time). Client * classes use the getXXX() functions to access these fields. These functions * do all the work of determining whether the user is logged in, * whether the requested option can be satisfied from cookies or @@ -64,11 +64,6 @@ class User implements IDBAccessObject { */ const GETOPTIONS_EXCLUDE_DEFAULTS = 1; - /** - * @var PasswordFactory Lazily loaded factory object for passwords - */ - private static $mPasswordFactory = null; - /** * Array of Strings List of member variables which are saved to the * shared cache (memcached). Any operation which changes the @@ -190,20 +185,7 @@ class User implements IDBAccessObject { public $mName; /** @var string */ public $mRealName; - /** - * @todo Make this actually private - * @private - * @var Password - */ - public $mPassword; - /** - * @todo Make this actually private - * @private - * @var Password - */ - public $mNewpassword; - /** @var string */ - public $mNewpassTime; + /** @var string */ public $mEmail; /** @var string TS_MW timestamp from the DB */ @@ -226,8 +208,6 @@ class User implements IDBAccessObject { public $mGroups; /** @var array */ protected $mOptionOverrides; - /** @var string */ - protected $mPasswordExpires; // @} /** @@ -407,6 +387,25 @@ class User implements IDBAccessObject { return true; } + /** + * @since 1.27 + * @param string $wikiId + * @param integer $userId + */ + public static function purge( $wikiId, $userId ) { + $cache = ObjectCache::getMainWANInstance(); + $cache->delete( $cache->makeGlobalKey( 'user', 'id', $wikiId, $userId ) ); + } + + /** + * @since 1.27 + * @param WANObjectCache $cache + * @return string + */ + protected function getCacheKey( WANObjectCache $cache ) { + return $cache->makeGlobalKey( 'user', 'id', wfWikiID(), $this->mId ); + } + /** * Load user data from shared cache, given mId has already been set. * @@ -419,8 +418,8 @@ class User implements IDBAccessObject { return false; } - $key = wfMemcKey( 'user', 'id', $this->mId ); - $data = ObjectCache::getMainWANInstance()->get( $key ); + $cache = ObjectCache::getMainWANInstance(); + $data = $cache->get( $this->getCacheKey( $cache ) ); if ( !is_array( $data ) || $data['mVersion'] < self::VERSION ) { // Object is expired return false; @@ -456,10 +455,11 @@ class User implements IDBAccessObject { $data[$name] = $this->$name; } $data['mVersion'] = self::VERSION; - $key = wfMemcKey( 'user', 'id', $this->mId ); - $opts = Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) ); - ObjectCache::getMainWANInstance()->set( $key, $data, 3600, $opts ); + + $cache = ObjectCache::getMainWANInstance(); + $key = $this->getCacheKey( $cache ); + $cache->set( $key, $data, $cache::TTL_HOUR, $opts ); } /** @name newFrom*() static factory methods */ @@ -559,7 +559,7 @@ class User implements IDBAccessObject { * The row should have the following fields from the user table in it: * - either user_name or user_id to load further data if needed (or both) * - user_real_name - * - all other fields (email, password, etc.) + * - all other fields (email, etc.) * It is useless to provide the remaining fields if either user_id, * user_name and user_real_name are not provided because the whole row * will be loaded once more from the database when accessing them. @@ -574,6 +574,97 @@ class User implements IDBAccessObject { return $user; } + /** + * Static factory method for creation of a "system" user from username. + * + * A "system" user is an account that's used to attribute logged actions + * taken by MediaWiki itself, as opposed to a bot or human user. Examples + * might include the 'Maintenance script' or 'Conversion script' accounts + * used by various scripts in the maintenance/ directory or accounts such + * as 'MediaWiki message delivery' used by the MassMessage extension. + * + * This can optionally create the user if it doesn't exist, and "steal" the + * account if it does exist. + * + * @param string $name Username + * @param array $options Options are: + * - validate: As for User::getCanonicalName(), default 'valid' + * - create: Whether to create the user if it doesn't already exist, default true + * - steal: Whether to reset the account's password and email if it + * already exists, default false + * @return User|null + */ + public static function newSystemUser( $name, $options = array() ) { + $options += array( + 'validate' => 'valid', + 'create' => true, + 'steal' => false, + ); + + $name = self::getCanonicalName( $name, $options['validate'] ); + if ( $name === false ) { + return null; + } + + $dbw = wfGetDB( DB_MASTER ); + $row = $dbw->selectRow( + 'user', + array_merge( + self::selectFields(), + array( 'user_password', 'user_newpassword' ) + ), + array( 'user_name' => $name ), + __METHOD__ + ); + if ( !$row ) { + // No user. Create it? + return $options['create'] ? self::createNew( $name ) : null; + } + $user = self::newFromRow( $row ); + + // A user is considered to exist as a non-system user if it has a + // password set, or a temporary password set, or an email set. + $passwordFactory = new PasswordFactory(); + $passwordFactory->init( RequestContext::getMain()->getConfig() ); + try { + $password = $passwordFactory->newFromCiphertext( $row->user_password ); + } catch ( PasswordError $e ) { + wfDebug( 'Invalid password hash found in database.' ); + $password = PasswordFactory::newInvalidPassword(); + } + try { + $newpassword = $passwordFactory->newFromCiphertext( $row->user_newpassword ); + } catch ( PasswordError $e ) { + wfDebug( 'Invalid password hash found in database.' ); + $newpassword = PasswordFactory::newInvalidPassword(); + } + if ( !$password instanceof InvalidPassword || !$newpassword instanceof InvalidPassword + || $user->mEmail + ) { + // User exists. Steal it? + if ( !$options['steal'] ) { + return null; + } + + $nopass = PasswordFactory::newInvalidPassword()->toString(); + + $dbw->update( + 'user', + array( + 'user_password' => $nopass, + 'user_newpassword' => $nopass, + 'user_newpass_time' => null, + ), + array( 'user_id' => $user->getId() ), + __METHOD__ + ); + $user->invalidateEmail(); + $user->saveSettings(); + } + + return $user; + } + // @} /** @@ -875,73 +966,6 @@ class User implements IDBAccessObject { } } - /** - * Expire a user's password - * @since 1.23 - * @param int $ts Optional timestamp to convert, default 0 for the current time - */ - public function expirePassword( $ts = 0 ) { - $this->loadPasswords(); - $timestamp = wfTimestamp( TS_MW, $ts ); - $this->mPasswordExpires = $timestamp; - $this->saveSettings(); - } - - /** - * Clear the password expiration for a user - * @since 1.23 - * @param bool $load Ensure user object is loaded first - */ - public function resetPasswordExpiration( $load = true ) { - global $wgPasswordExpirationDays; - if ( $load ) { - $this->load(); - } - $newExpire = null; - if ( $wgPasswordExpirationDays ) { - $newExpire = wfTimestamp( - TS_MW, - time() + ( $wgPasswordExpirationDays * 24 * 3600 ) - ); - } - // Give extensions a chance to force an expiration - Hooks::run( 'ResetPasswordExpiration', array( $this, &$newExpire ) ); - $this->mPasswordExpires = $newExpire; - } - - /** - * Check if the user's password is expired. - * TODO: Put this and password length into a PasswordPolicy object - * @since 1.23 - * @return string|bool The expiration type, or false if not expired - * hard: A password change is required to login - * soft: Allow login, but encourage password change - * false: Password is not expired - */ - public function getPasswordExpired() { - global $wgPasswordExpireGrace; - $expired = false; - $now = wfTimestamp(); - $expiration = $this->getPasswordExpireDate(); - $expUnix = wfTimestamp( TS_UNIX, $expiration ); - if ( $expiration !== null && $expUnix < $now ) { - $expired = ( $expUnix + $wgPasswordExpireGrace < $now ) ? 'hard' : 'soft'; - } - return $expired; - } - - /** - * Get this user's password expiration date. Since this may be using - * the cached User object, we assume that whatever mechanism is setting - * the expiration date is also expiring the User cache. - * @since 1.23 - * @return string|null The datestamp of the expiration, or null if not set - */ - public function getPasswordExpireDate() { - $this->load(); - return $this->mPasswordExpires; - } - /** * Given unvalidated user input, return a canonical username, or false if * the username is invalid. @@ -1022,19 +1046,12 @@ class User implements IDBAccessObject { /** * Return a random password. * + * @deprecated since 1.27, use PasswordFactory::generateRandomPasswordString() * @return string New random password */ public static function randomPassword() { global $wgMinimalPasswordLength; - // Decide the final password length based on our min password length, - // stopping at a minimum of 10 chars. - $length = max( 10, $wgMinimalPasswordLength ); - // Multiply by 1.25 to get the number of hex characters we need - $length = $length * 1.25; - // Generate random hex chars - $hex = MWCryptRand::generateHex( $length ); - // Convert from base 16 to base 32 to get a proper password like string - return wfBaseConvert( $hex, 16, 32 ); + return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength ); } /** @@ -1046,15 +1063,9 @@ class User implements IDBAccessObject { * @param string|bool $name */ public function loadDefaults( $name = false ) { - - $passwordFactory = self::getPasswordFactory(); - $this->mId = 0; $this->mName = $name; $this->mRealName = ''; - $this->mPassword = $passwordFactory->newFromCiphertext( null ); - $this->mNewpassword = $passwordFactory->newFromCiphertext( null ); - $this->mNewpassTime = null; $this->mEmail = ''; $this->mOptionOverrides = null; $this->mOptionsLoaded = false; @@ -1070,8 +1081,6 @@ class User implements IDBAccessObject { $this->mEmailAuthenticated = null; $this->mEmailToken = ''; $this->mEmailTokenExpires = null; - $this->mPasswordExpires = null; - $this->resetPasswordExpiration( false ); $this->mRegistration = wfTimestamp( TS_MW ); $this->mGroups = array(); @@ -1243,7 +1252,6 @@ class User implements IDBAccessObject { */ protected function loadFromRow( $row, $data = null ) { $all = true; - $passwordFactory = self::getPasswordFactory(); $this->mGroups = null; // deferred @@ -1280,31 +1288,6 @@ class User implements IDBAccessObject { $all = false; } - if ( isset( $row->user_password ) ) { - // Check for *really* old password hashes that don't even have a type - // The old hash format was just an md5 hex hash, with no type information - if ( preg_match( '/^[0-9a-f]{32}$/', $row->user_password ) ) { - $row->user_password = ":A:{$this->mId}:{$row->user_password}"; - } - - try { - $this->mPassword = $passwordFactory->newFromCiphertext( $row->user_password ); - } catch ( PasswordError $e ) { - wfDebug( 'Invalid password hash found in database.' ); - $this->mPassword = $passwordFactory->newFromCiphertext( null ); - } - - try { - $this->mNewpassword = $passwordFactory->newFromCiphertext( $row->user_newpassword ); - } catch ( PasswordError $e ) { - wfDebug( 'Invalid password hash found in database.' ); - $this->mNewpassword = $passwordFactory->newFromCiphertext( null ); - } - - $this->mNewpassTime = wfTimestampOrNull( TS_MW, $row->user_newpass_time ); - $this->mPasswordExpires = wfTimestampOrNull( TS_MW, $row->user_password_expires ); - } - if ( isset( $row->user_email ) ) { $this->mEmail = $row->user_email; $this->mTouched = wfTimestamp( TS_MW, $row->user_touched ); @@ -1367,33 +1350,6 @@ class User implements IDBAccessObject { } } - /** - * Load the user's password hashes from the database - * - * This is usually called in a scenario where the actual User object was - * loaded from the cache, and then password comparison needs to be performed. - * Password hashes are not stored in memcached. - * - * @since 1.24 - */ - private function loadPasswords() { - if ( $this->getId() !== 0 && - ( $this->mPassword === null || $this->mNewpassword === null ) - ) { - $db = ( $this->queryFlagsUsed & self::READ_LATEST ) - ? wfGetDB( DB_MASTER ) - : wfGetDB( DB_SLAVE ); - - $this->loadFromRow( $db->selectRow( - 'user', - array( 'user_password', 'user_newpassword', - 'user_newpass_time', 'user_password_expires' ), - array( 'user_id' => $this->getId() ), - __METHOD__ - ) ); - } - } - /** * Add the user to the group if he/she meets given criteria. * @@ -1801,8 +1757,6 @@ class User implements IDBAccessObject { return false; } - global $wgMemc; - $limits = $wgRateLimits[$action]; $keys = array(); $id = $this->getId(); @@ -1857,11 +1811,13 @@ class User implements IDBAccessObject { $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit; } + $cache = ObjectCache::getLocalClusterInstance(); + $triggered = false; foreach ( $keys as $key => $limit ) { list( $max, $period ) = $limit; $summary = "(limit $max in {$period}s)"; - $count = $wgMemc->get( $key ); + $count = $cache->get( $key ); // Already pinged? if ( $count ) { if ( $count >= $max ) { @@ -1874,11 +1830,11 @@ class User implements IDBAccessObject { } else { wfDebug( __METHOD__ . ": adding record for $key $summary\n" ); if ( $incrBy > 0 ) { - $wgMemc->add( $key, 0, intval( $period ) ); // first ping + $cache->add( $key, 0, intval( $period ) ); // first ping } } if ( $incrBy > 0 ) { - $wgMemc->incr( $key, $incrBy ); + $cache->incr( $key, $incrBy ); } } @@ -2291,17 +2247,17 @@ class User implements IDBAccessObject { * @param string $mode Use 'refresh' to clear now; otherwise before DB commit */ public function clearSharedCache( $mode = 'changed' ) { - $id = $this->getId(); - if ( !$id ) { + if ( !$this->getId() ) { return; } - $key = wfMemcKey( 'user', 'id', $id ); + $cache = ObjectCache::getMainWANInstance(); + $key = $this->getCacheKey( $cache ); if ( $mode === 'refresh' ) { - ObjectCache::getMainWANInstance()->delete( $key, 1 ); + $cache->delete( $key, 1 ); } else { - wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() use ( $key ) { - ObjectCache::getMainWANInstance()->delete( $key ); + wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() use ( $cache, $key ) { + $cache->delete( $key ); } ); } } @@ -2383,23 +2339,21 @@ class User implements IDBAccessObject { } /** + * @deprecated Removed in 1.27. * @return Password * @since 1.24 */ public function getPassword() { - $this->loadPasswords(); - - return $this->mPassword; + throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' ); } /** + * @deprecated Removed in 1.27. * @return Password * @since 1.24 */ public function getTemporaryPassword() { - $this->loadPasswords(); - - return $this->mNewpassword; + throw new BadMethodCallException( __METHOD__ . ' has been removed in 1.27' ); } /** @@ -2413,16 +2367,14 @@ class User implements IDBAccessObject { * wipes it, so the account cannot be logged in until * a new password is set, for instance via e-mail. * + * @deprecated since 1.27. AuthManager is coming. * @param string $str New password to set * @throws PasswordError On failure - * * @return bool */ public function setPassword( $str ) { global $wgAuth; - $this->loadPasswords(); - if ( $str !== null ) { if ( !$wgAuth->allowPasswordChange() ) { throw new PasswordError( wfMessage( 'password-change-forbidden' )->text() ); @@ -2438,7 +2390,9 @@ class User implements IDBAccessObject { throw new PasswordError( wfMessage( 'externaldberror' )->text() ); } - $this->setInternalPassword( $str ); + $this->setToken(); + $this->setOption( 'watchlisttoken', false ); + $this->setPasswordInternal( $str ); return true; } @@ -2446,19 +2400,49 @@ class User implements IDBAccessObject { /** * Set the password and reset the random token unconditionally. * + * @deprecated since 1.27. AuthManager is coming. * @param string|null $str New password to set or null to set an invalid * password hash meaning that the user will not be able to log in * through the web interface. */ public function setInternalPassword( $str ) { - $this->setToken(); - $this->setOption( 'watchlisttoken', false ); + global $wgAuth; + + if ( $wgAuth->allowSetLocalPassword() ) { + $this->setToken(); + $this->setOption( 'watchlisttoken', false ); + $this->setPasswordInternal( $str ); + } + } - $passwordFactory = self::getPasswordFactory(); - $this->mPassword = $passwordFactory->newFromPlaintext( $str ); + /** + * Actually set the password and such + * @since 1.27 cannot set a password for a user not in the database + * @param string|null $str New password to set or null to set an invalid + * password hash meaning that the user will not be able to log in + * through the web interface. + */ + private function setPasswordInternal( $str ) { + $id = self::idFromName( $this->getName() ); + if ( $id == 0 ) { + throw new LogicException( 'Cannot set a password for a user that is not in the database.' ); + } - $this->mNewpassword = $passwordFactory->newFromCiphertext( null ); - $this->mNewpassTime = null; + $passwordFactory = new PasswordFactory(); + $passwordFactory->init( RequestContext::getMain()->getConfig() ); + $dbw = wfGetDB( DB_MASTER ); + $dbw->update( + 'user', + array( + 'user_password' => $passwordFactory->newFromPlaintext( $str )->toString(), + 'user_newpassword' => PasswordFactory::newInvalidPassword()->toString(), + 'user_newpass_time' => $dbw->timestampOrNull( null ), + ), + array( + 'user_id' => $id, + ), + __METHOD__ + ); } /** @@ -2493,19 +2477,32 @@ class User implements IDBAccessObject { /** * Set the password for a password reminder or new account email * + * @deprecated since 1.27, AuthManager is coming * @param string $str New password to set or null to set an invalid * password hash meaning that the user will not be able to use it * @param bool $throttle If true, reset the throttle timestamp to the present */ public function setNewpassword( $str, $throttle = true ) { - $this->loadPasswords(); + $id = $this->getId(); + if ( $id == 0 ) { + throw new LogicException( 'Cannot set new password for a user that is not in the database.' ); + } + + $dbw = wfGetDB( DB_MASTER ); + + $passwordFactory = new PasswordFactory(); + $passwordFactory->init( RequestContext::getMain()->getConfig() ); + $update = array( + 'user_newpassword' => $passwordFactory->newFromPlaintext( $str )->toString(), + ); - $this->mNewpassword = self::getPasswordFactory()->newFromPlaintext( $str ); if ( $str === null ) { - $this->mNewpassTime = null; + $update['user_newpass_time'] = null; } elseif ( $throttle ) { - $this->mNewpassTime = wfTimestampNow(); + $update['user_newpass_time'] = $dbw->timestamp(); } + + $dbw->update( 'user', $update, array( 'user_id' => $id ), __METHOD__ ); } /** @@ -2515,11 +2512,27 @@ class User implements IDBAccessObject { */ public function isPasswordReminderThrottled() { global $wgPasswordReminderResendTime; + + if ( !$wgPasswordReminderResendTime ) { + return false; + } + $this->load(); - if ( !$this->mNewpassTime || !$wgPasswordReminderResendTime ) { + + $db = ( $this->queryFlagsUsed & self::READ_LATEST ) + ? wfGetDB( DB_MASTER ) + : wfGetDB( DB_SLAVE ); + $newpassTime = $db->selectField( + 'user', + 'user_newpass_time', + array( 'user_id' => $this->getId() ), + __METHOD__ + ); + + if ( $newpassTime === null ) { return false; } - $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgPasswordReminderResendTime * 3600; + $expiry = wfTimestamp( TS_UNIX, $newpassTime ) + $wgPasswordReminderResendTime * 3600; return time() < $expiry; } @@ -3657,8 +3670,6 @@ class User implements IDBAccessObject { * @todo Only rarely do all these fields need to be set! */ public function saveSettings() { - global $wgAuth; - if ( wfReadOnly() ) { // @TODO: caller should deal with this instead! // This should really just be an exception. @@ -3670,7 +3681,6 @@ class User implements IDBAccessObject { } $this->load(); - $this->loadPasswords(); if ( 0 == $this->mId ) { return; // anon } @@ -3681,17 +3691,10 @@ class User implements IDBAccessObject { $oldTouched = $this->mTouched; $newTouched = $this->newTouchedTimestamp(); - if ( !$wgAuth->allowSetLocalPassword() ) { - $this->mPassword = self::getPasswordFactory()->newFromCiphertext( null ); - } - $dbw = wfGetDB( DB_MASTER ); $dbw->update( 'user', array( /* SET */ 'user_name' => $this->mName, - 'user_password' => $this->mPassword->toString(), - 'user_newpassword' => $this->mNewpassword->toString(), - 'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ), 'user_real_name' => $this->mRealName, 'user_email' => $this->mEmail, 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), @@ -3699,7 +3702,6 @@ class User implements IDBAccessObject { 'user_token' => strval( $this->mToken ), 'user_email_token' => $this->mEmailToken, 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ), - 'user_password_expires' => $dbw->timestampOrNull( $this->mPasswordExpires ), ), array( /* WHERE */ 'user_id' => $this->mId, 'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check @@ -3757,10 +3759,6 @@ class User implements IDBAccessObject { * @param string $name Username to add * @param array $params Array of Strings Non-default parameters to save to * the database as user_* fields: - * - password: The user's password hash. Password logins will be disabled - * if this is omitted. - * - newpassword: Hash for a temporary password that has been mailed to - * the user. * - email: The user's email address. * - email_authenticated: The email authentication timestamp. * - real_name: The user's real name. @@ -3771,9 +3769,15 @@ class User implements IDBAccessObject { * @return User|null User object, or null if the username already exists. */ public static function createNew( $name, $params = array() ) { + foreach ( array( 'password', 'newpassword', 'newpass_time', 'password_expires' ) as $field ) { + if ( isset( $params[$field] ) ) { + wfDeprecated( __METHOD__ . " with param '$field'", '1.27' ); + unset( $params[$field] ); + } + } + $user = new User; $user->load(); - $user->loadPasswords(); $user->setToken(); // init token if ( isset( $params['options'] ) ) { $user->mOptions = $params['options'] + (array)$user->mOptions; @@ -3782,12 +3786,13 @@ class User implements IDBAccessObject { $dbw = wfGetDB( DB_MASTER ); $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' ); + $noPass = PasswordFactory::newInvalidPassword()->toString(); + $fields = array( 'user_id' => $seqVal, 'user_name' => $name, - 'user_password' => $user->mPassword->toString(), - 'user_newpassword' => $user->mNewpassword->toString(), - 'user_newpass_time' => $dbw->timestampOrNull( $user->mNewpassTime ), + 'user_password' => $noPass, + 'user_newpassword' => $noPass, 'user_email' => $user->mEmail, 'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ), 'user_real_name' => $user->mRealName, @@ -3836,13 +3841,14 @@ class User implements IDBAccessObject { */ public function addToDatabase() { $this->load(); - $this->loadPasswords(); if ( !$this->mToken ) { $this->setToken(); // init token } $this->mTouched = $this->newTouchedTimestamp(); + $noPass = PasswordFactory::newInvalidPassword()->toString(); + $dbw = wfGetDB( DB_MASTER ); $inWrite = $dbw->writesOrCallbacksPending(); $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' ); @@ -3850,9 +3856,8 @@ class User implements IDBAccessObject { array( 'user_id' => $seqVal, 'user_name' => $this->mName, - 'user_password' => $this->mPassword->toString(), - 'user_newpassword' => $this->mNewpassword->toString(), - 'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ), + 'user_password' => $noPass, + 'user_newpassword' => $noPass, 'user_email' => $this->mEmail, 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), 'user_real_name' => $this->mRealName, @@ -4002,13 +4007,14 @@ class User implements IDBAccessObject { /** * Check to see if the given clear-text password is one of the accepted passwords + * @deprecated since 1.27. AuthManager is coming. * @param string $password User password * @return bool True if the given password is correct, otherwise False */ public function checkPassword( $password ) { global $wgAuth, $wgLegacyEncoding; - $this->loadPasswords(); + $this->load(); // Some passwords will give a fatal Status, which means there is // some sort of technical or security reason for this password to @@ -4030,12 +4036,27 @@ class User implements IDBAccessObject { return false; } - if ( !$this->mPassword->equals( $password ) ) { + $passwordFactory = new PasswordFactory(); + $passwordFactory->init( RequestContext::getMain()->getConfig() ); + $db = ( $this->queryFlagsUsed & self::READ_LATEST ) + ? wfGetDB( DB_MASTER ) + : wfGetDB( DB_SLAVE ); + + try { + $mPassword = $passwordFactory->newFromCiphertext( $db->selectField( + 'user', 'user_password', array( 'user_id' => $this->getId() ), __METHOD__ + ) ); + } catch ( PasswordError $e ) { + wfDebug( 'Invalid password hash found in database.' ); + $mPassword = PasswordFactory::newInvalidPassword(); + } + + if ( !$mPassword->equals( $password ) ) { if ( $wgLegacyEncoding ) { // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted // Check for this with iconv $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password ); - if ( $cp1252Password === $password || !$this->mPassword->equals( $cp1252Password ) ) { + if ( $cp1252Password === $password || !$mPassword->equals( $cp1252Password ) ) { return false; } } else { @@ -4043,10 +4064,8 @@ class User implements IDBAccessObject { } } - $passwordFactory = self::getPasswordFactory(); - if ( $passwordFactory->needsUpdate( $this->mPassword ) && !wfReadOnly() ) { - $this->mPassword = $passwordFactory->newFromPlaintext( $password ); - $this->saveSettings(); + if ( $passwordFactory->needsUpdate( $mPassword ) && !wfReadOnly() ) { + $this->setPasswordInternal( $password ); } return true; @@ -4056,20 +4075,39 @@ class User implements IDBAccessObject { * Check if the given clear-text password matches the temporary password * sent by e-mail for password reset operations. * + * @deprecated since 1.27. AuthManager is coming. * @param string $plaintext - * * @return bool True if matches, false otherwise */ public function checkTemporaryPassword( $plaintext ) { global $wgNewPasswordExpiry; $this->load(); - $this->loadPasswords(); - if ( $this->mNewpassword->equals( $plaintext ) ) { - if ( is_null( $this->mNewpassTime ) ) { + + $passwordFactory = new PasswordFactory(); + $passwordFactory->init( RequestContext::getMain()->getConfig() ); + $db = ( $this->queryFlagsUsed & self::READ_LATEST ) + ? wfGetDB( DB_MASTER ) + : wfGetDB( DB_SLAVE ); + + $row = $db->selectRow( + 'user', + array( 'user_newpassword', 'user_newpass_time' ), + array( 'user_id' => $this->getId() ), + __METHOD__ + ); + try { + $newPassword = $passwordFactory->newFromCiphertext( $row->user_newpassword ); + } catch ( PasswordError $e ) { + wfDebug( 'Invalid password hash found in database.' ); + $newPassword = PasswordFactory::newInvalidPassword(); + } + + if ( $newPassword->equals( $plaintext ) ) { + if ( is_null( $row->user_newpass_time ) ) { return true; } - $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgNewPasswordExpiry; + $expiry = wfTimestamp( TS_UNIX, $row->user_newpass_time ) + $wgNewPasswordExpiry; return ( time() < $expiry ); } else { return false; @@ -4927,7 +4965,9 @@ class User implements IDBAccessObject { */ public static function crypt( $password, $salt = false ) { wfDeprecated( __METHOD__, '1.24' ); - $hash = self::getPasswordFactory()->newFromPlaintext( $password ); + $passwordFactory = new PasswordFactory(); + $passwordFactory->init( RequestContext::getMain()->getConfig() ); + $hash = $passwordFactory->newFromPlaintext( $password ); return $hash->toString(); } @@ -4956,7 +4996,9 @@ class User implements IDBAccessObject { } } - $hash = self::getPasswordFactory()->newFromCiphertext( $hash ); + $passwordFactory = new PasswordFactory(); + $passwordFactory->init( RequestContext::getMain()->getConfig() ); + $hash = $passwordFactory->newFromCiphertext( $hash ); return $hash->equals( $password ); } @@ -5166,15 +5208,14 @@ class User implements IDBAccessObject { /** * Lazily instantiate and return a factory object for making passwords * + * @deprecated since 1.27, create a PasswordFactory directly instead * @return PasswordFactory */ public static function getPasswordFactory() { - if ( self::$mPasswordFactory === null ) { - self::$mPasswordFactory = new PasswordFactory(); - self::$mPasswordFactory->init( RequestContext::getMain()->getConfig() ); - } - - return self::$mPasswordFactory; + wfDeprecated( __METHOD__, '1.27' ); + $ret = new PasswordFactory(); + $ret->init( RequestContext::getMain()->getConfig() ); + return $ret; } /** @@ -5196,6 +5237,7 @@ class User implements IDBAccessObject { * * @todo FIXME: This does not belong here; put it in Html or Linker or somewhere * + * @deprecated since 1.27 * @return array Array of HTML attributes suitable for feeding to * Html::element(), directly or indirectly. (Don't feed to Xml::*()! * That will get confused by the boolean attribute syntax used.) @@ -5209,7 +5251,7 @@ class User implements IDBAccessObject { # Note that the pattern requirement will always be satisfied if the # input is empty, so we need required in all cases. - # + # @todo FIXME: Bug 23769: This needs to not claim the password is required # if e-mail confirmation is being used. Since HTML5 input validation # is b0rked anyway in some browsers, just return nothing. When it's