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 ) {
536 '@phan-var DatabaseBlock $block';
537 if ( !isset( $databaseBlocks[$block->getParentBlockId()] ) ) {
538 $databaseBlocks[$block->getParentBlockId()] = $block;
542 $databaseBlocks[$block->getId()] = $block;
546 return array_values( array_merge( $systemBlocks, $databaseBlocks ) );
560 private function getBlockFromCookieValue(
564 $cookieValue = $request->getCookie(
'BlockID' );
565 if ( $cookieValue ===
null ) {
569 $blockCookieId = $this->getIdFromCookieValue( $cookieValue );
570 if ( $blockCookieId !==
null ) {
571 $block = $this->blockStore->newFromID( $blockCookieId );
573 $block instanceof DatabaseBlock &&
574 $this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
590 private function shouldApplyCookieBlock( DatabaseBlock $block, $isAnon ) {
591 if ( !$block->isExpired() ) {
592 switch ( $block->getType() ) {
593 case DatabaseBlock::TYPE_IP:
594 case DatabaseBlock::TYPE_RANGE:
598 $this->options->get( MainConfigNames::CookieSetOnIpBlock );
599 case DatabaseBlock::TYPE_USER:
600 return $block->isAutoblocking() &&
601 $this->options->get( MainConfigNames::CookieSetOnAutoblock );
615 private function isLocallyBlockedProxy( $ip ) {
616 $proxyList = $this->options->get( MainConfigNames::ProxyList );
621 if ( !is_array( $proxyList ) ) {
623 $proxyList = array_map(
'trim', file( $proxyList ) );
626 $proxyListIPSet =
new IPSet( $proxyList );
627 return $proxyListIPSet->match( $ip );
638 if ( !$this->options->get( MainConfigNames::EnableDnsBlacklist ) ||
639 ( $checkAllowed && in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) )
644 return $this->inDnsBlacklist( $ip, $this->options->get( MainConfigNames::DnsBlacklistUrls ) );
654 private function inDnsBlacklist( $ip, array $bases ) {
657 if ( IPUtils::isIPv4( $ip ) ) {
659 $ipReversed = implode(
'.', array_reverse( explode(
'.', $ip ) ) );
661 foreach ( $bases as $base ) {
665 if ( is_array( $base ) ) {
666 if ( count( $base ) >= 2 ) {
668 $hostname =
"{$base[1]}.$ipReversed.{$base[0]}";
670 $hostname =
"$ipReversed.{$base[0]}";
672 $basename = $base[0];
674 $hostname =
"$ipReversed.$base";
678 $ipList = $this->checkHost( $hostname );
682 'Hostname {hostname} is {ipList}, it\'s a proxy says {basename}!',
684 'hostname' => $hostname,
685 'ipList' => $ipList[0],
686 'basename' => $basename,
693 $this->logger->debug(
"Requested $hostname, not found in $basename." );
707 return gethostbynamel( $hostname );
730 if ( !$this->options->get( MainConfigNames::CookieSetOnIpBlock ) &&
731 !$this->options->get( MainConfigNames::CookieSetOnAutoblock ) ) {
738 if ( $request->getCookie(
'BlockID' ) !==
null ) {
739 $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
740 if ( $cookieBlock && $this->shouldApplyCookieBlock( $cookieBlock, $user->
isAnon() ) ) {
744 $this->clearBlockCookie( $response );
752 throw new LogicException( __METHOD__ .
' requires a loaded User object' );
755 throw new LogicException( __METHOD__ .
' must be called pre-send' );
759 $isAnon = $user->
isAnon();
762 foreach ( $block->toArray() as $originalBlock ) {
765 && $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon )
767 $this->setBlockCookie( $originalBlock, $response );
788 if ( $expiryTime ===
'infinity' || $expiryTime > $maxExpiryTime ) {
789 $expiryTime = $maxExpiryTime;
793 $expiryValue = (int)
wfTimestamp( TS_UNIX, $expiryTime );
794 $cookieOptions = [
'httpOnly' => false ];
795 $cookieValue = $this->getCookieValue( $block );
796 $response->
setCookie(
'BlockID', $cookieValue, $expiryValue, $cookieOptions );
806 private function shouldTrackBlockWithCookie( DatabaseBlock $block, $isAnon ) {
807 switch ( $block->getType() ) {
808 case DatabaseBlock::TYPE_IP:
809 case DatabaseBlock::TYPE_RANGE:
810 return $isAnon && $this->options->get( MainConfigNames::CookieSetOnIpBlock );
811 case DatabaseBlock::TYPE_USER:
813 $this->options->get( MainConfigNames::CookieSetOnAutoblock ) &&
814 $block->isAutoblocking();
827 $response->
clearCookie(
'BlockID', [
'httpOnly' =>
false ] );
838 private function getIdFromCookieValue( $cookieValue ) {
840 if ( !is_numeric( substr( $cookieValue, 0, 1 ) ) ) {
845 $bangPos = strpos( $cookieValue,
'!' );
846 $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
847 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
851 $storedHmac = substr( $cookieValue, $bangPos + 1 );
852 $calculatedHmac = MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
853 if ( $calculatedHmac === $storedHmac ) {
869 private function getCookieValue( DatabaseBlock $block ) {
870 $id = (string)$block->getId();
871 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
875 $hmac =
MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
876 return $id .
'!' . $hmac;