'deletedhistory',
'deletedtext',
'deleterevision',
- 'disableaccount',
'edit',
'editinterface',
'editusercssjs', #deprecated
}
/**
- * Return a random password. Sourced from mt_rand, so it's not particularly secure.
- * @todo hash random numbers to improve security, like generateToken()
+ * Return a random password.
*
* @return \string New random password
*/
static function randomPassword() {
- global $wgMinimalPasswordLength;
- $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
- $l = strlen( $pwchars ) - 1;
-
- $pwlength = max( 7, $wgMinimalPasswordLength );
- $digit = mt_rand( 0, $pwlength - 1 );
- $np = '';
- for ( $i = 0; $i < $pwlength; $i++ ) {
- $np .= $i == $digit ? chr( mt_rand( 48, 57 ) ) : $pwchars{ mt_rand( 0, $l ) };
- }
- return $np;
+ 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 );
}
/**
$this->mTouched = '0'; # Allow any pages to be cached
}
- $this->setToken(); # Random
+ $this->mToken = null; // Don't run cryptographic functions till we need a token
$this->mEmailAuthenticated = null;
$this->mEmailToken = '';
$this->mEmailTokenExpires = null;
return false;
}
- if ( isset( $_SESSION['wsToken'] ) ) {
- $passwordCorrect = $proposedUser->getToken() === $_SESSION['wsToken'];
+ if ( isset( $_SESSION['wsToken'] ) && $_SESSION['wsToken'] ) {
+ $passwordCorrect = $proposedUser->getToken( false ) === $_SESSION['wsToken'];
$from = 'session';
- } else if ( $wgRequest->getCookie( 'Token' ) !== null ) {
- $passwordCorrect = $proposedUser->getToken() === $wgRequest->getCookie( 'Token' );
+ } elseif ( $wgRequest->getCookie( 'Token' ) ) {
+ $passwordCorrect = $proposedUser->getToken( false ) === $wgRequest->getCookie( 'Token' );
$from = 'cookie';
} else {
# No session or persistent login cookie
$this->decodeOptions( $row->user_options );
$this->mTouched = wfTimestamp(TS_MW,$row->user_touched);
$this->mToken = $row->user_token;
+ if ( $this->mToken == '' ) {
+ $this->mToken = null;
+ }
$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 );
/**
* Get the user's current token.
+ * @param $forceCreation Force the generation of a new token if the user doesn't have one (default=true for backwards compatibility)
* @return \string Token
*/
- function getToken() {
+ function getToken( $forceCreation = true ) {
$this->load();
+ if ( !$this->mToken && $forceCreation ) {
+ $this->setToken();
+ }
return $this->mToken;
}
global $wgSecretKey, $wgProxyKey;
$this->load();
if ( !$token ) {
- if ( $wgSecretKey ) {
- $key = $wgSecretKey;
- } elseif ( $wgProxyKey ) {
- $key = $wgProxyKey;
- } else {
- $key = microtime();
- }
- $this->mToken = md5( $key . mt_rand( 0, 0x7fffffff ) . wfWikiID() . $this->mId );
+ $this->mToken = MWCryptRand::generateHex( USER_TOKEN_LENGTH );
} else {
$this->mToken = $token;
}
function setCookies() {
$this->load();
if ( 0 == $this->mId ) return;
+ if ( !$this->mToken ) {
+ // When token is empty or NULL generate a new one and then save it to the database
+ // This allows a wiki to re-secure itself after a leak of it's user table or $wgSecretKey
+ // Simply by setting every cell in the user_token column to NULL and letting them be
+ // regenerated as users log back into the wiki.
+ $this->setToken();
+ $this->saveSettings();
+ }
$session = array(
'wsUserID' => $this->mId,
'wsToken' => $this->mToken,
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_options' => '',
'user_touched' => $dbw->timestamp( $this->mTouched ),
- 'user_token' => $this->mToken,
+ 'user_token' => strval( $this->mToken ),
'user_email_token' => $this->mEmailToken,
'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
), array( /* WHERE */
'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
'user_real_name' => $user->mRealName,
'user_options' => '',
- 'user_token' => $user->mToken,
+ 'user_token' => strval( $user->mToken ),
'user_registration' => $dbw->timestamp( $user->mRegistration ),
'user_editcount' => 0,
);
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_real_name' => $this->mRealName,
'user_options' => '',
- 'user_token' => $this->mToken,
+ 'user_token' => strval( $this->mToken ),
'user_registration' => $dbw->timestamp( $this->mRegistration ),
'user_editcount' => 0,
), __METHOD__
return EDIT_TOKEN_SUFFIX;
} else {
if( !isset( $_SESSION['wsEditToken'] ) ) {
- $token = self::generateToken();
+ $token = MWCryptRand::generateHex( 32 );
$_SESSION['wsEditToken'] = $token;
} else {
$token = $_SESSION['wsEditToken'];
* @return \string The new random token
*/
public static function generateToken( $salt = '' ) {
- $token = dechex( mt_rand() ) . dechex( mt_rand() );
- return md5( $token . $salt );
+ return MWCryptRand::generateHex( 32 );
}
/**
$now = time();
$expires = $now + 7 * 24 * 60 * 60;
$expiration = wfTimestamp( TS_MW, $expires );
- $token = self::generateToken( $this->mId . $this->mEmail . $expires );
+ $token = MWCryptRand::generateHex( 32 );
$hash = md5( $token );
$this->load();
$this->mEmailToken = $hash;
if( $wgPasswordSalt ) {
if ( $salt === false ) {
- $salt = substr( wfGenerateToken(), 0, 8 );
+ $salt = MWCryptRand::generateHex( 8 );
}
return ':B:' . $salt . ':' . md5( $salt . '-' . md5( $password ) );
} else {
} elseif ( $type == ':B:' ) {
# Salted
list( $salt, $realHash ) = explode( ':', substr( $hash, 3 ), 2 );
- return md5( $salt.'-'.md5( $password ) ) == $realHash;
+ return md5( $salt.'-'.md5( $password ) ) === $realHash;
} else {
# Old-style
return self::oldCrypt( $password, $userId ) === $hash;