33 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 ) {
199 return new CompositeBlock( [
201 'reason' =>
new Message(
'blockedtext-composite-reason' ),
202 'originalBlocks' => $blocks,
217 if ( !IPUtils::isValid( $ip ) ) {
221 $blocks = array_merge(
223 $this->getSystemIpBlocks( $ip,
true )
226 return $this->createGetBlockResult( $ip, $blocks );
236 private function getCookieBlock( UserIdentity $user,
WebRequest $request ): array {
237 $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
239 return $cookieBlock instanceof DatabaseBlock ? [ $cookieBlock ] : [];
249 private function getSystemIpBlocks(
string $ip,
bool $isAnon ): array {
253 if ( !in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) ) {
255 if ( $this->isLocallyBlockedProxy( $ip ) ) {
257 $blocks[] =
new SystemBlock( [
258 'reason' =>
new Message(
'proxyblockreason' ),
260 'systemBlock' =>
'proxy',
262 } elseif ( $isAnon && $this->isDnsBlacklisted( $ip ) ) {
264 $blocks[] =
new SystemBlock( [
265 'reason' =>
new Message(
'sorbsreason' ),
268 'systemBlock' =>
'dnsbl',
274 if ( $isAnon && IPUtils::isInRanges( $ip, $this->options->get( MainConfigNames::SoftBlockRanges ) ) ) {
276 $blocks[] =
new SystemBlock( [
278 'reason' =>
new Message(
'softblockrangesreason', [ $ip ] ),
280 'systemBlock' =>
'wgSoftBlockRanges',
298 private function getXffBlocks(
string $ip,
string $xff,
bool $isAnon,
bool $fromPrimary ): array {
300 if ( $this->options->get( MainConfigNames::ApplyIpBlocksToXff )
301 && !in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) )
303 $xff = array_map(
'trim', explode(
',', $xff ) );
304 $xff = array_diff( $xff, [ $ip ] );
306 return DatabaseBlock::getBlocksForIPList( $xff, $isAnon, $fromPrimary );
322 private function getUniqueBlocks( array $blocks ) {
324 $databaseBlocks = [];
326 foreach ( $blocks as $block ) {
327 if ( $block instanceof SystemBlock ) {
328 $systemBlocks[] = $block;
329 } elseif ( $block->getType() === DatabaseBlock::TYPE_AUTO ) {
331 '@phan-var DatabaseBlock $block';
332 if ( !isset( $databaseBlocks[$block->getParentBlockId()] ) ) {
333 $databaseBlocks[$block->getParentBlockId()] = $block;
337 $databaseBlocks[$block->getId()] = $block;
341 return array_values( array_merge( $systemBlocks, $databaseBlocks ) );
355 private function getBlockFromCookieValue(
359 $cookieValue = $request->
getCookie(
'BlockID' );
360 if ( $cookieValue ===
null ) {
364 $blockCookieId = $this->getIdFromCookieValue( $cookieValue );
365 if ( $blockCookieId !==
null ) {
367 $block = DatabaseBlock::newFromID( $blockCookieId );
369 $block instanceof DatabaseBlock &&
370 $this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
386 private function shouldApplyCookieBlock( DatabaseBlock $block, $isAnon ) {
387 if ( !$block->isExpired() ) {
388 switch ( $block->getType() ) {
389 case DatabaseBlock::TYPE_IP:
390 case DatabaseBlock::TYPE_RANGE:
394 $this->options->get( MainConfigNames::CookieSetOnIpBlock );
395 case DatabaseBlock::TYPE_USER:
396 return $block->isAutoblocking() &&
397 $this->options->get( MainConfigNames::CookieSetOnAutoblock );
411 private function isLocallyBlockedProxy( $ip ) {
412 $proxyList = $this->options->get( MainConfigNames::ProxyList );
417 if ( !is_array( $proxyList ) ) {
419 $proxyList = array_map(
'trim', file( $proxyList ) );
422 $proxyListIPSet =
new IPSet( $proxyList );
423 return $proxyListIPSet->match( $ip );
434 if ( !$this->options->get( MainConfigNames::EnableDnsBlacklist ) ||
435 ( $checkAllowed && in_array( $ip, $this->options->get( MainConfigNames::ProxyWhitelist ) ) )
440 return $this->inDnsBlacklist( $ip, $this->options->get( MainConfigNames::DnsBlacklistUrls ) );
450 private function inDnsBlacklist( $ip, array $bases ) {
453 if ( IPUtils::isIPv4( $ip ) ) {
455 $ipReversed = implode(
'.', array_reverse( explode(
'.', $ip ) ) );
457 foreach ( $bases as
$base ) {
461 if ( is_array(
$base ) ) {
462 if ( count(
$base ) >= 2 ) {
464 $hostname =
"{$base[1]}.$ipReversed.{$base[0]}";
466 $hostname =
"$ipReversed.{$base[0]}";
468 $basename =
$base[0];
470 $hostname =
"$ipReversed.$base";
474 $ipList = $this->checkHost( $hostname );
478 'Hostname {hostname} is {ipList}, it\'s a proxy says {basename}!',
480 'hostname' => $hostname,
481 'ipList' => $ipList[0],
482 'basename' => $basename,
489 $this->logger->debug(
"Requested $hostname, not found in $basename." );
503 return gethostbynamel( $hostname );
528 if ( $request->
getCookie(
'BlockID' ) !==
null ) {
529 $cookieBlock = $this->getBlockFromCookieValue( $user, $request );
530 if ( $cookieBlock && $this->shouldApplyCookieBlock( $cookieBlock, $user->
isAnon() ) ) {
534 $this->clearBlockCookie( $response );
542 throw new LogicException( __METHOD__ .
' requires a loaded User object' );
545 throw new LogicException( __METHOD__ .
' must be called pre-send' );
549 $isAnon = $user->
isAnon();
554 foreach ( $block->getOriginalBlocks() as $originalBlock ) {
555 if ( $this->shouldTrackBlockWithCookie( $originalBlock, $isAnon ) ) {
556 '@phan-var DatabaseBlock $originalBlock';
557 $this->setBlockCookie( $originalBlock, $response );
562 if ( $this->shouldTrackBlockWithCookie( $block, $isAnon ) ) {
563 '@phan-var DatabaseBlock $block';
564 $this->setBlockCookie( $block, $response );
586 if ( $expiryTime ===
'infinity' || $expiryTime > $maxExpiryTime ) {
587 $expiryTime = $maxExpiryTime;
591 $expiryValue = (int)
wfTimestamp( TS_UNIX, $expiryTime );
592 $cookieOptions = [
'httpOnly' => false ];
593 $cookieValue = $this->getCookieValue( $block );
594 $response->
setCookie(
'BlockID', $cookieValue, $expiryValue, $cookieOptions );
604 private function shouldTrackBlockWithCookie(
AbstractBlock $block, $isAnon ) {
607 case DatabaseBlock::TYPE_IP:
608 case DatabaseBlock::TYPE_RANGE:
609 return $isAnon && $this->options->get( MainConfigNames::CookieSetOnIpBlock );
610 case DatabaseBlock::TYPE_USER:
612 $this->options->get( MainConfigNames::CookieSetOnAutoblock ) &&
613 $block->isAutoblocking();
628 $response->
clearCookie(
'BlockID', [
'httpOnly' =>
false ] );
643 if ( !is_numeric( substr( $cookieValue, 0, 1 ) ) ) {
648 $bangPos = strpos( $cookieValue,
'!' );
649 $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
650 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
654 $storedHmac = substr( $cookieValue, $bangPos + 1 );
655 $calculatedHmac =
MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
656 if ( $calculatedHmac === $storedHmac ) {
675 $id = (string)$block->
getId();
676 if ( !$this->options->get( MainConfigNames::SecretKey ) ) {
680 $hmac =
MWCryptHash::hmac( $id, $this->options->get( MainConfigNames::SecretKey ),
false );
681 $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.
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.
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.