* of the database.
*/
class User implements IDBAccessObject, UserIdentity {
+
/**
- * @const int Number of characters in user_token field.
+ * Number of characters required for the user_token field.
*/
const TOKEN_LENGTH = 32;
/**
- * @const string An invalid value for user_token
+ * An invalid string value for the user_token field.
*/
const INVALID_TOKEN = '*** INVALID ***';
/**
- * @const int Serialized record version.
+ * Version number to tag cached versions of serialized User objects. Should be increased when
+ * {@link $mCacheVars} or one of it's members changes.
*/
const VERSION = 13;
return $name;
}
- /**
- * Return a random password.
- *
- * @deprecated since 1.27, use PasswordFactory::generateRandomPasswordString()
- * @return string New random password
- */
- public static function randomPassword() {
- global $wgMinimalPasswordLength;
- return PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
- }
-
/**
* Set cached properties to default.
*
$user = $session->getUser();
if ( $user->isLoggedIn() ) {
$this->loadFromUserObject( $user );
- if ( $user->isBlocked() ) {
+ if ( $user->getBlock() ) {
// If this user is autoblocked, set a cookie to track the Block. This has to be done on
// every session load, because an autoblocked editor might not edit again from the same
// IP address after being blocked.
* protected against race conditions using a compare-and-set (CAS) mechanism
* based on comparing $this->mTouched with the user_touched field.
*
- * @param Database $db
+ * @param IDatabase $db
* @param array $conditions WHERE conditions for use with Database::update
* @return array WHERE conditions for use with Database::update
*/
- protected function makeUpdateConditions( Database $db, array $conditions ) {
+ protected function makeUpdateConditions( IDatabase $db, array $conditions ) {
if ( $this->mTouched ) {
// CAS check: only update if the row wasn't changed sicne it was loaded.
$conditions['user_touched'] = $db->timestamp( $this->mTouched );
if ( $success ) {
$this->mTouched = $newTouched;
- $this->clearSharedCache();
+ $this->clearSharedCache( 'changed' );
} else {
// Clears on failure too since that is desired if the cache is stale
$this->clearSharedCache( 'refresh' );
/**
* Get blocking information
+ *
+ * TODO: Move this into the BlockManager, along with block-related properties.
+ *
* @param bool $fromReplica Whether to check the replica DB first.
* To improve performance, non-critical checks are done against replica DBs.
* Check when actually saving should be done against master.
*/
private function getBlockedStatus( $fromReplica = true ) {
- global $wgProxyWhitelist, $wgApplyIpBlocksToXff, $wgSoftBlockRanges;
-
if ( $this->mBlockedby != -1 ) {
return;
}
// overwriting mBlockedby, surely?
$this->load();
- # We only need to worry about passing the IP address to the Block generator if the
- # user is not immune to autoblocks/hardblocks, and they are the current user so we
- # know which IP address they're actually coming from
- $ip = null;
- $sessionUser = RequestContext::getMain()->getUser();
- // the session user is set up towards the end of Setup.php. Until then,
- // assume it's a logged-out user.
- $globalUserName = $sessionUser->isSafeToLoad()
- ? $sessionUser->getName()
- : IP::sanitizeIP( $sessionUser->getRequest()->getIP() );
- if ( $this->getName() === $globalUserName && !$this->isAllowed( 'ipblock-exempt' ) ) {
- $ip = $this->getRequest()->getIP();
- }
-
- // User/IP blocking
- $block = Block::newFromTarget( $this, $ip, !$fromReplica );
-
- // Cookie blocking
- if ( !$block instanceof Block ) {
- $block = $this->getBlockFromCookieValue( $this->getRequest()->getCookie( 'BlockID' ) );
- }
-
- // Proxy blocking
- if ( !$block instanceof Block && $ip !== null && !in_array( $ip, $wgProxyWhitelist ) ) {
- // Local list
- if ( self::isLocallyBlockedProxy( $ip ) ) {
- $block = new Block( [
- 'byText' => wfMessage( 'proxyblocker' )->text(),
- 'reason' => wfMessage( 'proxyblockreason' )->plain(),
- 'address' => $ip,
- 'systemBlock' => 'proxy',
- ] );
- } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
- $block = new Block( [
- 'byText' => wfMessage( 'sorbs' )->text(),
- 'reason' => wfMessage( 'sorbsreason' )->plain(),
- 'address' => $ip,
- 'systemBlock' => 'dnsbl',
- ] );
- }
- }
-
- // (T25343) Apply IP blocks to the contents of XFF headers, if enabled
- if ( !$block instanceof Block
- && $wgApplyIpBlocksToXff
- && $ip !== null
- && !in_array( $ip, $wgProxyWhitelist )
- ) {
- $xff = $this->getRequest()->getHeader( 'X-Forwarded-For' );
- $xff = array_map( 'trim', explode( ',', $xff ) );
- $xff = array_diff( $xff, [ $ip ] );
- $xffblocks = Block::getBlocksForIPList( $xff, $this->isAnon(), !$fromReplica );
- $block = Block::chooseBlock( $xffblocks, $xff );
- if ( $block instanceof Block ) {
- # Mangle the reason to alert the user that the block
- # originated from matching the X-Forwarded-For header.
- $block->setReason( wfMessage( 'xffblockreason', $block->getReason() )->plain() );
- }
- }
-
- if ( !$block instanceof Block
- && $ip !== null
- && $this->isAnon()
- && IP::isInRanges( $ip, $wgSoftBlockRanges )
- ) {
- $block = new Block( [
- 'address' => $ip,
- 'byText' => 'MediaWiki default',
- 'reason' => wfMessage( 'softblockrangesreason', $ip )->plain(),
- 'anonOnly' => true,
- 'systemBlock' => 'wgSoftBlockRanges',
- ] );
- }
+ $block = MediaWikiServices::getInstance()->getBlockManager()->getUserBlock(
+ $this,
+ $fromReplica
+ );
if ( $block instanceof Block ) {
wfDebug( __METHOD__ . ": Found block.\n" );
Hooks::run( 'GetBlockedStatus', [ &$thisUser ] );
}
- /**
- * Try to load a Block from an ID given in a cookie value.
- * @param string|null $blockCookieVal The cookie value to check.
- * @return Block|bool The Block object, or false if none could be loaded.
- */
- protected function getBlockFromCookieValue( $blockCookieVal ) {
- // Make sure there's something to check. The cookie value must start with a number.
- if ( strlen( $blockCookieVal ) < 1 || !is_numeric( substr( $blockCookieVal, 0, 1 ) ) ) {
- return false;
- }
- // Load the Block from the ID in the cookie.
- $blockCookieId = Block::getIdFromCookieValue( $blockCookieVal );
- if ( $blockCookieId !== null ) {
- // An ID was found in the cookie.
- $tmpBlock = Block::newFromID( $blockCookieId );
- if ( $tmpBlock instanceof Block ) {
- $config = RequestContext::getMain()->getConfig();
-
- switch ( $tmpBlock->getType() ) {
- case Block::TYPE_USER:
- $blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking();
- $useBlockCookie = ( $config->get( 'CookieSetOnAutoblock' ) === true );
- break;
- case Block::TYPE_IP:
- case Block::TYPE_RANGE:
- // If block is type IP or IP range, load only if user is not logged in (T152462)
- $blockIsValid = !$tmpBlock->isExpired() && !$this->isLoggedIn();
- $useBlockCookie = ( $config->get( 'CookieSetOnIpBlock' ) === true );
- break;
- default:
- $blockIsValid = false;
- $useBlockCookie = false;
- }
-
- if ( $blockIsValid && $useBlockCookie ) {
- // Use the block.
- return $tmpBlock;
- }
-
- // If the block is not valid, remove the cookie.
- Block::clearCookie( $this->getRequest()->response() );
- } else {
- // If the block doesn't exist, remove the cookie.
- Block::clearCookie( $this->getRequest()->response() );
- }
- }
- return false;
- }
-
/**
* Whether the given IP is in a DNS blacklist.
*
+ * @deprecated since 1.34 Use BlockManager::isDnsBlacklisted.
* @param string $ip IP to check
* @param bool $checkWhitelist Whether to check the whitelist first
* @return bool True if blacklisted.
*/
public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
- global $wgEnableDnsBlacklist, $wgDnsBlacklistUrls, $wgProxyWhitelist;
-
- if ( !$wgEnableDnsBlacklist ||
- ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) )
- ) {
- return false;
- }
-
- return $this->inDnsBlacklist( $ip, $wgDnsBlacklistUrls );
+ return MediaWikiServices::getInstance()->getBlockManager()
+ ->isDnsBlacklisted( $ip, $checkWhitelist );
}
/**
* Whether the given IP is in a given DNS blacklist.
*
+ * @deprecated since 1.34 Check via BlockManager::isDnsBlacklisted instead.
* @param string $ip IP to check
* @param string|array $bases Array of Strings: URL of the DNS blacklist
* @return bool True if blacklisted.
*/
public function inDnsBlacklist( $ip, $bases ) {
+ wfDeprecated( __METHOD__, '1.34' );
+
$found = false;
// @todo FIXME: IPv6 ??? (https://bugs.php.net/bug.php?id=33170)
if ( IP::isIPv4( $ip ) ) {
/**
* Check if an IP address is in the local proxy list
*
+ * @deprecated since 1.34 Use BlockManager::getUserBlock instead.
* @param string $ip
- *
* @return bool
*/
public static function isLocallyBlockedProxy( $ip ) {
+ wfDeprecated( __METHOD__, '1.34' );
+
global $wgProxyList;
if ( !$wgProxyList ) {
// Set the user limit key
if ( $userLimit !== false ) {
+ // phan is confused because &can-bypass's value is a bool, so it assumes
+ // that $userLimit is also a bool here.
+ // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
list( $max, $period ) = $userLimit;
wfDebug( __METHOD__ . ": effective user limit: $max in {$period}s\n" );
$keys[$cache->makeKey( 'limiter', $action, 'user', $id )] = $userLimit;
$triggered = false;
foreach ( $keys as $key => $limit ) {
+ // phan is confused because &can-bypass's value is a bool, so it assumes
+ // that $userLimit is also a bool here.
+ // @phan-suppress-next-line PhanTypeInvalidExpressionArrayDestructuring
list( $max, $period ) = $limit;
$summary = "(limit $max in {$period}s)";
$count = $cache->get( $key );
/**
* Check if user is blocked
*
+ * @deprecated since 1.34, use User::getBlock() or
+ * PermissionManager::isBlockedFrom() or
+ * PermissionManager::userCan() instead.
+ *
* @param bool $fromReplica Whether to check the replica DB instead of
* the master. Hacked from false due to horrible probs on site.
* @return bool True if blocked, false otherwise
*
* Called implicitly from invalidateCache() and saveSettings().
*
- * @param string $mode Use 'refresh' to clear now; otherwise before DB commit
+ * @param string $mode Use 'refresh' to clear now or 'changed' to clear before DB commit
*/
- public function clearSharedCache( $mode = 'changed' ) {
+ public function clearSharedCache( $mode = 'refresh' ) {
if ( !$this->getId() ) {
return;
}
+ $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
$key = $this->getCacheKey( $cache );
+
if ( $mode === 'refresh' ) {
- $cache->delete( $key, 1 );
+ $cache->delete( $key, 1 ); // low tombstone/"hold-off" TTL
} else {
- $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
- if ( $lb->hasOrMadeRecentMasterChanges() ) {
- $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
- function () use ( $cache, $key ) {
- $cache->delete( $key );
- },
- __METHOD__
- );
- } else {
- $cache->delete( $key );
- }
+ $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle(
+ function () use ( $cache, $key ) {
+ $cache->delete( $key );
+ },
+ __METHOD__
+ );
}
}
*/
public function invalidateCache() {
$this->touch();
- $this->clearSharedCache();
+ $this->clearSharedCache( 'changed' );
}
/**
// $user->isAllowed(). It is also checked in Title::checkUserBlock()
// to give a better error message in the common case.
$config = RequestContext::getMain()->getConfig();
+ // @TODO Partial blocks should not prevent the user from logging in.
+ // see: https://phabricator.wikimedia.org/T208895
if (
$this->isLoggedIn() &&
$config->get( 'BlockDisablesLogin' ) &&
- $this->isBlocked()
+ $this->getBlock()
) {
$anon = new User;
$this->mRights = array_intersect( $this->mRights, $anon->getRights() );
$newTouched = $this->newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
- $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) {
+ $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $newTouched ) {
global $wgActorTableSchemaMigrationStage;
$dbw->update( 'user',
$this->saveOptions();
Hooks::run( 'UserSaveSettings', [ $this ] );
- $this->clearSharedCache();
+ $this->clearSharedCache( 'changed' );
$this->getUserPage()->purgeSquid();
}
$fields["user_$name"] = $value;
}
- return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) {
+ return $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) use ( $fields ) {
$dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
if ( $dbw->affectedRows() ) {
$newUser = self::newFromId( $dbw->insertId() );
$this->mTouched = $this->newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
- $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
+ $status = $dbw->doAtomicSection( __METHOD__, function ( IDatabase $dbw, $fname ) {
$noPass = PasswordFactory::newInvalidPassword()->toString();
$dbw->insert( 'user',
[
* @return bool A block was spread
*/
public function spreadAnyEditBlock() {
- if ( $this->isLoggedIn() && $this->isBlocked() ) {
+ if ( $this->isLoggedIn() && $this->getBlock() ) {
return $this->spreadBlock();
}
return $wgImplicitGroups;
}
- /**
- * Get the title of a page describing a particular group
- * @deprecated since 1.29 Use UserGroupMembership::getGroupPage instead
- *
- * @param string $group Internal group name
- * @return Title|bool Title of the page if it exists, false otherwise
- */
- public static function getGroupPage( $group ) {
- wfDeprecated( __METHOD__, '1.29' );
- return UserGroupMembership::getGroupPage( $group );
- }
-
- /**
- * Create a link to the group in HTML, if available;
- * else return the group name.
- * @deprecated since 1.29 Use UserGroupMembership::getLink instead, or
- * make the link yourself if you need custom text
- *
- * @param string $group Internal name of the group
- * @param string $text The text of the link
- * @return string HTML link to the group
- */
- public static function makeGroupLinkHTML( $group, $text = '' ) {
- wfDeprecated( __METHOD__, '1.29' );
-
- if ( $text == '' ) {
- $text = UserGroupMembership::getGroupName( $group );
- }
- $title = UserGroupMembership::getGroupPage( $group );
- if ( $title ) {
- return MediaWikiServices::getInstance()
- ->getLinkRenderer()->makeLink( $title, $text );
- }
-
- return htmlspecialchars( $text );
- }
-
- /**
- * Create a link to the group in Wikitext, if available;
- * else return the group name.
- * @deprecated since 1.29 Use UserGroupMembership::getLink instead, or
- * make the link yourself if you need custom text
- *
- * @param string $group Internal name of the group
- * @param string $text The text of the link
- * @return string Wikilink to the group
- */
- public static function makeGroupLinkWiki( $group, $text = '' ) {
- wfDeprecated( __METHOD__, '1.29' );
-
- if ( $text == '' ) {
- $text = UserGroupMembership::getGroupName( $group );
- }
- $title = UserGroupMembership::getGroupPage( $group );
- if ( $title ) {
- $page = $title->getFullText();
- return "[[$page|$text]]";
- }
-
- return $text;
- }
-
/**
* Returns an array of the groups that a particular group can add/remove.
*