X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fblock%2FBlockManager.php;h=c82ed1c258a27ab3382f6cf2130ab892886e9c46;hb=f7cddcf7c178cb90e993fe3690f310ab06f6a846;hp=abd2db24af2de43d47923db4e47c884989170d7b;hpb=78354323c1d1f8d8117a5d5c2955b6a94d08f318;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/block/BlockManager.php b/includes/block/BlockManager.php index abd2db24af..c82ed1c258 100644 --- a/includes/block/BlockManager.php +++ b/includes/block/BlockManager.php @@ -21,7 +21,9 @@ namespace MediaWiki\Block; use DateTime; +use DeferredUpdates; use IP; +use MediaWiki\Config\ServiceOptions; use MediaWiki\User\UserIdentity; use MWCryptHash; use User; @@ -43,70 +45,38 @@ class BlockManager { /** @var WebRequest */ private $currentRequest; - /** @var bool */ - private $applyIpBlocksToXff; - - /** @var bool */ - private $cookieSetOnAutoblock; - - /** @var bool */ - private $cookieSetOnIpBlock; - - /** @var array */ - private $dnsBlacklistUrls; - - /** @var bool */ - private $enableDnsBlacklist; - - /** @var array */ - private $proxyList; - - /** @var array */ - private $proxyWhitelist; - - /** @var string|bool */ - private $secretKey; - - /** @var array */ - private $softBlockRanges; + /** + * TODO Make this a const when HHVM support is dropped (T192166) + * + * @var array + * @since 1.34 + * */ + public static $constructorOptions = [ + 'ApplyIpBlocksToXff', + 'CookieSetOnAutoblock', + 'CookieSetOnIpBlock', + 'DnsBlacklistUrls', + 'EnableDnsBlacklist', + 'ProxyList', + 'ProxyWhitelist', + 'SecretKey', + 'SoftBlockRanges', + ]; /** + * @param ServiceOptions $options * @param User $currentUser * @param WebRequest $currentRequest - * @param bool $applyIpBlocksToXff - * @param bool $cookieSetOnAutoblock - * @param bool $cookieSetOnIpBlock - * @param array $dnsBlacklistUrls - * @param bool $enableDnsBlacklist - * @param array $proxyList - * @param array $proxyWhitelist - * @param string $secretKey - * @param array $softBlockRanges */ public function __construct( - $currentUser, - $currentRequest, - $applyIpBlocksToXff, - $cookieSetOnAutoblock, - $cookieSetOnIpBlock, - $dnsBlacklistUrls, - $enableDnsBlacklist, - $proxyList, - $proxyWhitelist, - $secretKey, - $softBlockRanges + ServiceOptions $options, + User $currentUser, + WebRequest $currentRequest ) { + $options->assertRequiredOptions( self::$constructorOptions ); + $this->options = $options; $this->currentUser = $currentUser; $this->currentRequest = $currentRequest; - $this->applyIpBlocksToXff = $applyIpBlocksToXff; - $this->cookieSetOnAutoblock = $cookieSetOnAutoblock; - $this->cookieSetOnIpBlock = $cookieSetOnIpBlock; - $this->dnsBlacklistUrls = $dnsBlacklistUrls; - $this->enableDnsBlacklist = $enableDnsBlacklist; - $this->proxyList = $proxyList; - $this->proxyWhitelist = $proxyWhitelist; - $this->secretKey = $secretKey; - $this->softBlockRanges = $softBlockRanges; } /** @@ -156,7 +126,7 @@ class BlockManager { } // Proxy blocking - if ( $ip !== null && !in_array( $ip, $this->proxyWhitelist ) ) { + if ( $ip !== null && !in_array( $ip, $this->options->get( 'ProxyWhitelist' ) ) ) { // Local list if ( $this->isLocallyBlockedProxy( $ip ) ) { $blocks[] = new SystemBlock( [ @@ -176,9 +146,9 @@ class BlockManager { } // (T25343) Apply IP blocks to the contents of XFF headers, if enabled - if ( $this->applyIpBlocksToXff + if ( $this->options->get( 'ApplyIpBlocksToXff' ) && $ip !== null - && !in_array( $ip, $this->proxyWhitelist ) + && !in_array( $ip, $this->options->get( 'ProxyWhitelist' ) ) ) { $xff = $request->getHeader( 'X-Forwarded-For' ); $xff = array_map( 'trim', explode( ',', $xff ) ); @@ -191,7 +161,7 @@ class BlockManager { // Soft blocking if ( $ip !== null && $isAnon - && IP::isInRanges( $ip, $this->softBlockRanges ) + && IP::isInRanges( $ip, $this->options->get( 'SoftBlockRanges' ) ) ) { $blocks[] = new SystemBlock( [ 'address' => $ip, @@ -232,7 +202,7 @@ class BlockManager { * @param AbstractBlock[] $blocks * @return AbstractBlock[] */ - private function getUniqueBlocks( $blocks ) { + private function getUniqueBlocks( array $blocks ) { $systemBlocks = []; $databaseBlocks = []; @@ -252,7 +222,8 @@ class BlockManager { } /** - * Try to load a block from an ID given in a cookie value. + * Try to load a block from an ID given in a cookie value. If the block is invalid + * or doesn't exist, remove the cookie. * * @param UserIdentity $user * @param WebRequest $request @@ -262,43 +233,45 @@ class BlockManager { UserIdentity $user, WebRequest $request ) { - $blockCookieVal = $request->getCookie( 'BlockID' ); - $response = $request->response(); + $blockCookieId = $this->getIdFromCookieValue( $request->getCookie( 'BlockID' ) ); - // 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 = $this->getIdFromCookieValue( $blockCookieVal ); if ( $blockCookieId !== null ) { - // An ID was found in the cookie. // TODO: remove dependency on DatabaseBlock - $tmpBlock = DatabaseBlock::newFromID( $blockCookieId ); - if ( $tmpBlock instanceof DatabaseBlock ) { - switch ( $tmpBlock->getType() ) { - case DatabaseBlock::TYPE_USER: - $blockIsValid = !$tmpBlock->isExpired() && $tmpBlock->isAutoblocking(); - $useBlockCookie = ( $this->cookieSetOnAutoblock === true ); - break; - case DatabaseBlock::TYPE_IP: - case DatabaseBlock::TYPE_RANGE: - // If block is type IP or IP range, load only if user is not logged in (T152462) - $blockIsValid = !$tmpBlock->isExpired() && $user->getId() === 0; - $useBlockCookie = ( $this->cookieSetOnIpBlock === true ); - break; - default: - $blockIsValid = false; - $useBlockCookie = false; - } + $block = DatabaseBlock::newFromID( $blockCookieId ); + if ( + $block instanceof DatabaseBlock && + $this->shouldApplyCookieBlock( $block, $user->isAnon() ) + ) { + return $block; + } + $this->clearBlockCookie( $request->response() ); + } - if ( $blockIsValid && $useBlockCookie ) { - // Use the block. - return $tmpBlock; - } + return false; + } + + /** + * Check if the block loaded from the cookie should be applied. + * + * @param DatabaseBlock $block + * @param bool $isAnon The user is logged out + * @return bool The block sould be applied + */ + private function shouldApplyCookieBlock( DatabaseBlock $block, $isAnon ) { + if ( !$block->isExpired() ) { + switch ( $block->getType() ) { + case DatabaseBlock::TYPE_IP: + case DatabaseBlock::TYPE_RANGE: + // If block is type IP or IP range, load only + // if user is not logged in (T152462) + return $isAnon && + $this->options->get( 'CookieSetOnIpBlock' ); + case DatabaseBlock::TYPE_USER: + return $block->isAutoblocking() && + $this->options->get( 'CookieSetOnAutoblock' ); + default: + return false; } - // If the block is invalid or doesn't exist, remove the cookie. - $this->clearBlockCookie( $response ); } return false; } @@ -310,41 +283,17 @@ class BlockManager { * @return bool */ private function isLocallyBlockedProxy( $ip ) { - if ( !$this->proxyList ) { + $proxyList = $this->options->get( 'ProxyList' ); + if ( !$proxyList ) { return false; } - if ( !is_array( $this->proxyList ) ) { + if ( !is_array( $proxyList ) ) { // Load values from the specified file - $this->proxyList = array_map( 'trim', file( $this->proxyList ) ); - } - - $resultProxyList = []; - $deprecatedIPEntries = []; - - // backward compatibility: move all ip addresses in keys to values - foreach ( $this->proxyList as $key => $value ) { - $keyIsIP = IP::isIPAddress( $key ); - $valueIsIP = IP::isIPAddress( $value ); - if ( $keyIsIP && !$valueIsIP ) { - $deprecatedIPEntries[] = $key; - $resultProxyList[] = $key; - } elseif ( $keyIsIP && $valueIsIP ) { - $deprecatedIPEntries[] = $key; - $resultProxyList[] = $key; - $resultProxyList[] = $value; - } else { - $resultProxyList[] = $value; - } + $proxyList = array_map( 'trim', file( $proxyList ) ); } - if ( $deprecatedIPEntries ) { - wfDeprecated( - 'IP addresses in the keys of $wgProxyList (found the following IP addresses in keys: ' . - implode( ', ', $deprecatedIPEntries ) . ', please move them to values)', '1.30' ); - } - - $proxyListIPSet = new IPSet( $resultProxyList ); + $proxyListIPSet = new IPSet( $proxyList ); return $proxyListIPSet->match( $ip ); } @@ -356,13 +305,13 @@ class BlockManager { * @return bool True if blacklisted. */ public function isDnsBlacklisted( $ip, $checkWhitelist = false ) { - if ( !$this->enableDnsBlacklist || - ( $checkWhitelist && in_array( $ip, $this->proxyWhitelist ) ) + if ( !$this->options->get( 'EnableDnsBlacklist' ) || + ( $checkWhitelist && in_array( $ip, $this->options->get( 'ProxyWhitelist' ) ) ) ) { return false; } - return $this->inDnsBlacklist( $ip, $this->dnsBlacklistUrls ); + return $this->inDnsBlacklist( $ip, $this->options->get( 'DnsBlacklistUrls' ) ); } /** @@ -431,26 +380,38 @@ class BlockManager { * @param User $user */ public function trackBlockWithCookie( User $user ) { - $block = $user->getBlock(); $request = $user->getRequest(); - $response = $request->response(); - $isAnon = $user->isAnon(); - - if ( $block && $request->getCookie( 'BlockID' ) === null ) { - if ( $block instanceof CompositeBlock ) { - // TODO: Improve on simply tracking the first trackable block (T225654) - foreach ( $block->getOriginalBlocks() as $originalBlock ) { - if ( $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon ) ) { - $this->setBlockCookie( $originalBlock, $response ); - return; + if ( $request->getCookie( 'BlockID' ) !== null ) { + // User already has a block cookie + return; + } + + // Defer checks until the user has been fully loaded to avoid circular dependency + // of User on itself (T180050 and T226777) + DeferredUpdates::addCallableUpdate( + function () use ( $user, $request ) { + $block = $user->getBlock(); + $response = $request->response(); + $isAnon = $user->isAnon(); + + if ( $block ) { + if ( $block instanceof CompositeBlock ) { + // TODO: Improve on simply tracking the first trackable block (T225654) + foreach ( $block->getOriginalBlocks() as $originalBlock ) { + if ( $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon ) ) { + $this->setBlockCookie( $originalBlock, $response ); + return; + } + } + } else { + if ( $this->shouldTrackBlockWithCookie( $block, $isAnon ) ) { + $this->setBlockCookie( $block, $response ); + } } } - } else { - if ( $this->shouldTrackBlockWithCookie( $block, $isAnon ) ) { - $this->setBlockCookie( $block, $response ); - } - } - } + }, + DeferredUpdates::PRESEND + ); } /** @@ -492,9 +453,11 @@ class BlockManager { switch ( $block->getType() ) { case DatabaseBlock::TYPE_IP: case DatabaseBlock::TYPE_RANGE: - return $isAnon && $this->cookieSetOnIpBlock; + return $isAnon && $this->options->get( 'CookieSetOnIpBlock' ); case DatabaseBlock::TYPE_USER: - return !$isAnon && $this->cookieSetOnAutoblock && $block->isAutoblocking(); + return !$isAnon && + $this->options->get( 'CookieSetOnAutoblock' ) && + $block->isAutoblocking(); default: return false; } @@ -523,15 +486,20 @@ class BlockManager { * @return int|null The block ID, or null if the HMAC is present and invalid. */ public function getIdFromCookieValue( $cookieValue ) { + // The cookie value must start with a number + if ( !is_numeric( substr( $cookieValue, 0, 1 ) ) ) { + return null; + } + // Extract the ID prefix from the cookie value (may be the whole value, if no bang found). $bangPos = strpos( $cookieValue, '!' ); $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos ); - if ( !$this->secretKey ) { + if ( !$this->options->get( 'SecretKey' ) ) { // If there's no secret key, just use the ID as given. return $id; } $storedHmac = substr( $cookieValue, $bangPos + 1 ); - $calculatedHmac = MWCryptHash::hmac( $id, $this->secretKey, false ); + $calculatedHmac = MWCryptHash::hmac( $id, $this->options->get( 'SecretKey' ), false ); if ( $calculatedHmac === $storedHmac ) { return $id; } else { @@ -552,11 +520,11 @@ class BlockManager { */ public function getCookieValue( DatabaseBlock $block ) { $id = $block->getId(); - if ( !$this->secretKey ) { + if ( !$this->options->get( 'SecretKey' ) ) { // If there's no secret key, don't append a HMAC. return $id; } - $hmac = MWCryptHash::hmac( $id, $this->secretKey, false ); + $hmac = MWCryptHash::hmac( $id, $this->options->get( 'SecretKey' ), false ); $cookieValue = $id . '!' . $hmac; return $cookieValue; }