67 private LoggerInterface $logger;
79 LoggerInterface $logger,
85 $this->options = $options;
86 $this->userFactory = $userFactory;
87 $this->userIdentityUtils = $userIdentityUtils;
88 $this->logger = $logger;
89 $this->hookRunner =
new HookRunner( $hookContainer );
90 $this->blockStore = $blockStore;
91 $this->proxyLookup = $proxyLookup;
94 $this->createAccountBlockCache =
new BlockCache;
136 $disableIpBlockExemptChecking =
false
141 $checkIpBlocks = $request &&
146 !$disableIpBlockExemptChecking &&
147 !$this->isIpBlockExempt( $user );
151 $checkIpBlocks ? $request :
null,
178 $fromPrimary = !$fromReplica;
188 $block = $this->userBlockCache->get( $cacheKey );
189 if ( $block !==
null ) {
190 $this->logger->debug(
"Block cache hit with key {$cacheKey}" );
191 return $block ?:
null;
193 $this->logger->debug(
"Block cache miss with key {$cacheKey}" );
198 $ip = $request->
getIP();
201 $applySoftBlocks = !$this->userIdentityUtils->isNamed( $user );
203 $xff = $request->
getHeader(
'X-Forwarded-For' );
205 $blocks = array_merge(
206 $this->blockStore->newListFromTarget( $user, $ip, $fromPrimary ),
207 $this->getSystemIpBlocks( $ip, $applySoftBlocks ),
208 $this->getXffBlocks( $ip, $xff, $applySoftBlocks, $fromPrimary ),
209 $this->getCookieBlock( $user, $request )
216 $blocks = $this->blockStore->newListFromTarget( $user,
null, $fromPrimary );
220 $block = $this->createGetBlockResult( $ip, $blocks );
222 $legacyUser = $this->userFactory->newFromUserIdentity( $user );
223 $this->hookRunner->onGetUserBlock( clone $legacyUser, $ip, $block );
225 $this->userBlockCache->set( $cacheKey, $block ?: false );
235 $this->userBlockCache->clearUser( $user );
236 $this->createAccountBlockCache->clearUser( $user );
256 $cachedBlock = $this->createAccountBlockCache->get( $key );
257 if ( $cachedBlock !==
null ) {
258 $this->logger->debug(
"Create account block cache hit with key {$key}" );
259 return $cachedBlock ?:
null;
261 $this->logger->debug(
"Create account block cache miss with key {$key}" );
263 $applicableBlocks = [];
264 $userBlock = $this->getBlock( $user, $request, $fromReplica );
266 $applicableBlocks = $userBlock->toArray();
273 $ipBlock = $this->blockStore->newFromTarget(
274 null, $request->
getIP()
277 $applicableBlocks = array_merge( $applicableBlocks, $ipBlock->toArray() );
281 foreach ( $applicableBlocks as $i => $block ) {
282 if ( !$block->appliesToRight(
'createaccount' ) ) {
283 unset( $applicableBlocks[$i] );
286 $result = $this->createGetBlockResult(
287 $request ? $request->
getIP() :
null,
290 $this->createAccountBlockCache->set( $key, $result ?: false );
314 $blocks = $block->getOriginalBlocks();
315 $originalCount = count( $blocks );
316 foreach ( $blocks as $i => $originalBlock ) {
317 if ( !$callback( $originalBlock ) ) {
318 unset( $blocks[$i] );
323 } elseif ( count( $blocks ) === 1 ) {
324 return $blocks[ array_key_first( $blocks ) ];
325 } elseif ( count( $blocks ) === $originalCount ) {
328 return $block->withOriginalBlocks( array_values( $blocks ) );
330 } elseif ( !$callback( $block ) ) {
342 private function isIpBlockExempt(
UserIdentity $user ) {
344 ->userHasRight( $user,
'ipblock-exempt' );
352 private function createGetBlockResult( ?
string $ip, array $blocks ): ?AbstractBlock {
354 $blocks = $this->getUniqueBlocks( $blocks );
356 if ( count( $blocks ) === 0 ) {
358 } elseif ( count( $blocks ) === 1 ) {
362 $compositeBlock->setTarget( $ip );
363 return $compositeBlock;
377 if ( !IPUtils::isValid( $ip ) ) {
381 $blocks = array_merge(
382 $this->blockStore->newListFromTarget( $ip, $ip, !$fromReplica ),
383 $this->getSystemIpBlocks( $ip,
true )
386 return $this->createGetBlockResult( $ip, $blocks );
396 private function getCookieBlock( UserIdentity $user,
WebRequest $request ): array {
397 $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
399 return $cookieBlock instanceof DatabaseBlock ? [ $cookieBlock ] : [];
409 private function getSystemIpBlocks(
string $ip,
bool $applySoftBlocks ): array {
413 if ( !in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) ) {
415 if ( $this->isLocallyBlockedProxy( $ip ) ) {
416 $blocks[] =
new SystemBlock( [
417 'reason' =>
new Message(
'proxyblockreason' ),
419 'systemBlock' =>
'proxy',
421 } elseif ( $applySoftBlocks && $this->isDnsBlacklisted( $ip ) ) {
422 $blocks[] =
new SystemBlock( [
423 'reason' =>
new Message(
'sorbsreason' ),
426 'systemBlock' =>
'dnsbl',
432 if ( $applySoftBlocks && IPUtils::isInRanges( $ip, $this->options->get( MainConfigNames::SoftBlockRanges ) ) ) {
433 $blocks[] =
new SystemBlock( [
435 'reason' =>
new Message(
'softblockrangesreason', [ $ip ] ),
437 'systemBlock' =>
'wgSoftBlockRanges',
455 private function getXffBlocks(
458 bool $applySoftBlocks,
462 if ( $this->options->get( MainConfigNames::ApplyIpBlocksToXff )
463 && !in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) )
465 $xff = array_map(
'trim', explode(
',', $xff ) );
466 $xff = array_diff( $xff, [ $ip ] );
467 $xffblocks = $this->getBlocksForIPList( $xff, $applySoftBlocks, $fromPrimary );
471 $xffblocks = array_filter( $xffblocks,
static function ( $block ) {
472 return $block->getType() !== Block::TYPE_AUTO;
494 if ( $ipChain === [] ) {
499 foreach ( array_unique( $ipChain ) as $ipaddr ) {
505 if ( !IPUtils::isValid( $ipaddr ) ) {
509 if ( $this->proxyLookup->isTrustedProxy( $ipaddr ) ) {
514 return $this->blockStore->newListFromIPs( $ips, $applySoftBlocks, $fromPrimary );
527 private function getUniqueBlocks( array $blocks ) {
529 $databaseBlocks = [];
531 foreach ( $blocks as $block ) {
533 $systemBlocks[] = $block;
534 } elseif ( $block->getType() === DatabaseBlock::TYPE_AUTO && $block instanceof DatabaseBlock ) {
535 if ( !isset( $databaseBlocks[$block->getParentBlockId()] ) ) {
536 $databaseBlocks[$block->getParentBlockId()] = $block;
540 $databaseBlocks[$block->getId()] = $block;
544 return array_values( array_merge( $systemBlocks, $databaseBlocks ) );
558 private function getBlockFromCookieValue(
562 $cookieValue = $request->getCookie(
'BlockID' );
563 if ( $cookieValue ===
null ) {
567 $blockCookieId = $this->getIdFromCookieValue( $cookieValue );
568 if ( $blockCookieId !==
null ) {
569 $block = $this->blockStore->newFromID( $blockCookieId );
571 $block instanceof DatabaseBlock &&
572 $this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
588 private function shouldApplyCookieBlock( DatabaseBlock $block, $isAnon ) {
589 if ( !$block->isExpired() ) {
590 switch ( $block->getType() ) {
591 case DatabaseBlock::TYPE_IP:
592 case DatabaseBlock::TYPE_RANGE:
596 $this->options->get( MainConfigNames::CookieSetOnIpBlock );
597 case DatabaseBlock::TYPE_USER:
598 return $block->isAutoblocking() &&
599 $this->options->get( MainConfigNames::CookieSetOnAutoblock );
613 private function isLocallyBlockedProxy( $ip ) {
614 $proxyList = $this->options->get( MainConfigNames::ProxyList );
619 if ( !is_array( $proxyList ) ) {
621 $proxyList = array_map(
'trim', file( $proxyList ) );
624 $proxyListIPSet =
new IPSet( $proxyList );
625 return $proxyListIPSet->match( $ip );
636 if ( !$this->options->get( MainConfigNames::EnableDnsBlacklist ) ||
637 ( $checkAllowed && in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) )
642 return $this->inDnsBlacklist( $ip, $this->options->get( MainConfigNames::DnsBlacklistUrls ) );
652 private function inDnsBlacklist( $ip, array $bases ) {
655 if ( IPUtils::isIPv4( $ip ) ) {
657 $ipReversed = implode(
'.', array_reverse( explode(
'.', $ip ) ) );
659 foreach ( $bases as $base ) {
663 if ( is_array( $base ) ) {
664 if ( count( $base ) >= 2 ) {
666 $hostname =
"{$base[1]}.$ipReversed.{$base[0]}";
668 $hostname =
"$ipReversed.{$base[0]}";
670 $basename = $base[0];
672 $hostname =
"$ipReversed.$base";
676 $ipList = $this->checkHost( $hostname );
680 'Hostname {hostname} is {ipList}, it\'s a proxy says {basename}!',
682 'hostname' => $hostname,
683 'ipList' => $ipList[0],
684 'basename' => $basename,
691 $this->logger->debug(
"Requested $hostname, not found in $basename." );
705 return gethostbynamel( $hostname );
728 if ( !$this->options->get( MainConfigNames::CookieSetOnIpBlock ) &&
729 !$this->options->get( MainConfigNames::CookieSetOnAutoblock ) ) {
736 if ( $request->getCookie(
'BlockID' ) !==
null ) {
737 $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
738 if ( $cookieBlock && $this->shouldApplyCookieBlock( $cookieBlock, $user->
isAnon() ) ) {
742 $this->clearBlockCookie( $response );
750 throw new LogicException( __METHOD__ .
' requires a loaded User object' );
753 throw new LogicException( __METHOD__ .
' must be called pre-send' );
757 $isAnon = $user->
isAnon();
760 foreach ( $block->toArray() as $originalBlock ) {
763 && $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon )
765 $this->setBlockCookie( $originalBlock, $response );
786 if ( $expiryTime ===
'infinity' || $expiryTime > $maxExpiryTime ) {
787 $expiryTime = $maxExpiryTime;
791 $expiryValue = (int)
wfTimestamp( TS_UNIX, $expiryTime );
792 $cookieOptions = [
'httpOnly' => false ];
793 $cookieValue = $this->getCookieValue( $block );
794 $response->
setCookie(
'BlockID', $cookieValue, $expiryValue, $cookieOptions );
804 private function shouldTrackBlockWithCookie( DatabaseBlock $block, $isAnon ) {
805 switch ( $block->getType() ) {
806 case DatabaseBlock::TYPE_IP:
807 case DatabaseBlock::TYPE_RANGE:
808 return $isAnon && $this->options->get( MainConfigNames::CookieSetOnIpBlock );
809 case DatabaseBlock::TYPE_USER:
811 $this->options->get( MainConfigNames::CookieSetOnAutoblock ) &&
812 $block->isAutoblocking();
825 $response->
clearCookie(
'BlockID', [
'httpOnly' =>
false ] );
836 private function getIdFromCookieValue( $cookieValue ) {
838 if ( !is_numeric( substr( $cookieValue, 0, 1 ) ) ) {
843 $bangPos = strpos( $cookieValue,
'!' );
844 $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
845 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
849 $storedHmac = substr( $cookieValue, $bangPos + 1 );
850 $calculatedHmac = MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
851 if ( $calculatedHmac === $storedHmac ) {
867 private function getCookieValue( DatabaseBlock $block ) {
868 $id = (string)$block->getId();
869 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
873 $hmac =
MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
874 return $id .
'!' . $hmac;