33use Psr\Log\LoggerInterface;
48 private $permissionManager;
88 LoggerInterface $logger,
92 $this->options = $options;
93 $this->permissionManager = $permissionManager;
94 $this->userFactory = $userFactory;
95 $this->logger = $logger;
96 $this->hookRunner =
new HookRunner( $hookContainer );
135 $disableIpBlockExemptChecking =
false
137 $fromPrimary = !$fromReplica;
143 $checkIpBlocks = $request &&
148 !$disableIpBlockExemptChecking &&
149 !$this->permissionManager->userHasRight( $user,
'ipblock-exempt' );
151 if ( $request && $checkIpBlocks ) {
154 $ip = $request->
getIP();
157 $xff = $request->
getHeader(
'X-Forwarded-For' );
160 $blocks = array_merge(
162 $this->getSystemIpBlocks( $ip, $isAnon ),
163 $this->getXffBlocks( $ip, $xff, $isAnon, $fromPrimary ),
164 $this->getCookieBlock( $user, $request )
176 $block = $this->createGetBlockResult( $ip, $blocks );
178 $legacyUser = $this->userFactory->newFromUserIdentity( $user );
179 $this->hookRunner->onGetUserBlock( clone $legacyUser, $ip, $block );
189 private function createGetBlockResult( ?
string $ip, array $blocks ): ?
AbstractBlock {
191 $blocks = $this->getUniqueBlocks( $blocks );
193 if ( count( $blocks ) === 0 ) {
195 } elseif ( count( $blocks ) === 1 ) {
198 return new CompositeBlock( [
200 'reason' =>
new Message(
'blockedtext-composite-reason' ),
201 'originalBlocks' => $blocks,
216 if ( !IPUtils::isValid( $ip ) ) {
220 $blocks = array_merge(
222 $this->getSystemIpBlocks( $ip,
true )
225 return $this->createGetBlockResult( $ip, $blocks );
235 private function getCookieBlock( UserIdentity $user,
WebRequest $request ): array {
236 $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
238 return $cookieBlock instanceof DatabaseBlock ? [ $cookieBlock ] : [];
248 private function getSystemIpBlocks(
string $ip,
bool $isAnon ): array {
252 if ( !in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) ) {
254 if ( $this->isLocallyBlockedProxy( $ip ) ) {
255 $blocks[] =
new SystemBlock( [
256 'reason' =>
new Message(
'proxyblockreason' ),
258 'systemBlock' =>
'proxy',
260 } elseif ( $isAnon && $this->isDnsBlacklisted( $ip ) ) {
261 $blocks[] =
new SystemBlock( [
262 'reason' =>
new Message(
'sorbsreason' ),
265 'systemBlock' =>
'dnsbl',
271 if ( $isAnon && IPUtils::isInRanges( $ip, $this->options->get( MainConfigNames::SoftBlockRanges ) ) ) {
272 $blocks[] =
new SystemBlock( [
274 'reason' =>
new Message(
'softblockrangesreason', [ $ip ] ),
276 'systemBlock' =>
'wgSoftBlockRanges',
294 private function getXffBlocks(
string $ip,
string $xff,
bool $isAnon,
bool $fromPrimary ): array {
296 if ( $this->options->get( MainConfigNames::ApplyIpBlocksToXff )
297 && !in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) )
299 $xff = array_map(
'trim', explode(
',', $xff ) );
300 $xff = array_diff( $xff, [ $ip ] );
302 $xffblocks = DatabaseBlock::getBlocksForIPList( $xff, $isAnon, $fromPrimary );
306 $xffblocks = array_filter( $xffblocks,
static function ( $block ) {
307 return $block->
getType() !== Block::TYPE_AUTO;
326 private function getUniqueBlocks( array $blocks ) {
328 $databaseBlocks = [];
330 foreach ( $blocks as $block ) {
331 if ( $block instanceof SystemBlock ) {
332 $systemBlocks[] = $block;
333 } elseif ( $block->getType() === DatabaseBlock::TYPE_AUTO ) {
335 '@phan-var DatabaseBlock $block';
336 if ( !isset( $databaseBlocks[$block->getParentBlockId()] ) ) {
337 $databaseBlocks[$block->getParentBlockId()] = $block;
341 $databaseBlocks[$block->getId()] = $block;
345 return array_values( array_merge( $systemBlocks, $databaseBlocks ) );
359 private function getBlockFromCookieValue(
363 $cookieValue = $request->
getCookie(
'BlockID' );
364 if ( $cookieValue ===
null ) {
368 $blockCookieId = $this->getIdFromCookieValue( $cookieValue );
369 if ( $blockCookieId !==
null ) {
371 $block = DatabaseBlock::newFromID( $blockCookieId );
373 $block instanceof DatabaseBlock &&
374 $this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
390 private function shouldApplyCookieBlock( DatabaseBlock $block, $isAnon ) {
391 if ( !$block->isExpired() ) {
392 switch ( $block->getType() ) {
393 case DatabaseBlock::TYPE_IP:
394 case DatabaseBlock::TYPE_RANGE:
398 $this->options->get( MainConfigNames::CookieSetOnIpBlock );
399 case DatabaseBlock::TYPE_USER:
400 return $block->isAutoblocking() &&
401 $this->options->get( MainConfigNames::CookieSetOnAutoblock );
415 private function isLocallyBlockedProxy( $ip ) {
416 $proxyList = $this->options->get( MainConfigNames::ProxyList );
421 if ( !is_array( $proxyList ) ) {
423 $proxyList = array_map(
'trim', file( $proxyList ) );
426 $proxyListIPSet =
new IPSet( $proxyList );
427 return $proxyListIPSet->match( $ip );
438 if ( !$this->options->get( MainConfigNames::EnableDnsBlacklist ) ||
439 ( $checkAllowed && in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) )
444 return $this->inDnsBlacklist( $ip, $this->options->get( MainConfigNames::DnsBlacklistUrls ) );
454 private function inDnsBlacklist( $ip, array $bases ) {
457 if ( IPUtils::isIPv4( $ip ) ) {
459 $ipReversed = implode(
'.', array_reverse( explode(
'.', $ip ) ) );
461 foreach ( $bases as
$base ) {
465 if ( is_array(
$base ) ) {
466 if ( count(
$base ) >= 2 ) {
468 $hostname =
"{$base[1]}.$ipReversed.{$base[0]}";
470 $hostname =
"$ipReversed.{$base[0]}";
472 $basename =
$base[0];
474 $hostname =
"$ipReversed.$base";
478 $ipList = $this->checkHost( $hostname );
482 'Hostname {hostname} is {ipList}, it\'s a proxy says {basename}!',
484 'hostname' => $hostname,
485 'ipList' => $ipList[0],
486 'basename' => $basename,
493 $this->logger->debug(
"Requested $hostname, not found in $basename." );
507 return gethostbynamel( $hostname );
532 if ( $request->
getCookie(
'BlockID' ) !==
null ) {
533 $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
534 if ( $cookieBlock && $this->shouldApplyCookieBlock( $cookieBlock, $user->
isAnon() ) ) {
538 $this->clearBlockCookie( $response );
546 throw new LogicException( __METHOD__ .
' requires a loaded User object' );
549 throw new LogicException( __METHOD__ .
' must be called pre-send' );
553 $isAnon = $user->
isAnon();
558 foreach ( $block->getOriginalBlocks() as $originalBlock ) {
559 if ( $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon ) ) {
560 '@phan-var DatabaseBlock $originalBlock';
561 $this->setBlockCookie( $originalBlock, $response );
566 if ( $this->shouldTrackBlockWithCookie( $block, $isAnon ) ) {
567 '@phan-var DatabaseBlock $block';
568 $this->setBlockCookie( $block, $response );
590 if ( $expiryTime ===
'infinity' || $expiryTime > $maxExpiryTime ) {
591 $expiryTime = $maxExpiryTime;
595 $expiryValue = (int)
wfTimestamp( TS_UNIX, $expiryTime );
596 $cookieOptions = [
'httpOnly' => false ];
597 $cookieValue = $this->getCookieValue( $block );
598 $response->
setCookie(
'BlockID', $cookieValue, $expiryValue, $cookieOptions );
608 private function shouldTrackBlockWithCookie(
AbstractBlock $block, $isAnon ) {
611 case DatabaseBlock::TYPE_IP:
612 case DatabaseBlock::TYPE_RANGE:
613 return $isAnon && $this->options->get( MainConfigNames::CookieSetOnIpBlock );
614 case DatabaseBlock::TYPE_USER:
616 $this->options->get( MainConfigNames::CookieSetOnAutoblock ) &&
617 $block->isAutoblocking();
632 $response->
clearCookie(
'BlockID', [
'httpOnly' =>
false ] );
647 if ( !is_numeric( substr( $cookieValue, 0, 1 ) ) ) {
652 $bangPos = strpos( $cookieValue,
'!' );
653 $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
654 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
658 $storedHmac = substr( $cookieValue, $bangPos + 1 );
659 $calculatedHmac = MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
660 if ( $calculatedHmac === $storedHmac ) {
679 $id = (string)$block->
getId();
680 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
684 $hmac = MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
685 $cookieValue = $id .
'!' . $hmac;
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
A class containing constants representing the names of configuration variables.
const DnsBlacklistUrls
Name constant for the DnsBlacklistUrls setting, for use with Config::get()
const SoftBlockRanges
Name constant for the SoftBlockRanges setting, for use with Config::get()
const CookieSetOnAutoblock
Name constant for the CookieSetOnAutoblock setting, for use with Config::get()
const EnableDnsBlacklist
Name constant for the EnableDnsBlacklist setting, for use with Config::get()
const ApplyIpBlocksToXff
Name constant for the ApplyIpBlocksToXff setting, for use with Config::get()
const ProxyList
Name constant for the ProxyList setting, for use with Config::get()
const ProxyWhitelist
Name constant for the ProxyWhitelist setting, for use with Config::get()
const CookieSetOnIpBlock
Name constant for the CookieSetOnIpBlock setting, for use with Config::get()
const SecretKey
Name constant for the SecretKey setting, for use with Config::get()
The Message class deals with fetching and processing of interface message into a variety of formats.
getBlock( $freshness=self::READ_NORMAL, $disableIpBlockExemptChecking=false)
Get the block affecting the user, or null if the user is not blocked.
getRequest()
Get the WebRequest object to use with this object.
isSafeToLoad()
Test if it's safe to load this User object.
isAnon()
Get whether the user is anonymous.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
getIP()
Work out the IP address based on various globals For trusted proxies, use the XFF client IP (first of...
getCookie( $key, $prefix=null, $default=null)
Get a cookie from the $_COOKIE jar.
getHeader( $name, $flags=0)
Get a request header, or false if it isn't set.
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
setCookie( $name, $value, $expire=0, $options=[])
Set the browser cookie.
clearCookie( $name, $options=[])
Unset a browser cookie.
headersSent()
Test if headers have been sent.