* @file
*/
-/**
- * Int Number of characters in user_token field.
- * @ingroup Constants
- */
-define( 'USER_TOKEN_LENGTH', 32 );
-
-/**
- * Int Serialized record version.
- * @ingroup Constants
- */
-define( 'MW_USER_VERSION', 9 );
-
/**
* String Some punctuation to prevent editing from broken text-mangling proxies.
* @ingroup Constants
*/
define( 'EDIT_TOKEN_SUFFIX', '+\\' );
-/**
- * Thrown by User::setPassword() on error.
- * @ingroup Exception
- */
-class PasswordError extends MWException {
- // NOP
-}
-
/**
* The User object encapsulates all of the user-specific settings (user_id,
* name, rights, password, email address, options, last login time). Client
* for rendering normal pages are set in the cookie to minimize use
* of the database.
*/
-class User {
+class User implements IDBAccessObject {
/**
- * Global constants made accessible as class constants so that autoloader
+ * @const int Number of characters in user_token field.
+ */
+ const TOKEN_LENGTH = 32;
+
+ /**
+ * Global constant made accessible as class constants so that autoloader
* magic can be used.
*/
- const USER_TOKEN_LENGTH = USER_TOKEN_LENGTH;
- const MW_USER_VERSION = MW_USER_VERSION;
const EDIT_TOKEN_SUFFIX = EDIT_TOKEN_SUFFIX;
+ /**
+ * @const int Serialized record version.
+ */
+ const VERSION = 10;
+
/**
* Maximum items in $mWatchedItems
*/
const MAX_WATCHED_ITEMS_CACHE = 100;
+ /**
+ * @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
'mId',
'mName',
'mRealName',
- 'mPassword',
- 'mNewpassword',
- 'mNewpassTime',
'mEmail',
'mTouched',
'mToken',
'mEmailAuthenticated',
'mEmailToken',
'mEmailTokenExpires',
- 'mPasswordExpires',
'mRegistration',
'mEditCount',
// user_groups table
'nominornewtalk',
'noratelimit',
'override-export-depth',
+ 'pagelang',
'passwordreset',
'patrol',
'patrolmarks',
'userrights-interwiki',
'viewmyprivateinfo',
'viewmywatchlist',
+ 'viewsuppressed',
'writeapi',
);
public $mRealName;
+ /**
+ * @todo Make this actually private
+ * @private
+ */
public $mPassword;
+ /**
+ * @todo Make this actually private
+ * @private
+ */
public $mNewpassword;
public $mNewpassTime;
/**
* Load user table data, given mId has already been set.
- * @return bool false if the ID does not exist, true otherwise
+ * @return bool False if the ID does not exist, true otherwise
*/
public function loadFromId() {
- global $wgMemc;
if ( $this->mId == 0 ) {
$this->loadDefaults();
return false;
}
// Try cache
- $key = wfMemcKey( 'user', 'id', $this->mId );
- $data = $wgMemc->get( $key );
- if ( !is_array( $data ) || $data['mVersion'] < MW_USER_VERSION ) {
- // Object is expired, load from DB
- $data = false;
- }
-
- if ( !$data ) {
+ $cache = $this->loadFromCache();
+ if ( !$cache ) {
wfDebug( "User: cache miss for user {$this->mId}\n" );
// Load from DB
if ( !$this->loadFromDatabase() ) {
return false;
}
$this->saveToCache();
- } else {
- wfDebug( "User: got user {$this->mId} from cache\n" );
- // Restore from cache
- foreach ( self::$mCacheVars as $name ) {
- $this->$name = $data[$name];
- }
}
$this->mLoadedItems = true;
return true;
}
+ /**
+ * Load user data from shared cache, given mId has already been set.
+ *
+ * @return bool false if the ID does not exist or data is invalid, true otherwise
+ * @since 1.25
+ */
+ public function loadFromCache() {
+ global $wgMemc;
+
+ if ( $this->mId == 0 ) {
+ $this->loadDefaults();
+ return false;
+ }
+
+ $key = wfMemcKey( 'user', 'id', $this->mId );
+ $data = $wgMemc->get( $key );
+ if ( !is_array( $data ) || $data['mVersion'] < self::VERSION ) {
+ // Object is expired
+ return false;
+ }
+
+ wfDebug( "User: got user {$this->mId} from cache\n" );
+
+ // Restore from cache
+ foreach ( self::$mCacheVars as $name ) {
+ $this->$name = $data[$name];
+ }
+
+ return true;
+ }
+
/**
* Save user data to the shared cache
*/
foreach ( self::$mCacheVars as $name ) {
$data[$name] = $this->$name;
}
- $data['mVersion'] = MW_USER_VERSION;
+ $data['mVersion'] = self::VERSION;
$key = wfMemcKey( 'user', 'id', $this->mId );
global $wgMemc;
$wgMemc->set( $key, $data );
* Given unvalidated password input, return error message on failure.
*
* @param string $password Desired password
- * @return bool|string|array true on success, string or array of error message on failure
+ * @return bool|string|array True on success, string or array of error message on failure
*/
public function getPasswordValidity( $password ) {
$result = $this->checkPasswordValidity( $password );
* @param int $ts Optional timestamp to convert, default 0 for the current time
*/
public function expirePassword( $ts = 0 ) {
- $this->load();
+ $this->loadPasswords();
$timestamp = wfTimestamp( TS_MW, $ts );
$this->mPasswordExpires = $timestamp;
$this->saveSettings();
return $this->mPasswordExpires;
}
- /**
- * Does a string look like an e-mail address?
- *
- * This validates an email address using an HTML5 specification found at:
- * http://www.whatwg.org/html/states-of-the-type-attribute.html#valid-e-mail-address
- * Which as of 2011-01-24 says:
- *
- * A valid e-mail address is a string that matches the ABNF production
- * 1*( atext / "." ) "@" ldh-str *( "." ldh-str ) where atext is defined
- * in RFC 5322 section 3.2.3, and ldh-str is defined in RFC 1034 section
- * 3.5.
- *
- * This function is an implementation of the specification as requested in
- * bug 22449.
- *
- * Client-side forms will use the same standard validation rules via JS or
- * HTML 5 validation; additional restrictions can be enforced server-side
- * by extensions via the 'isValidEmailAddr' hook.
- *
- * Note that this validation doesn't 100% match RFC 2822, but is believed
- * to be liberal enough for wide use. Some invalid addresses will still
- * pass validation here.
- *
- * @param string $addr E-mail address
- * @return bool
- * @deprecated since 1.18 call Sanitizer::isValidEmail() directly
- */
- public static function isValidEmailAddr( $addr ) {
- wfDeprecated( __METHOD__, '1.18' );
- return Sanitizer::validateEmail( $addr );
- }
-
/**
* Given unvalidated user input, return a canonical username, or false if
* the username is invalid.
return false;
}
- // Clean up name according to title rules
- $t = ( $validate === 'valid' ) ?
+ // Clean up name according to title rules,
+ // but only when validation is requested (bug 12654)
+ $t = ( $validate !== false ) ?
Title::newFromText( $name ) : Title::makeTitle( NS_USER, $name );
// Check for invalid titles
if ( is_null( $t ) ) {
public function loadDefaults( $name = false ) {
wfProfileIn( __METHOD__ );
+ $passwordFactory = self::getPasswordFactory();
+
$this->mId = 0;
$this->mName = $name;
$this->mRealName = '';
- $this->mPassword = $this->mNewpassword = '';
+ $this->mPassword = $passwordFactory->newFromCiphertext( null );
+ $this->mNewpassword = $passwordFactory->newFromCiphertext( null );
$this->mNewpassTime = null;
$this->mEmail = '';
$this->mOptionOverrides = null;
* Load user and user_group data from the database.
* $this->mId must be set, this is how the user is identified.
*
+ * @param int $flags Supports User::READ_LOCKING
* @return bool True if the user exists, false if the user is anonymous
*/
- public function loadFromDatabase() {
+ public function loadFromDatabase( $flags = 0 ) {
// Paranoia
$this->mId = intval( $this->mId );
'user',
self::selectFields(),
array( 'user_id' => $this->mId ),
- __METHOD__
+ __METHOD__,
+ ( $flags & self::READ_LOCKING == self::READ_LOCKING )
+ ? array( 'LOCK IN SHARE MODE' )
+ : array()
);
wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) );
*/
public function loadFromRow( $row, $data = null ) {
$all = true;
+ $passwordFactory = self::getPasswordFactory();
$this->mGroups = null; // deferred
}
if ( isset( $row->user_password ) ) {
- $this->mPassword = $row->user_password;
- $this->mNewpassword = $row->user_newpassword;
+ // 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 );
$this->mToken = $row->user_token;
$this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
$this->mEmailToken = $row->user_email_token;
$this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
- $this->mPasswordExpires = wfTimestampOrNull( TS_MW, $row->user_password_expires );
$this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
} else {
$all = false;
}
}
+ /**
+ * 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 ) ) {
+ $this->loadFromRow( wfGetDB( DB_MASTER )->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.
*
foreach ( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
$defOpt['searchNs' . $nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
}
- $defOpt['skin'] = $wgDefaultSkin;
+ $defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) );
* @return bool True if blacklisted.
*/
public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
- global $wgEnableSorbs, $wgEnableDnsBlacklist,
- $wgSorbsUrl, $wgDnsBlacklistUrls, $wgProxyWhitelist;
+ global $wgEnableDnsBlacklist, $wgDnsBlacklistUrls, $wgProxyWhitelist;
- if ( !$wgEnableDnsBlacklist && !$wgEnableSorbs ) {
+ if ( !$wgEnableDnsBlacklist ) {
return false;
}
return false;
}
- $urls = array_merge( $wgDnsBlacklistUrls, (array)$wgSorbsUrl );
- return $this->inDnsBlacklist( $ip, $urls );
+ return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
}
/**
// If more than one group applies, use the group with the highest limit
foreach ( $this->getGroups() as $group ) {
if ( isset( $limits[$group] ) ) {
- if ( $userLimit === false || $limits[$group] > $userLimit ) {
+ if ( $userLimit === false
+ || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
+ ) {
$userLimit = $limits[$group];
}
}
return $this->mLocked;
}
global $wgAuth;
- StubObject::unstub( $wgAuth );
$authUser = $wgAuth->getUserInstance( $this );
$this->mLocked = (bool)$authUser->isLocked();
return $this->mLocked;
$this->getBlockedStatus();
if ( !$this->mHideName ) {
global $wgAuth;
- StubObject::unstub( $wgAuth );
$authUser = $wgAuth->getUserInstance( $this );
$this->mHideName = (bool)$authUser->isHidden();
}
* @see getNewtalk()
* @param string $field 'user_ip' for anonymous users, 'user_id' otherwise
* @param string|int $id User's IP address for anonymous users, User ID otherwise
- * @param bool $fromMaster true to fetch from the master, false for a slave
+ * @param bool $fromMaster True to fetch from the master, false for a slave
* @return bool True if the user has new messages
*/
protected function checkNewtalk( $field, $id, $fromMaster = false ) {
*
* Called implicitly from invalidateCache() and saveSettings().
*/
- private function clearSharedCache() {
+ public function clearSharedCache() {
$this->load();
if ( $this->mId ) {
global $wgMemc;
$userid = $this->mId;
$touched = $this->mTouched;
$method = __METHOD__;
- $dbw->onTransactionIdle( function() use ( $dbw, $userid, $touched, $method ) {
+ $dbw->onTransactionIdle( function () use ( $dbw, $userid, $touched, $method ) {
// Prevent contention slams by checking user_touched first
$encTouched = $dbw->addQuotes( $dbw->timestamp( $touched ) );
$needsPurge = $dbw->selectField( 'user', '1',
return $this->mTouched;
}
+ /**
+ * @return Password
+ * @since 1.24
+ */
+ public function getPassword() {
+ $this->loadPasswords();
+
+ return $this->mPassword;
+ }
+
+ /**
+ * @return Password
+ * @since 1.24
+ */
+ public function getTemporaryPassword() {
+ $this->loadPasswords();
+
+ return $this->mNewpassword;
+ }
+
/**
* Set the password and reset the random token.
* Calls through to authentication plugin if necessary;
* a new password is set, for instance via e-mail.
*
* @param string $str New password to set
- * @throws PasswordError on failure
+ * @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() );
* through the web interface.
*/
public function setInternalPassword( $str ) {
- $this->load();
$this->setToken();
- if ( $str === null ) {
- // Save an invalid hash...
- $this->mPassword = '';
- } else {
- $this->mPassword = self::crypt( $str );
- }
- $this->mNewpassword = '';
+ $passwordFactory = self::getPasswordFactory();
+ $this->mPassword = $passwordFactory->newFromPlaintext( $str );
+
+ $this->mNewpassword = $passwordFactory->newFromCiphertext( null );
$this->mNewpassTime = null;
}
public function setToken( $token = false ) {
$this->load();
if ( !$token ) {
- $this->mToken = MWCryptRand::generateHex( USER_TOKEN_LENGTH );
+ $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
} else {
$this->mToken = $token;
}
* @param bool $throttle If true, reset the throttle timestamp to the present
*/
public function setNewpassword( $str, $throttle = true ) {
- $this->load();
+ $this->loadPasswords();
+ $this->mNewpassword = self::getPasswordFactory()->newFromPlaintext( $str );
if ( $str === null ) {
- $this->mNewpassword = '';
$this->mNewpassTime = null;
- } else {
- $this->mNewpassword = self::crypt( $str );
- if ( $throttle ) {
- $this->mNewpassTime = wfTimestampNow();
- }
+ } elseif ( $throttle ) {
+ $this->mNewpassTime = wfTimestampNow();
}
}
if ( $str == $this->mEmail ) {
return;
}
- $this->mEmail = $str;
$this->invalidateEmail();
+ $this->mEmail = $str;
wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) );
}
* @param IContextSource $context
* @param array $options Assoc. array with options keys to check as keys.
* Defaults to $this->mOptions.
- * @return array the key => kind mapping data
+ * @return array The key => kind mapping data
*/
public function getOptionKinds( IContextSource $context, $options = null ) {
$this->loadOptions();
foreach ( $columns as $column ) {
foreach ( $rows as $row ) {
- $checkmatrixOptions["$prefix-$column-$row"] = true;
+ $checkmatrixOptions["$prefix$column-$row"] = true;
}
}
}
}
+ wfRunHooks( 'UserResetAllOptions', array( $this, &$newOptions, $this->mOptions, $resetKinds ) );
+
$this->mOptions = $newOptions;
$this->mOptionsLoaded = true;
}
/**
* Get the user's edit count.
- * @return int|null null for anonymous users
+ * @return int|null Null for anonymous users
*/
public function getEditCount() {
if ( !$this->getId() ) {
return null;
}
- if ( !isset( $this->mEditCount ) ) {
+ if ( $this->mEditCount === null ) {
/* Populate the count, if it has not been populated yet */
wfProfileIn( __METHOD__ );
$dbr = wfGetDB( DB_SLAVE );
/**
* Check if user is allowed to access a feature / make an action
*
- * @internal param \String $varargs permissions to test
+ * @param string $permissions,... Permissions to test
* @return bool True if user is allowed to perform *any* of the given actions
- *
- * @return bool
*/
public function isAllowedAny( /*...*/ ) {
$permissions = func_get_args();
/**
*
- * @internal param $varargs string
+ * @param string $permissions,... Permissions to test
* @return bool True if the user is allowed to perform *all* of the given actions
*/
public function isAllowedAll( /*...*/ ) {
global $wgAuth;
$this->load();
+ $this->loadPasswords();
if ( wfReadOnly() ) {
return;
}
$this->mTouched = self::newTouchedTimestamp();
if ( !$wgAuth->allowSetLocalPassword() ) {
- $this->mPassword = '';
+ $this->mPassword = self::getPasswordFactory()->newFromCiphertext( null );
}
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'user',
array( /* SET */
'user_name' => $this->mName,
- 'user_password' => $this->mPassword,
- 'user_newpassword' => $this->mNewpassword,
+ '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,
public static function createNew( $name, $params = array() ) {
$user = new User;
$user->load();
+ $user->loadPasswords();
$user->setToken(); // init token
if ( isset( $params['options'] ) ) {
$user->mOptions = $params['options'] + (array)$user->mOptions;
$fields = array(
'user_id' => $seqVal,
'user_name' => $name,
- 'user_password' => $user->mPassword,
- 'user_newpassword' => $user->mNewpassword,
+ 'user_password' => $user->mPassword->toString(),
+ 'user_newpassword' => $user->mNewpassword->toString(),
'user_newpass_time' => $dbw->timestampOrNull( $user->mNewpassTime ),
'user_email' => $user->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
*/
public function addToDatabase() {
$this->load();
+ $this->loadPasswords();
if ( !$this->mToken ) {
$this->setToken(); // init token
}
array(
'user_id' => $seqVal,
'user_name' => $this->mName,
- 'user_password' => $this->mPassword,
- 'user_newpassword' => $this->mNewpassword,
+ 'user_password' => $this->mPassword->toString(),
+ 'user_newpassword' => $this->mNewpassword->toString(),
'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ),
'user_email' => $this->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
array( 'IGNORE' )
);
if ( !$dbw->affectedRows() ) {
- if ( !$inWrite ) {
- // XXX: Get out of REPEATABLE-READ so the SELECT below works.
- // Often this case happens early in views before any writes.
- // This shows up at least with CentralAuth.
+ // The queries below cannot happen in the same REPEATABLE-READ snapshot.
+ // Handle this by COMMIT, if possible, or by LOCK IN SHARE MODE otherwise.
+ if ( $inWrite ) {
+ // Can't commit due to pending writes that may need atomicity.
+ // This may cause some lock contention unlike the case below.
+ $options = array( 'LOCK IN SHARE MODE' );
+ $flags = self::READ_LOCKING;
+ } else {
+ // Often, this case happens early in views before any writes when
+ // using CentralAuth. It's should be OK to commit and break the snapshot.
$dbw->commit( __METHOD__, 'flush' );
+ $options = array();
+ $flags = 0;
}
$this->mId = $dbw->selectField( 'user', 'user_id',
- array( 'user_name' => $this->mName ), __METHOD__ );
+ array( 'user_name' => $this->mName ), __METHOD__, $options );
$loaded = false;
if ( $this->mId ) {
- if ( $this->loadFromDatabase() ) {
+ if ( $this->loadFromDatabase( $flags ) ) {
$loaded = true;
}
}
/**
* Check to see if the given clear-text password is one of the accepted passwords
- * @param string $password user password.
- * @return bool True if the given password is correct, otherwise False.
+ * @param string $password User password
+ * @return bool True if the given password is correct, otherwise False
*/
public function checkPassword( $password ) {
global $wgAuth, $wgLegacyEncoding;
- $this->load();
+
+ $section = new ProfileSection( __METHOD__ );
+
+ $this->loadPasswords();
// Certain authentication plugins do NOT want to save
// domain passwords in a mysql database, so we should
// check this (in case $wgAuth->strict() is false).
-
if ( $wgAuth->authenticate( $this->getName(), $password ) ) {
return true;
} elseif ( $wgAuth->strict() ) {
// Auth plugin doesn't allow local authentication for this user name
return false;
}
- if ( self::comparePasswords( $this->mPassword, $password, $this->mId ) ) {
- return true;
- } elseif ( $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
- && self::comparePasswords( $this->mPassword, $cp1252Password, $this->mId )
- ) {
- return true;
+
+ $passwordFactory = self::getPasswordFactory();
+ if ( !$this->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 ) ) {
+ return false;
+ }
+ } else {
+ return false;
}
}
- return false;
+
+ if ( $passwordFactory->needsUpdate( $this->mPassword ) ) {
+ $this->mPassword = $passwordFactory->newFromPlaintext( $password );
+ $this->saveSettings();
+ }
+
+ return true;
}
/**
global $wgNewPasswordExpiry;
$this->load();
- if ( self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ) ) {
+ $this->loadPasswords();
+ if ( $this->mNewpassword->equals( $plaintext ) ) {
if ( is_null( $this->mNewpassTime ) ) {
return true;
}
* Alias for getEditToken.
* @deprecated since 1.19, use getEditToken instead.
*
- * @param string|array $salt of Strings Optional function-specific data for hashing
+ * @param string|array $salt Array of Strings Optional function-specific data for hashing
* @param WebRequest|null $request WebRequest object to use or null to use $wgRequest
* @return string The new edit token
*/
*
* @since 1.19
*
- * @param string|array $salt of Strings Optional function-specific data for hashing
+ * @param string|array $salt Array of Strings Optional function-specific data for hashing
* @param WebRequest|null $request WebRequest object to use or null to use $wgRequest
* @return string The new edit token
*/
}
if ( $this->isAnon() ) {
- return EDIT_TOKEN_SUFFIX;
+ return self::EDIT_TOKEN_SUFFIX;
} else {
$token = $request->getSessionData( 'wsEditToken' );
if ( $token === null ) {
if ( is_array( $salt ) ) {
$salt = implode( '|', $salt );
}
- return md5( $token . $salt ) . EDIT_TOKEN_SUFFIX;
+ return md5( $token . $salt ) . self::EDIT_TOKEN_SUFFIX;
}
}
*
* @param string $val Input value to compare
* @param string $salt Optional function-specific data for hashing
- * @param WebRequest|null $request object to use or null to use $wgRequest
+ * @param WebRequest|null $request Object to use or null to use $wgRequest
* @return bool Whether the token matches
*/
public function matchEditTokenNoSuffix( $val, $salt = '', $request = null ) {
$sender = new MailAddress( $wgPasswordSender,
wfMessage( 'emailsender' )->inContentLanguage()->text() );
} else {
- $sender = new MailAddress( $from );
+ $sender = MailAddress::newFromUser( $from );
}
- $to = new MailAddress( $this );
+ $to = MailAddress::newFromUser( $this );
return UserMailer::send( $to, $sender, $subject, $body, $replyto );
}
$this->mEmailToken = null;
$this->mEmailTokenExpires = null;
$this->setEmailAuthenticationTimestamp( null );
+ $this->mEmail = '';
wfRunHooks( 'InvalidateEmailComplete', array( $this ) );
return true;
}
* Returns an array of the groups that a particular group can add/remove.
*
* @param string $group The group to check for whether it can add/remove
- * @return array array( 'add' => array( addablegroups ),
+ * @return array Array( 'add' => array( addablegroups ),
* 'remove' => array( removablegroups ),
* 'add-self' => array( addablegroups to self),
* 'remove-self' => array( removable groups from self) )
/**
* Returns an array of groups that this user can add and remove
- * @return array array( 'add' => array( addablegroups ),
+ * @return array Array( 'add' => array( addablegroups ),
* 'remove' => array( removablegroups ),
* 'add-self' => array( addablegroups to self),
* 'remove-self' => array( removable groups from self) )
return $msg->isBlank() ? $right : $msg->text();
}
- /**
- * Make an old-style password hash
- *
- * @param string $password Plain-text password
- * @param string $userId User ID
- * @return string Password hash
- */
- public static function oldCrypt( $password, $userId ) {
- global $wgPasswordSalt;
- if ( $wgPasswordSalt ) {
- return md5( $userId . '-' . md5( $password ) );
- } else {
- return md5( $password );
- }
- }
-
/**
* Make a new-style password hash
*
* @param bool|string $salt Optional salt, may be random or the user ID.
* If unspecified or false, will generate one automatically
* @return string Password hash
+ * @deprecated since 1.24, use Password class
*/
public static function crypt( $password, $salt = false ) {
- global $wgPasswordSalt;
-
- $hash = '';
- if ( !wfRunHooks( 'UserCryptPassword', array( &$password, &$salt, &$wgPasswordSalt, &$hash ) ) ) {
- return $hash;
- }
-
- if ( $wgPasswordSalt ) {
- if ( $salt === false ) {
- $salt = MWCryptRand::generateHex( 8 );
- }
- return ':B:' . $salt . ':' . md5( $salt . '-' . md5( $password ) );
- } else {
- return ':A:' . md5( $password );
- }
+ wfDeprecated( __METHOD__, '1.24' );
+ $hash = self::getPasswordFactory()->newFromPlaintext( $password );
+ return $hash->toString();
}
/**
* @param string|bool $userId User ID for old-style password salt
*
* @return bool
+ * @deprecated since 1.24, use Password class
*/
public static function comparePasswords( $hash, $password, $userId = false ) {
- $type = substr( $hash, 0, 3 );
-
- $result = false;
- if ( !wfRunHooks( 'UserComparePasswords', array( &$hash, &$password, &$userId, &$result ) ) ) {
- return $result;
+ wfDeprecated( __METHOD__, '1.24' );
+
+ // 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}$/', $hash ) ) {
+ global $wgPasswordSalt;
+ if ( $wgPasswordSalt ) {
+ $password = ":B:{$userId}:{$hash}";
+ } else {
+ $password = ":A:{$hash}";
+ }
}
- if ( $type == ':A:' ) {
- // Unsalted
- return md5( $password ) === substr( $hash, 3 );
- } elseif ( $type == ':B:' ) {
- // Salted
- list( $salt, $realHash ) = explode( ':', substr( $hash, 3 ), 2 );
- return md5( $salt . '-' . md5( $password ) ) === $realHash;
- } else {
- // Old-style
- return self::oldCrypt( $password, $userId ) === $hash;
- }
+ $hash = self::getPasswordFactory()->newFromCiphertext( $hash );
+ return $hash->equals( $password );
}
/**
$dbw->insert( 'user_properties', $insert_rows, __METHOD__, array( 'IGNORE' ) );
}
+ /**
+ * Lazily instantiate and return a factory object for making passwords
+ *
+ * @return PasswordFactory
+ */
+ public static function getPasswordFactory() {
+ if ( self::$mPasswordFactory === null ) {
+ self::$mPasswordFactory = new PasswordFactory();
+ self::$mPasswordFactory->init( RequestContext::getMain()->getConfig() );
+ }
+
+ return self::$mPasswordFactory;
+ }
+
/**
* Provide an array of HTML5 attributes to put on an input element
* intended for the user to enter a new password. This may include
'user_id',
'user_name',
'user_real_name',
- 'user_password',
- 'user_newpassword',
- 'user_newpass_time',
'user_email',
'user_touched',
'user_token',
'user_email_authenticated',
'user_email_token',
'user_email_token_expires',
- 'user_password_expires',
'user_registration',
'user_editcount',
);