*
* @file
*/
+
+use MediaWiki\MediaWikiServices;
+
class Block {
/** @var string */
public $mReason;
/** @var bool */
protected $isAutoblocking;
+ /** @var string|null */
+ protected $systemBlockType;
+
# TYPE constants
const TYPE_USER = 1;
const TYPE_IP = 2;
* blockEmail bool Disallow sending emails
* allowUsertalk bool Allow the target to edit its own talk page
* byText string Username of the blocker (for foreign users)
+ * systemBlock string Indicate that this block is automatically
+ * created by MediaWiki rather than being stored
+ * in the database. Value is a string to return
+ * from self::getSystemBlockType().
*
* @since 1.26 accepts $options array instead of individual parameters; order
* of parameters above reflects the original order
'blockEmail' => false,
'allowUsertalk' => false,
'byText' => '',
+ 'systemBlock' => null,
];
if ( func_num_args() > 1 || !is_array( $options ) ) {
$this->mReason = $options['reason'];
$this->mTimestamp = wfTimestamp( TS_MW, $options['timestamp'] );
- $this->mExpiry = wfGetDB( DB_SLAVE )->decodeExpiry( $options['expiry'] );
+ $this->mExpiry = wfGetDB( DB_REPLICA )->decodeExpiry( $options['expiry'] );
# Boolean settings
$this->mAuto = (bool)$options['auto'];
$this->prevents( 'createaccount', (bool)$options['createAccount'] );
$this->mFromMaster = false;
+ $this->systemBlockType = $options['systemBlock'];
}
/**
* @return Block|null
*/
public static function newFromID( $id ) {
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
$res = $dbr->selectRow(
'ipblocks',
self::selectFields(),
* @return bool Whether a relevant block was found
*/
protected function newLoad( $vagueTarget = null ) {
- $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_SLAVE );
+ $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_REPLICA );
if ( $this->type !== null ) {
$conds = [
# range. We know that all blocks must be smaller than $wgBlockCIDRLimit,
# so we can improve performance by filtering on a LIKE clause
$chunk = self::getIpFragment( $start );
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
$like = $dbr->buildLike( $chunk, $dbr->anyString() );
# Fairly hard to make a malicious SQL statement out of hex characters,
$this->mParentBlockId = $row->ipb_parent_block_id;
// I wish I didn't have to do this
- $this->mExpiry = wfGetDB( DB_SLAVE )->decodeExpiry( $row->ipb_expiry );
+ $this->mExpiry = wfGetDB( DB_REPLICA )->decodeExpiry( $row->ipb_expiry );
$this->isHardblock( !$row->ipb_anon_only );
$this->isAutoblocking( $row->ipb_enable_autoblock );
*/
public function insert( $dbw = null ) {
global $wgBlockDisablesLogin;
+
+ if ( $this->getSystemBlockType() !== null ) {
+ throw new MWException( 'Cannot insert a system block into the database' );
+ }
+
wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
if ( $dbw === null ) {
*/
protected function getDatabaseArray( $db = null ) {
if ( !$db ) {
- $db = wfGetDB( DB_SLAVE );
+ $db = wfGetDB( DB_REPLICA );
}
$expiry = $db->encodeExpiry( $this->mExpiry );
return;
}
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_REPLICA );
$options = [ 'ORDER BY' => 'rc_timestamp DESC' ];
$conds = [ 'rc_user_text' => (string)$block->getTarget() ];
public static function isWhitelistedFromAutoblocks( $ip ) {
// Try to get the autoblock_whitelist from the cache, as it's faster
// than getting the msg raw and explode()'ing it.
- $cache = ObjectCache::getMainWANInstance();
+ $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
$lines = $cache->getWithSetCallback(
wfMemcKey( 'ipb', 'autoblock', 'whitelist' ),
$cache::TTL_DAY,
- function () {
+ function ( $curValue, &$ttl, array &$setOpts ) {
+ $setOpts += Database::getCacheSetOptions( wfGetDB( DB_REPLICA ) );
+
return explode( "\n",
wfMessage( 'autoblock_whitelist' )->inContentLanguage()->plain() );
}
return false;
}
+ # Don't autoblock for system blocks
+ if ( $this->getSystemBlockType() !== null ) {
+ throw new MWException( 'Cannot autoblock from a system block' );
+ }
+
# Check for presence on the autoblock whitelist.
if ( self::isWhitelistedFromAutoblocks( $autoblockIP ) ) {
return false;
}
+ // Avoid PHP 7.1 warning of passing $this by reference
+ $block = $this;
# Allow hooks to cancel the autoblock.
- if ( !Hooks::run( 'AbortAutoblock', [ $autoblockIP, &$this ] ) ) {
+ if ( !Hooks::run( 'AbortAutoblock', [ $autoblockIP, &$block ] ) ) {
wfDebug( "Autoblock aborted by hook.\n" );
return false;
}
return $this->mId;
}
+ /**
+ * Get the system block type, if any
+ * @return string|null
+ */
+ public function getSystemBlockType() {
+ return $this->systemBlockType;
+ }
+
/**
* Get/set a flag determining whether the master is used for reads
*
* @param array $ipChain List of IPs (strings), usually retrieved from the
* X-Forwarded-For header of the request
* @param bool $isAnon Exclude anonymous-only blocks if false
- * @param bool $fromMaster Whether to query the master or slave database
+ * @param bool $fromMaster Whether to query the master or replica DB
* @return array Array of Blocks
* @since 1.22
*/
}
$conds = [];
+ $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
foreach ( array_unique( $ipChain ) as $ipaddr ) {
# Discard invalid IP addresses. Since XFF can be spoofed and we do not
# necessarily trust the header given to us, make sure that we are only
continue;
}
# Don't check trusted IPs (includes local squids which will be in every request)
- if ( IP::isTrustedProxy( $ipaddr ) ) {
+ if ( $proxyLookup->isTrustedProxy( $ipaddr ) ) {
continue;
}
# Check both the original IP (to check against single blocks), as well as build
if ( $fromMaster ) {
$db = wfGetDB( DB_MASTER );
} else {
- $db = wfGetDB( DB_SLAVE );
+ $db = wfGetDB( DB_REPLICA );
}
$conds = $db->makeList( $conds, LIST_OR );
if ( !$isAnon ) {
$this->blocker = $user;
}
+ /**
+ * Set the 'BlockID' cookie to this block's ID and expiry time. The cookie's expiry will be
+ * the same as the block's, to a maximum of 24 hours.
+ *
+ * An empty value can also be set, in order to retain the cookie but remove the block ID
+ * (e.g. as used in User::getBlockedStatus).
+ *
+ * @param WebResponse $response The response on which to set the cookie.
+ * @param boolean $setEmpty Whether to set the cookie's value to the empty string.
+ */
+ public function setCookie( WebResponse $response, $setEmpty = false ) {
+ // Calculate the default expiry time.
+ $maxExpiryTime = wfTimestamp( TS_MW, wfTimestamp() + ( 24 * 60 * 60 ) );
+
+ // Use the Block's expiry time only if it's less than the default.
+ $expiryTime = $this->getExpiry();
+ if ( $expiryTime === 'infinity' || $expiryTime > $maxExpiryTime ) {
+ $expiryTime = $maxExpiryTime;
+ }
+
+ // Set the cookie. Reformat the MediaWiki datetime as a Unix timestamp for the cookie.
+ $cookieValue = $setEmpty ? '' : $this->getCookieValue();
+ $expiryValue = DateTime::createFromFormat( 'YmdHis', $expiryTime )->format( 'U' );
+ $cookieOptions = [ 'httpOnly' => false ];
+ $response->setCookie( 'BlockID', $cookieValue, $expiryValue, $cookieOptions );
+ }
+
+ /**
+ * Get the BlockID cookie's value for this block. This is usually the block ID concatenated
+ * with an HMAC in order to avoid spoofing (T152951), but if wgSecretKey is not set will just
+ * be the block ID.
+ * @return string The block ID, probably concatenated with "!" and the HMAC.
+ */
+ public function getCookieValue() {
+ $config = RequestContext::getMain()->getConfig();
+ $id = $this->getId();
+ $secretKey = $config->get( 'SecretKey' );
+ if ( !$secretKey ) {
+ // If there's no secret key, don't append a HMAC.
+ return $id;
+ }
+ $hmac = MWCryptHash::hmac( $id, $secretKey, false );
+ $cookieValue = $id . '!' . $hmac;
+ return $cookieValue;
+ }
+
+ /**
+ * Get the stored ID from the 'BlockID' cookie. The cookie's value is usually a combination of
+ * the ID and a HMAC (see Block::setCookie), but will sometimes only be the ID.
+ * @param string $cookieValue The string in which to find the ID.
+ * @return integer|null The block ID, or null if the HMAC is present and invalid.
+ */
+ public static function getIdFromCookieValue( $cookieValue ) {
+ // Extract the ID prefix from the cookie value (may be the whole value, if no bang found).
+ $bangPos = strpos( $cookieValue, '!' );
+ $id = ( $bangPos === false ) ? $cookieValue : substr( $cookieValue, 0, $bangPos );
+ // Get the site-wide secret key.
+ $config = RequestContext::getMain()->getConfig();
+ $secretKey = $config->get( 'SecretKey' );
+ if ( !$secretKey ) {
+ // If there's no secret key, just use the ID as given.
+ return $id;
+ }
+ $storedHmac = substr( $cookieValue, $bangPos + 1 );
+ $calculatedHmac = MWCryptHash::hmac( $id, $secretKey, false );
+ if ( $calculatedHmac === $storedHmac ) {
+ return $id;
+ } else {
+ return null;
+ }
+ }
+
/**
* Get the key and parameters for the corresponding error message.
*
* This could be a username, an IP range, or a single IP. */
$intended = $this->getTarget();
+ $systemBlockType = $this->getSystemBlockType();
+
$lang = $context->getLanguage();
return [
- $this->mAuto ? 'autoblockedtext' : 'blockedtext',
+ $systemBlockType !== null
+ ? 'systemblockedtext'
+ : ( $this->mAuto ? 'autoblockedtext' : 'blockedtext' ),
$link,
$reason,
$context->getRequest()->getIP(),
$this->getByName(),
- $this->getId(),
+ $systemBlockType !== null ? $systemBlockType : $this->getId(),
$lang->formatExpiry( $this->mExpiry ),
(string)$intended,
$lang->userTimeAndDate( $this->mTimestamp, $context->getUser() ),