53 private $userIdentityUtils;
86 private $userBlockCache;
89 private $createAccountBlockCache;
103 LoggerInterface $logger,
109 $this->options = $options;
110 $this->userFactory = $userFactory;
111 $this->userIdentityUtils = $userIdentityUtils;
112 $this->logger = $logger;
113 $this->hookRunner =
new HookRunner( $hookContainer );
114 $this->blockStore = $blockStore;
115 $this->proxyLookup = $proxyLookup;
118 $this->createAccountBlockCache =
new BlockCache;
160 $disableIpBlockExemptChecking =
false
165 $checkIpBlocks = $request &&
170 !$disableIpBlockExemptChecking &&
171 !$this->isIpBlockExempt( $user );
175 $checkIpBlocks ? $request :
null,
202 $fromPrimary = !$fromReplica;
212 $block = $this->userBlockCache->get( $cacheKey );
213 if ( $block !==
null ) {
214 $this->logger->debug(
"Block cache hit with key {$cacheKey}" );
215 return $block ?:
null;
217 $this->logger->debug(
"Block cache miss with key {$cacheKey}" );
222 $ip = $request->
getIP();
225 $applySoftBlocks = !$this->userIdentityUtils->isNamed( $user );
227 $xff = $request->
getHeader(
'X-Forwarded-For' );
229 $blocks = array_merge(
230 $this->blockStore->newListFromTarget( $user, $ip, $fromPrimary ),
231 $this->getSystemIpBlocks( $ip, $applySoftBlocks ),
232 $this->getXffBlocks( $ip, $xff, $applySoftBlocks, $fromPrimary ),
233 $this->getCookieBlock( $user, $request )
240 $blocks = $this->blockStore->newListFromTarget( $user,
null, $fromPrimary );
244 $block = $this->createGetBlockResult( $ip, $blocks );
246 $legacyUser = $this->userFactory->newFromUserIdentity( $user );
247 $this->hookRunner->onGetUserBlock( clone $legacyUser, $ip, $block );
249 $this->userBlockCache->set( $cacheKey, $block ?: false );
259 $this->userBlockCache->clearUser( $user );
260 $this->createAccountBlockCache->clearUser( $user );
280 $cachedBlock = $this->createAccountBlockCache->get( $key );
281 if ( $cachedBlock !==
null ) {
282 $this->logger->debug(
"Create account block cache hit with key {$key}" );
283 return $cachedBlock ?:
null;
285 $this->logger->debug(
"Create account block cache miss with key {$key}" );
287 $applicableBlocks = [];
288 $userBlock = $this->getBlock( $user, $request, $fromReplica );
290 $applicableBlocks = $userBlock->toArray();
297 $ipBlock = $this->blockStore->newFromTarget(
298 null, $request->
getIP()
301 $applicableBlocks = array_merge( $applicableBlocks, $ipBlock->toArray() );
305 foreach ( $applicableBlocks as $i => $block ) {
306 if ( !$block->appliesToRight(
'createaccount' ) ) {
307 unset( $applicableBlocks[$i] );
310 $result = $this->createGetBlockResult(
311 $request ? $request->
getIP() :
null,
314 $this->createAccountBlockCache->set( $key, $result ?: false );
338 $blocks = $block->getOriginalBlocks();
339 $originalCount = count( $blocks );
340 foreach ( $blocks as $i => $originalBlock ) {
341 if ( !$callback( $originalBlock ) ) {
342 unset( $blocks[$i] );
347 } elseif ( count( $blocks ) === 1 ) {
348 return $blocks[ array_key_first( $blocks ) ];
349 } elseif ( count( $blocks ) === $originalCount ) {
352 return $block->withOriginalBlocks( array_values( $blocks ) );
354 } elseif ( !$callback( $block ) ) {
366 private function isIpBlockExempt(
UserIdentity $user ) {
368 ->userHasRight( $user,
'ipblock-exempt' );
376 private function createGetBlockResult( ?
string $ip, array $blocks ): ?AbstractBlock {
378 $blocks = $this->getUniqueBlocks( $blocks );
380 if ( count( $blocks ) === 0 ) {
382 } elseif ( count( $blocks ) === 1 ) {
386 $compositeBlock->setTarget( $ip );
387 return $compositeBlock;
401 if ( !IPUtils::isValid( $ip ) ) {
405 $blocks = array_merge(
406 $this->blockStore->newListFromTarget( $ip, $ip, !$fromReplica ),
407 $this->getSystemIpBlocks( $ip,
true )
410 return $this->createGetBlockResult( $ip, $blocks );
420 private function getCookieBlock( UserIdentity $user,
WebRequest $request ): array {
421 $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
423 return $cookieBlock instanceof DatabaseBlock ? [ $cookieBlock ] : [];
433 private function getSystemIpBlocks(
string $ip,
bool $applySoftBlocks ): array {
437 if ( !in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) ) {
439 if ( $this->isLocallyBlockedProxy( $ip ) ) {
440 $blocks[] =
new SystemBlock( [
441 'reason' =>
new Message(
'proxyblockreason' ),
443 'systemBlock' =>
'proxy',
445 } elseif ( $applySoftBlocks && $this->isDnsBlacklisted( $ip ) ) {
446 $blocks[] =
new SystemBlock( [
447 'reason' =>
new Message(
'sorbsreason' ),
450 'systemBlock' =>
'dnsbl',
456 if ( $applySoftBlocks && IPUtils::isInRanges( $ip, $this->options->get( MainConfigNames::SoftBlockRanges ) ) ) {
457 $blocks[] =
new SystemBlock( [
459 'reason' =>
new Message(
'softblockrangesreason', [ $ip ] ),
461 'systemBlock' =>
'wgSoftBlockRanges',
479 private function getXffBlocks(
482 bool $applySoftBlocks,
486 if ( $this->options->get( MainConfigNames::ApplyIpBlocksToXff )
487 && !in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) )
489 $xff = array_map(
'trim', explode(
',', $xff ) );
490 $xff = array_diff( $xff, [ $ip ] );
491 $xffblocks = $this->getBlocksForIPList( $xff, $applySoftBlocks, $fromPrimary );
495 $xffblocks = array_filter( $xffblocks,
static function ( $block ) {
496 return $block->getType() !== Block::TYPE_AUTO;
518 if ( $ipChain === [] ) {
523 foreach ( array_unique( $ipChain ) as $ipaddr ) {
529 if ( !IPUtils::isValid( $ipaddr ) ) {
533 if ( $this->proxyLookup->isTrustedProxy( $ipaddr ) ) {
538 return $this->blockStore->newListFromIPs( $ips, $applySoftBlocks, $fromPrimary );
551 private function getUniqueBlocks( array $blocks ) {
553 $databaseBlocks = [];
555 foreach ( $blocks as $block ) {
557 $systemBlocks[] = $block;
558 } elseif ( $block->getType() === DatabaseBlock::TYPE_AUTO ) {
560 '@phan-var DatabaseBlock $block';
561 if ( !isset( $databaseBlocks[$block->getParentBlockId()] ) ) {
562 $databaseBlocks[$block->getParentBlockId()] = $block;
566 $databaseBlocks[$block->getId()] = $block;
570 return array_values( array_merge( $systemBlocks, $databaseBlocks ) );
584 private function getBlockFromCookieValue(
588 $cookieValue = $request->getCookie(
'BlockID' );
589 if ( $cookieValue ===
null ) {
593 $blockCookieId = $this->getIdFromCookieValue( $cookieValue );
594 if ( $blockCookieId !==
null ) {
595 $block = $this->blockStore->newFromID( $blockCookieId );
597 $block instanceof DatabaseBlock &&
598 $this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
614 private function shouldApplyCookieBlock( DatabaseBlock $block, $isAnon ) {
615 if ( !$block->isExpired() ) {
616 switch ( $block->getType() ) {
617 case DatabaseBlock::TYPE_IP:
618 case DatabaseBlock::TYPE_RANGE:
622 $this->options->get( MainConfigNames::CookieSetOnIpBlock );
623 case DatabaseBlock::TYPE_USER:
624 return $block->isAutoblocking() &&
625 $this->options->get( MainConfigNames::CookieSetOnAutoblock );
639 private function isLocallyBlockedProxy( $ip ) {
640 $proxyList = $this->options->get( MainConfigNames::ProxyList );
645 if ( !is_array( $proxyList ) ) {
647 $proxyList = array_map(
'trim', file( $proxyList ) );
650 $proxyListIPSet =
new IPSet( $proxyList );
651 return $proxyListIPSet->match( $ip );
662 if ( !$this->options->get( MainConfigNames::EnableDnsBlacklist ) ||
663 ( $checkAllowed && in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) )
668 return $this->inDnsBlacklist( $ip, $this->options->get( MainConfigNames::DnsBlacklistUrls ) );
678 private function inDnsBlacklist( $ip, array $bases ) {
681 if ( IPUtils::isIPv4( $ip ) ) {
683 $ipReversed = implode(
'.', array_reverse( explode(
'.', $ip ) ) );
685 foreach ( $bases as $base ) {
689 if ( is_array( $base ) ) {
690 if ( count( $base ) >= 2 ) {
692 $hostname =
"{$base[1]}.$ipReversed.{$base[0]}";
694 $hostname =
"$ipReversed.{$base[0]}";
696 $basename = $base[0];
698 $hostname =
"$ipReversed.$base";
702 $ipList = $this->checkHost( $hostname );
706 'Hostname {hostname} is {ipList}, it\'s a proxy says {basename}!',
708 'hostname' => $hostname,
709 'ipList' => $ipList[0],
710 'basename' => $basename,
717 $this->logger->debug(
"Requested $hostname, not found in $basename." );
731 return gethostbynamel( $hostname );
756 if ( $request->getCookie(
'BlockID' ) !==
null ) {
757 $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
758 if ( $cookieBlock && $this->shouldApplyCookieBlock( $cookieBlock, $user->
isAnon() ) ) {
762 $this->clearBlockCookie( $response );
770 throw new LogicException( __METHOD__ .
' requires a loaded User object' );
773 throw new LogicException( __METHOD__ .
' must be called pre-send' );
777 $isAnon = $user->
isAnon();
780 foreach ( $block->toArray() as $originalBlock ) {
783 && $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon )
785 $this->setBlockCookie( $originalBlock, $response );
806 if ( $expiryTime ===
'infinity' || $expiryTime > $maxExpiryTime ) {
807 $expiryTime = $maxExpiryTime;
811 $expiryValue = (int)
wfTimestamp( TS_UNIX, $expiryTime );
812 $cookieOptions = [
'httpOnly' => false ];
813 $cookieValue = $this->getCookieValue( $block );
814 $response->
setCookie(
'BlockID', $cookieValue, $expiryValue, $cookieOptions );
824 private function shouldTrackBlockWithCookie( DatabaseBlock $block, $isAnon ) {
825 switch ( $block->getType() ) {
826 case DatabaseBlock::TYPE_IP:
827 case DatabaseBlock::TYPE_RANGE:
828 return $isAnon && $this->options->get( MainConfigNames::CookieSetOnIpBlock );
829 case DatabaseBlock::TYPE_USER:
831 $this->options->get( MainConfigNames::CookieSetOnAutoblock ) &&
832 $block->isAutoblocking();
845 $response->
clearCookie(
'BlockID', [
'httpOnly' =>
false ] );
856 private function getIdFromCookieValue( $cookieValue ) {
858 if ( !is_numeric( substr( $cookieValue, 0, 1 ) ) ) {
863 $bangPos = strpos( $cookieValue,
'!' );
864 $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
865 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
869 $storedHmac = substr( $cookieValue, $bangPos + 1 );
870 $calculatedHmac = MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
871 if ( $calculatedHmac === $storedHmac ) {
887 private function getCookieValue( DatabaseBlock $block ) {
888 $id = (string)$block->getId();
889 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
893 $hmac =
MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
894 return $id .
'!' . $hmac;