34 use Psr\Log\LoggerInterface;
38 use Wikimedia\IPUtils;
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 );
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 return DatabaseBlock::getBlocksForIPList( $xff, $isAnon, $fromPrimary );
318 private function getUniqueBlocks( array $blocks ) {
320 $databaseBlocks = [];
322 foreach ( $blocks as $block ) {
323 if ( $block instanceof SystemBlock ) {
324 $systemBlocks[] = $block;
325 } elseif ( $block->getType() === DatabaseBlock::TYPE_AUTO ) {
327 '@phan-var DatabaseBlock $block';
328 if ( !isset( $databaseBlocks[$block->getParentBlockId()] ) ) {
329 $databaseBlocks[$block->getParentBlockId()] = $block;
333 $databaseBlocks[$block->getId()] = $block;
337 return array_values( array_merge( $systemBlocks, $databaseBlocks ) );
351 private function getBlockFromCookieValue(
355 $cookieValue = $request->
getCookie(
'BlockID' );
356 if ( $cookieValue ===
null ) {
360 $blockCookieId = $this->getIdFromCookieValue( $cookieValue );
361 if ( $blockCookieId !==
null ) {
363 $block = DatabaseBlock::newFromID( $blockCookieId );
365 $block instanceof DatabaseBlock &&
366 $this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
382 private function shouldApplyCookieBlock( DatabaseBlock $block, $isAnon ) {
383 if ( !$block->isExpired() ) {
384 switch ( $block->getType() ) {
385 case DatabaseBlock::TYPE_IP:
386 case DatabaseBlock::TYPE_RANGE:
390 $this->options->get( MainConfigNames::CookieSetOnIpBlock );
391 case DatabaseBlock::TYPE_USER:
392 return $block->isAutoblocking() &&
393 $this->options->get( MainConfigNames::CookieSetOnAutoblock );
407 private function isLocallyBlockedProxy( $ip ) {
408 $proxyList = $this->options->get( MainConfigNames::ProxyList );
413 if ( !is_array( $proxyList ) ) {
415 $proxyList = array_map(
'trim', file( $proxyList ) );
418 $proxyListIPSet =
new IPSet( $proxyList );
419 return $proxyListIPSet->match( $ip );
430 if ( !$this->options->get( MainConfigNames::EnableDnsBlacklist ) ||
431 ( $checkAllowed && in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) )
436 return $this->inDnsBlacklist( $ip, $this->options->get( MainConfigNames::DnsBlacklistUrls ) );
446 private function inDnsBlacklist( $ip, array $bases ) {
449 if ( IPUtils::isIPv4( $ip ) ) {
451 $ipReversed = implode(
'.', array_reverse( explode(
'.', $ip ) ) );
453 foreach ( $bases as
$base ) {
457 if ( is_array(
$base ) ) {
458 if ( count(
$base ) >= 2 ) {
460 $hostname =
"{$base[1]}.$ipReversed.{$base[0]}";
462 $hostname =
"$ipReversed.{$base[0]}";
464 $basename =
$base[0];
466 $hostname =
"$ipReversed.$base";
470 $ipList = $this->checkHost( $hostname );
474 'Hostname {hostname} is {ipList}, it\'s a proxy says {basename}!',
476 'hostname' => $hostname,
477 'ipList' => $ipList[0],
478 'basename' => $basename,
485 $this->logger->debug(
"Requested $hostname, not found in $basename." );
499 return gethostbynamel( $hostname );
524 if ( $request->
getCookie(
'BlockID' ) !==
null ) {
525 $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
526 if ( $cookieBlock && $this->shouldApplyCookieBlock( $cookieBlock, $user->
isAnon() ) ) {
530 $this->clearBlockCookie( $response );
538 throw new LogicException( __METHOD__ .
' requires a loaded User object' );
541 throw new LogicException( __METHOD__ .
' must be called pre-send' );
545 $isAnon = $user->
isAnon();
550 foreach ( $block->getOriginalBlocks() as $originalBlock ) {
551 if ( $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon ) ) {
552 '@phan-var DatabaseBlock $originalBlock';
553 $this->setBlockCookie( $originalBlock, $response );
558 if ( $this->shouldTrackBlockWithCookie( $block, $isAnon ) ) {
559 '@phan-var DatabaseBlock $block';
560 $this->setBlockCookie( $block, $response );
582 if ( $expiryTime ===
'infinity' || $expiryTime > $maxExpiryTime ) {
583 $expiryTime = $maxExpiryTime;
587 $expiryValue = (int)
wfTimestamp( TS_UNIX, $expiryTime );
588 $cookieOptions = [
'httpOnly' => false ];
589 $cookieValue = $this->getCookieValue( $block );
590 $response->
setCookie(
'BlockID', $cookieValue, $expiryValue, $cookieOptions );
600 private function shouldTrackBlockWithCookie(
AbstractBlock $block, $isAnon ) {
603 case DatabaseBlock::TYPE_IP:
604 case DatabaseBlock::TYPE_RANGE:
605 return $isAnon && $this->options->get( MainConfigNames::CookieSetOnIpBlock );
606 case DatabaseBlock::TYPE_USER:
608 $this->options->get( MainConfigNames::CookieSetOnAutoblock ) &&
609 $block->isAutoblocking();
624 $response->
clearCookie(
'BlockID', [
'httpOnly' =>
false ] );
639 if ( !is_numeric( substr( $cookieValue, 0, 1 ) ) ) {
644 $bangPos = strpos( $cookieValue,
'!' );
645 $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
646 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
650 $storedHmac = substr( $cookieValue, $bangPos + 1 );
651 $calculatedHmac =
MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
652 if ( $calculatedHmac === $storedHmac ) {
671 $id = (string)$block->
getId();
672 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
676 $hmac =
MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
677 $cookieValue = $id .
'!' . $hmac;
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
if(!defined('MW_SETUP_CALLBACK'))
static hmac( $data, $key, $raw=true)
Generate a keyed cryptographic hash value (HMAC) for a string, making use of the best hash algorithm ...
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.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
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.