/** * CheckUser since being changed to use Block::newFromTarget started failing * because the new function didn't accept empty strings like Block::load() * had. Regression bug 29116. * * @dataProvider provideBug29116Data * @covers Block::newFromTarget */ public function testBug29116NewFromTargetWithEmptyIp($vagueTarget) { $block = Block::newFromTarget('UTBlockee', $vagueTarget); $this->assertTrue($this->block->equals($block), "newFromTarget() returns the same block as the one that was made when " . "given empty vagueTarget param " . var_export($vagueTarget, true)); }
/** * Given the form data, actually implement a block * @param $data Array * @param $context IContextSource * @return Bool|String */ public static function processForm(array $data, IContextSource $context) { global $wgBlockAllowsUTEdit; $performer = $context->getUser(); // Handled by field validator callback // self::validateTargetField( $data['Target'] ); # This might have been a hidden field or a checkbox, so interesting data # can come from it $data['Confirm'] = !in_array($data['Confirm'], array('', '0', null, false), true); list($target, $type) = self::getTargetAndType($data['Target']); if ($type == Block::TYPE_USER) { $user = $target; $target = $user->getName(); $userId = $user->getId(); # Give admins a heads-up before they go and block themselves. Much messier # to do this for IPs, but it's pretty unlikely they'd ever get the 'block' # permission anyway, although the code does allow for it. # Note: Important to use $target instead of $data['Target'] # since both $data['PreviousTarget'] and $target are normalized # but $data['target'] gets overriden by (non-normalized) request variable # from previous request. if ($target === $performer->getName() && ($data['PreviousTarget'] !== $target || !$data['Confirm'])) { return array('ipb-blockingself'); } } elseif ($type == Block::TYPE_RANGE) { $userId = 0; } elseif ($type == Block::TYPE_IP) { $target = $target->getName(); $userId = 0; } else { # This should have been caught in the form field validation return array('badipaddress'); } if (strlen($data['Expiry']) == 0 || strlen($data['Expiry']) > 50 || !self::parseExpiryInput($data['Expiry'])) { return array('ipb_expiry_invalid'); } if (!isset($data['DisableEmail'])) { $data['DisableEmail'] = false; } # If the user has done the form 'properly', they won't even have been given the # option to suppress-block unless they have the 'hideuser' permission if (!isset($data['HideUser'])) { $data['HideUser'] = false; } if ($data['HideUser']) { if (!$performer->isAllowed('hideuser')) { # this codepath is unreachable except by a malicious user spoofing forms, # or by race conditions (user has oversight and sysop, loads block form, # and is de-oversighted before submission); so need to fail completely # rather than just silently disable hiding return array('badaccess-group0'); } # Recheck params here... if ($type != Block::TYPE_USER) { $data['HideUser'] = false; # IP users should not be hidden } elseif (!in_array($data['Expiry'], array('infinite', 'infinity', 'indefinite'))) { # Bad expiry. return array('ipb_expiry_temp'); } elseif ($user->getEditCount() > self::HIDEUSER_CONTRIBLIMIT) { # Typically, the user should have a handful of edits. # Disallow hiding users with many edits for performance. return array('ipb_hide_invalid'); } elseif (!$data['Confirm']) { return array('ipb-confirmhideuser'); } } # Create block object. $block = new Block(); $block->setTarget($target); $block->setBlocker($performer); $block->mReason = $data['Reason'][0]; $block->mExpiry = self::parseExpiryInput($data['Expiry']); $block->prevents('createaccount', $data['CreateAccount']); $block->prevents('editownusertalk', !$wgBlockAllowsUTEdit || $data['DisableUTEdit']); $block->prevents('sendemail', $data['DisableEmail']); $block->isHardblock($data['HardBlock']); $block->isAutoblocking($data['AutoBlock']); $block->mHideName = $data['HideUser']; if (!wfRunHooks('BlockIp', array(&$block, &$performer))) { return array('hookaborted'); } # Try to insert block. Is there a conflicting block? $status = $block->insert(); if (!$status) { # Show form unless the user is already aware of this... if (!$data['Confirm'] || array_key_exists('PreviousTarget', $data) && $data['PreviousTarget'] !== $target) { return array(array('ipb_already_blocked', $block->getTarget())); # Otherwise, try to update the block... } else { # This returns direct blocks before autoblocks/rangeblocks, since we should # be sure the user is blocked by now it should work for our purposes $currentBlock = Block::newFromTarget($target); if ($block->equals($currentBlock)) { return array(array('ipb_already_blocked', $block->getTarget())); } # If the name was hidden and the blocking user cannot hide # names, then don't allow any block changes... if ($currentBlock->mHideName && !$performer->isAllowed('hideuser')) { return array('cant-see-hidden-user'); } $currentBlock->delete(); $status = $block->insert(); $logaction = 'reblock'; # Unset _deleted fields if requested if ($currentBlock->mHideName && !$data['HideUser']) { RevisionDeleteUser::unsuppressUserName($target, $userId); } # If hiding/unhiding a name, this should go in the private logs if ((bool) $currentBlock->mHideName) { $data['HideUser'] = true; } } } else { $logaction = 'block'; } wfRunHooks('BlockIpComplete', array($block, $performer)); # Set *_deleted fields if requested if ($data['HideUser']) { RevisionDeleteUser::suppressUserName($target, $userId); } # Can't watch a rangeblock if ($type != Block::TYPE_RANGE && $data['Watch']) { $performer->addWatch(Title::makeTitle(NS_USER, $target)); } # Block constructor sanitizes certain block options on insert $data['BlockEmail'] = $block->prevents('sendemail'); $data['AutoBlock'] = $block->isAutoblocking(); # Prepare log parameters $logParams = array(); $logParams[] = $data['Expiry']; $logParams[] = self::blockLogFlags($data, $type); # Make log entry, if the name is hidden, put it in the oversight log $log_type = $data['HideUser'] ? 'suppress' : 'block'; $log = new LogPage($log_type); $log_id = $log->addEntry($logaction, Title::makeTitle(NS_USER, $target), $data['Reason'][0], $logParams); # Relate log ID to block IDs (bug 25763) $blockIds = array_merge(array($status['id']), $status['autoIds']); $log->addRelations('ipb_id', $blockIds, $log_id); # Report to the user return true; }
/** * Given the form data, actually implement a block. This is also called from ApiBlock. * * @param array $data * @param IContextSource $context * @return bool|string */ public static function processForm(array $data, IContextSource $context) { global $wgBlockAllowsUTEdit, $wgHideUserContribLimit, $wgContLang; $performer = $context->getUser(); // Handled by field validator callback // self::validateTargetField( $data['Target'] ); # This might have been a hidden field or a checkbox, so interesting data # can come from it $data['Confirm'] = !in_array($data['Confirm'], ['', '0', null, false], true); /** @var User $target */ list($target, $type) = self::getTargetAndType($data['Target']); if ($type == Block::TYPE_USER) { $user = $target; $target = $user->getName(); $userId = $user->getId(); # Give admins a heads-up before they go and block themselves. Much messier # to do this for IPs, but it's pretty unlikely they'd ever get the 'block' # permission anyway, although the code does allow for it. # Note: Important to use $target instead of $data['Target'] # since both $data['PreviousTarget'] and $target are normalized # but $data['target'] gets overridden by (non-normalized) request variable # from previous request. if ($target === $performer->getName() && ($data['PreviousTarget'] !== $target || !$data['Confirm'])) { return ['ipb-blockingself', 'ipb-confirmaction']; } } elseif ($type == Block::TYPE_RANGE) { $userId = 0; } elseif ($type == Block::TYPE_IP) { $target = $target->getName(); $userId = 0; } else { # This should have been caught in the form field validation return ['badipaddress']; } $expiryTime = self::parseExpiryInput($data['Expiry']); if (strlen($data['Expiry']) == 0 || strlen($data['Expiry']) > 50 || !$expiryTime) { return ['ipb_expiry_invalid']; } // an expiry time should be in the future, not in the // past (wouldn't make any sense) - bug T123069 if ($expiryTime < wfTimestampNow()) { return ['ipb_expiry_old']; } if (!isset($data['DisableEmail'])) { $data['DisableEmail'] = false; } # If the user has done the form 'properly', they won't even have been given the # option to suppress-block unless they have the 'hideuser' permission if (!isset($data['HideUser'])) { $data['HideUser'] = false; } if ($data['HideUser']) { if (!$performer->isAllowed('hideuser')) { # this codepath is unreachable except by a malicious user spoofing forms, # or by race conditions (user has hideuser and block rights, loads block form, # and loses hideuser rights before submission); so need to fail completely # rather than just silently disable hiding return ['badaccess-group0']; } # Recheck params here... if ($type != Block::TYPE_USER) { $data['HideUser'] = false; # IP users should not be hidden } elseif (!wfIsInfinity($data['Expiry'])) { # Bad expiry. return ['ipb_expiry_temp']; } elseif ($wgHideUserContribLimit !== false && $user->getEditCount() > $wgHideUserContribLimit) { # Typically, the user should have a handful of edits. # Disallow hiding users with many edits for performance. return [['ipb_hide_invalid', Message::numParam($wgHideUserContribLimit)]]; } elseif (!$data['Confirm']) { return ['ipb-confirmhideuser', 'ipb-confirmaction']; } } # Create block object. $block = new Block(); $block->setTarget($target); $block->setBlocker($performer); # Truncate reason for whole multibyte characters $block->mReason = $wgContLang->truncate($data['Reason'][0], 255); $block->mExpiry = $expiryTime; $block->prevents('createaccount', $data['CreateAccount']); $block->prevents('editownusertalk', !$wgBlockAllowsUTEdit || $data['DisableUTEdit']); $block->prevents('sendemail', $data['DisableEmail']); $block->isHardblock($data['HardBlock']); $block->isAutoblocking($data['AutoBlock']); $block->mHideName = $data['HideUser']; $reason = ['hookaborted']; if (!Hooks::run('BlockIp', [&$block, &$performer, &$reason])) { return $reason; } # Try to insert block. Is there a conflicting block? $status = $block->insert(); if (!$status) { # Indicates whether the user is confirming the block and is aware of # the conflict (did not change the block target in the meantime) $blockNotConfirmed = !$data['Confirm'] || array_key_exists('PreviousTarget', $data) && $data['PreviousTarget'] !== $target; # Special case for API - bug 32434 $reblockNotAllowed = array_key_exists('Reblock', $data) && !$data['Reblock']; # Show form unless the user is already aware of this... if ($blockNotConfirmed || $reblockNotAllowed) { return [['ipb_already_blocked', $block->getTarget()]]; # Otherwise, try to update the block... } else { # This returns direct blocks before autoblocks/rangeblocks, since we should # be sure the user is blocked by now it should work for our purposes $currentBlock = Block::newFromTarget($target); if ($block->equals($currentBlock)) { return [['ipb_already_blocked', $block->getTarget()]]; } # If the name was hidden and the blocking user cannot hide # names, then don't allow any block changes... if ($currentBlock->mHideName && !$performer->isAllowed('hideuser')) { return ['cant-see-hidden-user']; } $currentBlock->isHardblock($block->isHardblock()); $currentBlock->prevents('createaccount', $block->prevents('createaccount')); $currentBlock->mExpiry = $block->mExpiry; $currentBlock->isAutoblocking($block->isAutoblocking()); $currentBlock->mHideName = $block->mHideName; $currentBlock->prevents('sendemail', $block->prevents('sendemail')); $currentBlock->prevents('editownusertalk', $block->prevents('editownusertalk')); $currentBlock->mReason = $block->mReason; $status = $currentBlock->update(); $logaction = 'reblock'; # Unset _deleted fields if requested if ($currentBlock->mHideName && !$data['HideUser']) { RevisionDeleteUser::unsuppressUserName($target, $userId); } # If hiding/unhiding a name, this should go in the private logs if ((bool) $currentBlock->mHideName) { $data['HideUser'] = true; } } } else { $logaction = 'block'; } Hooks::run('BlockIpComplete', [$block, $performer]); # Set *_deleted fields if requested if ($data['HideUser']) { RevisionDeleteUser::suppressUserName($target, $userId); } # Can't watch a rangeblock if ($type != Block::TYPE_RANGE && $data['Watch']) { WatchAction::doWatch(Title::makeTitle(NS_USER, $target), $performer, User::IGNORE_USER_RIGHTS); } # Block constructor sanitizes certain block options on insert $data['BlockEmail'] = $block->prevents('sendemail'); $data['AutoBlock'] = $block->isAutoblocking(); # Prepare log parameters $logParams = []; $logParams['5::duration'] = $data['Expiry']; $logParams['6::flags'] = self::blockLogFlags($data, $type); # Make log entry, if the name is hidden, put it in the suppression log $log_type = $data['HideUser'] ? 'suppress' : 'block'; $logEntry = new ManualLogEntry($log_type, $logaction); $logEntry->setTarget(Title::makeTitle(NS_USER, $target)); $logEntry->setComment($data['Reason'][0]); $logEntry->setPerformer($performer); $logEntry->setParameters($logParams); # Relate log ID to block IDs (bug 25763) $blockIds = array_merge([$status['id']], $status['autoIds']); $logEntry->setRelations(['ipb_id' => $blockIds]); $logId = $logEntry->insert(); $logEntry->publish($logId); # Report to the user return true; }
/** * Backend block code. * $userID and $expiry will be filled accordingly * @return array(message key, arguments) on failure, empty array on success */ function doBlock(&$userId = null, &$expiry = null) { global $wgUser, $wgSysopUserBans, $wgSysopRangeBans, $wgBlockAllowsUTEdit, $wgBlockCIDRLimit; $userId = 0; # Expand valid IPv6 addresses, usernames are left as is $this->BlockAddress = IP::sanitizeIP($this->BlockAddress); # isIPv4() and IPv6() are used for final validation $rxIP4 = '\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}'; $rxIP6 = '\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}'; $rxIP = "({$rxIP4}|{$rxIP6})"; # Check for invalid specifications if (!preg_match("/^{$rxIP}\$/", $this->BlockAddress)) { $matches = array(); if (preg_match("/^({$rxIP4})\\/(\\d{1,2})\$/", $this->BlockAddress, $matches)) { # IPv4 if ($wgSysopRangeBans) { if (!IP::isIPv4($this->BlockAddress) || $matches[2] > 32) { return array('ip_range_invalid'); } elseif ($matches[2] < $wgBlockCIDRLimit['IPv4']) { return array('ip_range_toolarge', $wgBlockCIDRLimit['IPv4']); } $this->BlockAddress = Block::normaliseRange($this->BlockAddress); } else { # Range block illegal return array('range_block_disabled'); } } elseif (preg_match("/^({$rxIP6})\\/(\\d{1,3})\$/", $this->BlockAddress, $matches)) { # IPv6 if ($wgSysopRangeBans) { if (!IP::isIPv6($this->BlockAddress) || $matches[2] > 128) { return array('ip_range_invalid'); } elseif ($matches[2] < $wgBlockCIDRLimit['IPv6']) { return array('ip_range_toolarge', $wgBlockCIDRLimit['IPv6']); } $this->BlockAddress = Block::normaliseRange($this->BlockAddress); } else { # Range block illegal return array('range_block_disabled'); } } else { # Username block if ($wgSysopUserBans) { $user = User::newFromName($this->BlockAddress); if (!is_null($user) && $user->getId()) { # Use canonical name $userId = $user->getId(); $this->BlockAddress = $user->getName(); } else { return array('nosuchusershort', htmlspecialchars($user ? $user->getName() : $this->BlockAddress)); } } else { return array('badipaddress'); } } } if ($wgUser->isBlocked() && $wgUser->getId() !== $userId) { return array('cant-block-while-blocked'); } $reasonstr = $this->BlockReasonList; if ($reasonstr != 'other' && $this->BlockReason != '') { // Entry from drop down menu + additional comment $reasonstr .= wfMsgForContent('colon-separator') . $this->BlockReason; } elseif ($reasonstr == 'other') { $reasonstr = $this->BlockReason; } $expirestr = $this->BlockExpiry; if ($expirestr == 'other') { $expirestr = $this->BlockOther; } if (strlen($expirestr) == 0 || strlen($expirestr) > 50) { return array('ipb_expiry_invalid'); } if (false === ($expiry = Block::parseExpiryInput($expirestr))) { // Bad expiry. return array('ipb_expiry_invalid'); } if ($this->BlockHideName) { // Recheck params here... if (!$userId || !$wgUser->isAllowed('hideuser')) { $this->BlockHideName = false; // IP users should not be hidden } elseif ($expiry !== 'infinity') { // Bad expiry. return array('ipb_expiry_temp'); } elseif (User::edits($userId) > self::HIDEUSER_CONTRIBLIMIT) { // Typically, the user should have a handful of edits. // Disallow hiding users with many edits for performance. return array('ipb_hide_invalid'); } } # Create block object # Note: for a user block, ipb_address is only for display purposes $block = new Block($this->BlockAddress, $userId, $wgUser->getId(), $reasonstr, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, $this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName, $this->BlockEmail, isset($this->BlockAllowUsertalk) ? $this->BlockAllowUsertalk : $wgBlockAllowsUTEdit); # Should this be privately logged? $suppressLog = (bool) $this->BlockHideName; if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) { # Try to insert block. Is there a conflicting block? if (!$block->insert()) { # Show form unless the user is already aware of this... if (!$this->BlockReblock) { return array('ipb_already_blocked'); # Otherwise, try to update the block... } else { # This returns direct blocks before autoblocks/rangeblocks, since we should # be sure the user is blocked by now it should work for our purposes $currentBlock = Block::newFromDB($this->BlockAddress, $userId); if ($block->equals($currentBlock)) { return array('ipb_already_blocked'); } # If the name was hidden and the blocking user cannot hide # names, then don't allow any block changes... if ($currentBlock->mHideName && !$wgUser->isAllowed('hideuser')) { return array('cant-see-hidden-user'); } $currentBlock->delete(); $block->insert(); # If hiding/unhiding a name, this should go in the private logs $suppressLog = $suppressLog || (bool) $currentBlock->mHideName; $log_action = 'reblock'; # Unset _deleted fields if requested if ($currentBlock->mHideName && !$this->BlockHideName) { self::unsuppressUserName($this->BlockAddress, $userId); } } } else { $log_action = 'block'; } wfRunHooks('BlockIpComplete', array($block, $wgUser)); # Set *_deleted fields if requested if ($this->BlockHideName) { self::suppressUserName($this->BlockAddress, $userId); } # Only show watch link when this is no range block if ($this->BlockWatchUser && $block->mRangeStart == $block->mRangeEnd) { $wgUser->addWatch(Title::makeTitle(NS_USER, $this->BlockAddress)); } # Block constructor sanitizes certain block options on insert $this->BlockEmail = $block->mBlockEmail; $this->BlockEnableAutoblock = $block->mEnableAutoblock; # Prepare log parameters $logParams = array(); $logParams[] = $expirestr; $logParams[] = $this->blockLogFlags(); # Make log entry, if the name is hidden, put it in the oversight log $log_type = $suppressLog ? 'suppress' : 'block'; $log = new LogPage($log_type); $log->addEntry($log_action, Title::makeTitle(NS_USER, $this->BlockAddress), $reasonstr, $logParams); # Report to the user return array(); } else { return array('hookaborted'); } }
/** * Backend block code. * $userID and $expiry will be filled accordingly * @return array(message key, arguments) on failure, empty array on success */ function doBlock(&$userId = null, &$expiry = null) { global $wgUser, $wgSysopUserBans, $wgSysopRangeBans, $wgBlockAllowsUTEdit; $userId = 0; # Expand valid IPv6 addresses, usernames are left as is $this->BlockAddress = IP::sanitizeIP($this->BlockAddress); # isIPv4() and IPv6() are used for final validation $rxIP4 = '\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}'; $rxIP6 = '\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}:\\w{1,4}'; $rxIP = "({$rxIP4}|{$rxIP6})"; # Check for invalid specifications if (!preg_match("/^{$rxIP}\$/", $this->BlockAddress)) { $matches = array(); if (preg_match("/^({$rxIP4})\\/(\\d{1,2})\$/", $this->BlockAddress, $matches)) { # IPv4 if ($wgSysopRangeBans) { if (!IP::isIPv4($this->BlockAddress) || $matches[2] < 16 || $matches[2] > 32) { return array('ip_range_invalid'); } $this->BlockAddress = Block::normaliseRange($this->BlockAddress); } else { # Range block illegal return array('range_block_disabled'); } } else { if (preg_match("/^({$rxIP6})\\/(\\d{1,3})\$/", $this->BlockAddress, $matches)) { # IPv6 if ($wgSysopRangeBans) { if (!IP::isIPv6($this->BlockAddress) || $matches[2] < 64 || $matches[2] > 128) { return array('ip_range_invalid'); } $this->BlockAddress = Block::normaliseRange($this->BlockAddress); } else { # Range block illegal return array('range_block_disabled'); } } else { # Username block if ($wgSysopUserBans) { $user = User::newFromName($this->BlockAddress); if (!is_null($user) && $user->getId()) { # Use canonical name $userId = $user->getId(); $this->BlockAddress = $user->getName(); } else { return array('nosuchusershort', htmlspecialchars($user ? $user->getName() : $this->BlockAddress)); } } else { return array('badipaddress'); } } } } if ($wgUser->isBlocked() && $wgUser->getId() !== $userId) { return array('cant-block-while-blocked'); } $reasonstr = $this->BlockReasonList; if ($reasonstr != 'other' && $this->BlockReason != '') { // Entry from drop down menu + additional comment $reasonstr .= ': ' . $this->BlockReason; } elseif ($reasonstr == 'other') { $reasonstr = $this->BlockReason; } $expirestr = $this->BlockExpiry; if ($expirestr == 'other') { $expirestr = $this->BlockOther; } if (strlen($expirestr) == 0 || strlen($expirestr) > 50) { return array('ipb_expiry_invalid'); } if (false === ($expiry = Block::parseExpiryInput($expirestr))) { // Bad expiry. return array('ipb_expiry_invalid'); } if ($this->BlockHideName && $expiry != 'infinity') { // Bad expiry. return array('ipb_expiry_temp'); } # Create block # Note: for a user block, ipb_address is only for display purposes $block = new Block($this->BlockAddress, $userId, $wgUser->getId(), $reasonstr, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, $this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName, $this->BlockEmail, isset($this->BlockAllowUsertalk) ? $this->BlockAllowUsertalk : $wgBlockAllowsUTEdit); if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) { if (!$block->insert()) { if (!$this->BlockReblock) { return array('ipb_already_blocked'); } else { # This returns direct blocks before autoblocks/rangeblocks, since we should # be sure the user is blocked by now it should work for our purposes $currentBlock = Block::newFromDB($this->BlockAddress, $userId); if ($block->equals($currentBlock)) { return array('ipb_already_blocked'); } $currentBlock->delete(); $block->insert(); $log_action = 'reblock'; } } else { $log_action = 'block'; } wfRunHooks('BlockIpComplete', array($block, $wgUser)); if ($this->BlockWatchUser) { $wgUser->addWatch(Title::makeTitle(NS_USER, $this->BlockAddress)); } # Prepare log parameters $logParams = array(); $logParams[] = $expirestr; $logParams[] = $this->blockLogFlags(); # Make log entry, if the name is hidden, put it in the oversight log $log_type = $this->BlockHideName ? 'suppress' : 'block'; $log = new LogPage($log_type); $log->addEntry($log_action, Title::makeTitle(NS_USER, $this->BlockAddress), $reasonstr, $logParams); # Report to the user return array(); } else { return array('hookaborted'); } }