summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'CheckUser/includes/CheckUserHooks.php')
-rw-r--r--CheckUser/includes/CheckUserHooks.php280
1 files changed, 211 insertions, 69 deletions
diff --git a/CheckUser/includes/CheckUserHooks.php b/CheckUser/includes/CheckUserHooks.php
index 9a8b4256..4facc308 100644
--- a/CheckUser/includes/CheckUserHooks.php
+++ b/CheckUser/includes/CheckUserHooks.php
@@ -1,9 +1,68 @@
<?php
-use MediaWiki\MediaWikiServices;
use MediaWiki\Auth\AuthenticationResponse;
+use MediaWiki\Block\DatabaseBlock;
+use MediaWiki\CheckUser\SpecialInvestigate;
+use MediaWiki\CheckUser\SpecialInvestigateBlock;
+use MediaWiki\CheckUser\SpecialInvestigateLog;
+use MediaWiki\MediaWikiServices;
+use Wikimedia\IPUtils;
+use Wikimedia\Rdbms\IDatabase;
class CheckUserHooks {
+
+ /**
+ * The maximum number of bytes that fit in CheckUser's text fields
+ * (cuc_agent,cuc_actiontext,cuc_comment,cuc_xff)
+ */
+ private const TEXT_FIELD_LENGTH = 255;
+
+ /**
+ * @param array &$list
+ * @return bool
+ */
+ public static function onSpecialPage_initList( &$list ) {
+ global $wgCheckUserEnableSpecialInvestigate;
+
+ if ( $wgCheckUserEnableSpecialInvestigate ) {
+ $list['Investigate'] = [
+ 'class' => SpecialInvestigate::class,
+ 'services' => [
+ 'LinkRenderer',
+ 'ContentLanguage',
+ 'UserOptionsManager',
+ 'CheckUserPreliminaryCheckPagerFactory',
+ 'CheckUserComparePagerFactory',
+ 'CheckUserTimelinePagerFactory',
+ 'CheckUserTokenQueryManager',
+ 'CheckUserDurationManager',
+ 'CheckUserEventLogger',
+ 'CheckUserGuidedTourLauncher',
+ 'CheckUserHookRunner',
+ ],
+ ];
+
+ $list['InvestigateLog'] = [
+ 'class' => SpecialInvestigateLog::class,
+ 'services' => [
+ 'CheckUserInvestigateLogPagerFactory',
+ ],
+ ];
+
+ $list['InvestigateBlock'] = [
+ 'class' => SpecialInvestigateBlock::class,
+ 'services' => [
+ 'PermissionManager',
+ 'TitleFormatter',
+ 'UserFactory',
+ 'CheckUserEventLogger',
+ ]
+ ];
+ }
+
+ return true;
+ }
+
/**
* Hook function for RecentChange_save
* Saves user data into the cu_changes table
@@ -13,14 +72,14 @@ class CheckUserHooks {
* @return bool
*/
public static function updateCheckUserData( RecentChange $rc ) {
- global $wgRequest;
+ global $wgRequest, $wgCheckUserLogAdditionalRights;
/**
* RC_CATEGORIZE recent changes are generally triggered by other edits.
* Thus there is no reason to store checkuser data about them.
* @see https://phabricator.wikimedia.org/T125209
*/
- if ( defined( 'RC_CATEGORIZE' ) && $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
+ if ( $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
return true;
}
/**
@@ -28,7 +87,7 @@ class CheckUserHooks {
* Thus there is no reason to store checkuser data about them.
* @see https://phabricator.wikimedia.org/T125664
*/
- if ( defined( 'RC_EXTERNAL' ) && $rc->getAttribute( 'rc_type' ) == RC_EXTERNAL ) {
+ if ( $rc->getAttribute( 'rc_type' ) == RC_EXTERNAL ) {
return true;
}
@@ -45,17 +104,33 @@ class CheckUserHooks {
// BC: check if log_type and log_action exists
// If not, then $rc_comment is the actiontext and comment
if ( isset( $attribs['rc_log_type'] ) && $attribs['rc_type'] == RC_LOG ) {
+ $pm = MediaWikiServices::getInstance()->getPermissionManager();
$target = Title::makeTitle( $attribs['rc_namespace'], $attribs['rc_title'] );
$context = RequestContext::newExtraneousContext( $target );
+ $scope = $pm->addTemporaryUserRights( $context->getUser(), $wgCheckUserLogAdditionalRights );
+
$formatter = LogFormatter::newFromRow( $rc->getAttributes() );
$formatter->setContext( $context );
$actionText = $formatter->getPlainActionText();
+
+ \Wikimedia\ScopedCallback::consume( $scope );
} else {
$actionText = '';
}
- $dbw = wfGetDB( DB_MASTER );
+ $comment = $rc->getAttribute( 'rc_comment' );
+
+ $services = MediaWikiServices::getInstance();
+ $contLang = $services->getContentLanguage();
+
+ // (T199323) Truncate text fields prior to database insertion
+ // Attempting to insert too long text will cause an error in MariaDB/MySQL strict mode
+ $actionText = $contLang->truncateForDatabase( $actionText, self::TEXT_FIELD_LENGTH );
+ $agent = $contLang->truncateForDatabase( $agent, self::TEXT_FIELD_LENGTH );
+ $xff = $contLang->truncateForDatabase( $xff, self::TEXT_FIELD_LENGTH );
+ $comment = $contLang->truncateForDatabase( $comment, self::TEXT_FIELD_LENGTH );
+
$rcRow = [
'cuc_namespace' => $attribs['rc_namespace'],
'cuc_title' => $attribs['rc_title'],
@@ -63,15 +138,15 @@ class CheckUserHooks {
'cuc_user' => $attribs['rc_user'],
'cuc_user_text' => $attribs['rc_user_text'],
'cuc_actiontext' => $actionText,
- 'cuc_comment' => $rc->getAttribute( 'rc_comment' ),
+ 'cuc_comment' => $comment,
'cuc_this_oldid' => $attribs['rc_this_oldid'],
'cuc_last_oldid' => $attribs['rc_last_oldid'],
'cuc_type' => $attribs['rc_type'],
'cuc_timestamp' => $attribs['rc_timestamp'],
- 'cuc_ip' => IP::sanitizeIP( $ip ),
- 'cuc_ip_hex' => $ip ? IP::toHex( $ip ) : null,
+ 'cuc_ip' => IPUtils::sanitizeIP( $ip ),
+ 'cuc_ip_hex' => $ip ? IPUtils::toHex( $ip ) : null,
'cuc_xff' => !$isSquidOnly ? $xff : '',
- 'cuc_xff_hex' => ( $xff_ip && !$isSquidOnly ) ? IP::toHex( $xff_ip ) : null,
+ 'cuc_xff_hex' => ( $xff_ip && !$isSquidOnly ) ? IPUtils::toHex( $xff_ip ) : null,
'cuc_agent' => $agent
];
# On PG, MW unsets cur_id due to schema incompatibilites. So it may not be set!
@@ -80,6 +155,8 @@ class CheckUserHooks {
}
Hooks::run( 'CheckUserInsertForRecentChange', [ $rc, &$rcRow ] );
+
+ $dbw = $services->getDBLoadBalancer()->getConnectionRef( DB_MASTER );
$dbw->insert( 'cu_changes', $rcRow, __METHOD__ );
return true;
@@ -102,24 +179,36 @@ class CheckUserHooks {
list( $xff_ip, $isSquidOnly ) = self::getClientIPfromXFF( $xff );
// Get agent
$agent = $wgRequest->getHeader( 'User-Agent' );
- $dbw = wfGetDB( DB_MASTER );
+
+ $actionText = wfMessage( 'checkuser-reset-action', $account->getName() )
+ ->inContentLanguage()->text();
+
+ $services = MediaWikiServices::getInstance();
+ $contLang = $services->getContentLanguage();
+
+ // (T199323) Truncate comment fields prior to database insertion
+ // Attempting to insert too long text will cause an error in MariaDB/MySQL strict mode
+ $actionText = $contLang->truncateForDatabase( $actionText, self::TEXT_FIELD_LENGTH );
+ $agent = $contLang->truncateForDatabase( $agent, self::TEXT_FIELD_LENGTH );
+ $xff = $contLang->truncateForDatabase( $xff, self::TEXT_FIELD_LENGTH );
+
+ $dbw = $services->getDBLoadBalancer()->getConnectionRef( DB_MASTER );
$rcRow = [
'cuc_namespace' => NS_USER,
'cuc_title' => '',
'cuc_minor' => 0,
'cuc_user' => $user->getId(),
'cuc_user_text' => $user->getName(),
- 'cuc_actiontext' => wfMessage( 'checkuser-reset-action', $account->getName() )
- ->inContentLanguage()->text(),
+ 'cuc_actiontext' => $actionText,
'cuc_comment' => '',
'cuc_this_oldid' => 0,
'cuc_last_oldid' => 0,
'cuc_type' => RC_LOG,
'cuc_timestamp' => $dbw->timestamp( wfTimestampNow() ),
- 'cuc_ip' => IP::sanitizeIP( $ip ),
- 'cuc_ip_hex' => $ip ? IP::toHex( $ip ) : null,
+ 'cuc_ip' => IPUtils::sanitizeIP( $ip ),
+ 'cuc_ip_hex' => $ip ? IPUtils::toHex( $ip ) : null,
'cuc_xff' => !$isSquidOnly ? $xff : '',
- 'cuc_xff_hex' => ( $xff_ip && !$isSquidOnly ) ? IP::toHex( $xff_ip ) : null,
+ 'cuc_xff_hex' => ( $xff_ip && !$isSquidOnly ) ? IPUtils::toHex( $xff_ip ) : null,
'cuc_agent' => $agent
];
$dbw->insert( 'cu_changes', $rcRow, __METHOD__ );
@@ -160,24 +249,36 @@ class CheckUserHooks {
// Get agent
$agent = $wgRequest->getHeader( 'User-Agent' );
- $dbr = wfGetDB( DB_REPLICA );
+ $actionText = wfMessage( 'checkuser-email-action', $hash )->inContentLanguage()->text();
+
+ $services = MediaWikiServices::getInstance();
+ $contLang = $services->getContentLanguage();
+
+ // (T199323) Truncate text fields prior to database insertion
+ // Attempting to insert too long text will cause an error in MariaDB/MySQL strict mode
+ $actionText = $contLang->truncateForDatabase( $actionText, self::TEXT_FIELD_LENGTH );
+ $agent = $contLang->truncateForDatabase( $agent, self::TEXT_FIELD_LENGTH );
+ $xff = $contLang->truncateForDatabase( $xff, self::TEXT_FIELD_LENGTH );
+
+ $lb = $services->getDBLoadBalancer();
+ $dbr = $lb->getConnectionRef( DB_REPLICA );
+
$rcRow = [
'cuc_namespace' => NS_USER,
'cuc_title' => '',
'cuc_minor' => 0,
'cuc_user' => $userFrom->getId(),
'cuc_user_text' => $userFrom->getName(),
- 'cuc_actiontext' =>
- wfMessage( 'checkuser-email-action', $hash )->inContentLanguage()->text(),
+ 'cuc_actiontext' => $actionText,
'cuc_comment' => '',
'cuc_this_oldid' => 0,
'cuc_last_oldid' => 0,
'cuc_type' => RC_LOG,
'cuc_timestamp' => $dbr->timestamp( wfTimestampNow() ),
- 'cuc_ip' => IP::sanitizeIP( $ip ),
- 'cuc_ip_hex' => $ip ? IP::toHex( $ip ) : null,
+ 'cuc_ip' => IPUtils::sanitizeIP( $ip ),
+ 'cuc_ip_hex' => $ip ? IPUtils::toHex( $ip ) : null,
'cuc_xff' => !$isSquidOnly ? $xff : '',
- 'cuc_xff_hex' => ( $xff_ip && !$isSquidOnly ) ? IP::toHex( $xff_ip ) : null,
+ 'cuc_xff_hex' => ( $xff_ip && !$isSquidOnly ) ? IPUtils::toHex( $xff_ip ) : null,
'cuc_agent' => $agent
];
if ( trim( $wgCUPublicKey ) != '' ) {
@@ -187,8 +288,8 @@ class CheckUserHooks {
}
$fname = __METHOD__;
- DeferredUpdates::addCallableUpdate( function () use ( $rcRow, $fname ) {
- $dbw = wfGetDB( DB_MASTER );
+ DeferredUpdates::addCallableUpdate( function () use ( $lb, $rcRow, $fname ) {
+ $dbw = $lb->getConnectionRef( DB_MASTER );
$dbw->insert( 'cu_changes', $rcRow, $fname );
} );
@@ -225,7 +326,19 @@ class CheckUserHooks {
list( $xff_ip, $isSquidOnly ) = self::getClientIPfromXFF( $xff );
// Get agent
$agent = $wgRequest->getHeader( 'User-Agent' );
- $dbw = wfGetDB( DB_MASTER );
+ $services = MediaWikiServices::getInstance();
+ $contLang = $services->getContentLanguage();
+
+ $actiontext = wfMessage( $actiontext )->inContentLanguage()->text();
+
+ // (T199323) Truncate text fields prior to database insertion
+ // Attempting to insert too long text will cause an error in MariaDB/MySQL strict mode
+ $actionText = $contLang->truncateForDatabase( $actiontext, self::TEXT_FIELD_LENGTH );
+ $agent = $contLang->truncateForDatabase( $agent, self::TEXT_FIELD_LENGTH );
+ $xff = $contLang->truncateForDatabase( $xff, self::TEXT_FIELD_LENGTH );
+
+ $dbw = $services->getDBLoadBalancer()->getConnectionRef( DB_MASTER );
+
$rcRow = [
'cuc_page_id' => 0,
'cuc_namespace' => NS_USER,
@@ -233,16 +346,16 @@ class CheckUserHooks {
'cuc_minor' => 0,
'cuc_user' => $user->getId(),
'cuc_user_text' => $user->getName(),
- 'cuc_actiontext' => wfMessage( $actiontext )->inContentLanguage()->text(),
+ 'cuc_actiontext' => $actionText,
'cuc_comment' => '',
'cuc_this_oldid' => 0,
'cuc_last_oldid' => 0,
'cuc_type' => RC_LOG,
'cuc_timestamp' => $dbw->timestamp( wfTimestampNow() ),
- 'cuc_ip' => IP::sanitizeIP( $ip ),
- 'cuc_ip_hex' => $ip ? IP::toHex( $ip ) : null,
+ 'cuc_ip' => IPUtils::sanitizeIP( $ip ),
+ 'cuc_ip_hex' => $ip ? IPUtils::toHex( $ip ) : null,
'cuc_xff' => !$isSquidOnly ? $xff : '',
- 'cuc_xff_hex' => ( $xff_ip && !$isSquidOnly ) ? IP::toHex( $xff_ip ) : null,
+ 'cuc_xff_hex' => ( $xff_ip && !$isSquidOnly ) ? IPUtils::toHex( $xff_ip ) : null,
'cuc_agent' => $agent
];
$dbw->insert( 'cu_changes', $rcRow, __METHOD__ );
@@ -258,7 +371,7 @@ class CheckUserHooks {
public static function onAuthManagerLoginAuthenticateAudit(
AuthenticationResponse $ret, $user, $username
) {
- global $wgRequest, $wgCheckUserLogLogins;
+ global $wgRequest, $wgCheckUserLogLogins, $wgCheckUserLogSuccessfulBotLogins;
if ( !$wgCheckUserLogLogins ) {
return;
@@ -272,42 +385,68 @@ class CheckUserHooks {
return;
}
+ if (
+ $wgCheckUserLogSuccessfulBotLogins !== true &&
+ $ret->status === AuthenticationResponse::PASS
+ ) {
+ $userGroups = MediaWikiServices::getInstance()
+ ->getUserGroupManager()
+ ->getUserGroups( $user );
+
+ if ( in_array( 'bot', $userGroups ) ) {
+ return;
+ }
+ }
+
+ $ip = $wgRequest->getIP();
+ $xff = $wgRequest->getHeader( 'X-Forwarded-For' );
+ list( $xff_ip, $isSquidOnly ) = self::getClientIPfromXFF( $xff );
+ $agent = $wgRequest->getHeader( 'User-Agent' );
+ $userName = $user->getName();
+
if ( $ret->status === AuthenticationResponse::FAIL ) {
$msg = 'checkuser-login-failure';
+ $cuc_user = 0;
+ $cuc_user_text = $ip;
} elseif ( $ret->status === AuthenticationResponse::PASS ) {
$msg = 'checkuser-login-success';
+ $cuc_user = $user->getId();
+ $cuc_user_text = $userName;
} else {
// Abstain, Redirect, etc.
return;
}
- $ip = $wgRequest->getIP();
- $xff = $wgRequest->getHeader( 'X-Forwarded-For' );
- list( $xff_ip, $isSquidOnly ) = self::getClientIPfromXFF( $xff );
- $agent = $wgRequest->getHeader( 'User-Agent' );
- $userName = $user->getName();
$target = "[[User:$userName|$userName]]";
- $msg = wfMessage( $msg );
- $msg->params( $target );
+ $actionText = wfMessage( $msg )->params( $target )->inContentLanguage()->text();
- $dbw = wfGetDB( DB_MASTER );
+ $services = MediaWikiServices::getInstance();
+ $contLang = $services->getContentLanguage();
+
+ // (T199323) Truncate text fields prior to database insertion
+ // Attempting to insert too long text will cause an error in MariaDB/MySQL strict mode
+ $actionText = $contLang->truncateForDatabase( $actionText, self::TEXT_FIELD_LENGTH );
+ $agent = $contLang->truncateForDatabase( $agent, self::TEXT_FIELD_LENGTH );
+ $xff = $contLang->truncateForDatabase( $xff, self::TEXT_FIELD_LENGTH );
+
+ $dbw = $services->getDBLoadBalancer()->getConnectionRef( DB_MASTER );
$rcRow = [
'cuc_page_id' => 0,
'cuc_namespace' => NS_USER,
'cuc_title' => '',
'cuc_minor' => 0,
- 'cuc_user' => 0,
- 'cuc_user_text' => $ip,
- 'cuc_actiontext' => $msg->inContentLanguage()->text(),
+ 'cuc_user' => $cuc_user,
+ 'cuc_user_text' => $cuc_user_text,
+ 'cuc_actiontext' => $actionText,
'cuc_comment' => '',
'cuc_this_oldid' => 0,
'cuc_last_oldid' => 0,
'cuc_type' => RC_LOG,
'cuc_timestamp' => $dbw->timestamp( wfTimestampNow() ),
- 'cuc_ip' => IP::sanitizeIP( $ip ),
- 'cuc_ip_hex' => $ip ? IP::toHex( $ip ) : null,
+ 'cuc_ip' => IPUtils::sanitizeIP( $ip ),
+ 'cuc_ip_hex' => $ip ? IPUtils::toHex( $ip ) : null,
'cuc_xff' => !$isSquidOnly ? $xff : '',
- 'cuc_xff_hex' => ( $xff_ip && !$isSquidOnly ) ? IP::toHex( $xff_ip ) : null,
+ 'cuc_xff_hex' => ( $xff_ip && !$isSquidOnly ) ? IPUtils::toHex( $xff_ip ) : null,
'cuc_agent' => $agent
];
$dbw->insert( 'cu_changes', $rcRow, __METHOD__ );
@@ -315,16 +454,24 @@ class CheckUserHooks {
/**
* Hook function to prune data from the cu_changes table
- * @return true
*/
public static function maybePruneIPData() {
- # Every 50th edit, prune the checkuser changes table.
- if ( 0 == mt_rand( 0, 49 ) ) {
- $fname = __METHOD__;
- DeferredUpdates::addCallableUpdate( function () use ( $fname ) {
+ if ( mt_rand( 0, 9 ) != 0 ) {
+ return;
+ }
+
+ DeferredUpdates::addUpdate( new AutoCommitUpdate(
+ wfGetDB( DB_MASTER ),
+ __METHOD__,
+ function ( IDatabase $dbw, $fname ) {
global $wgCUDMaxAge;
- $dbw = wfGetDB( DB_MASTER );
+ $key = "{$dbw->getDomainID()}:PruneCheckUserData"; // per-wiki
+ $scopedLock = $dbw->getScopedLockAndFlush( $key, $fname, 1 );
+ if ( !$scopedLock ) {
+ return;
+ }
+
$encCutoff = $dbw->addQuotes( $dbw->timestamp( time() - $wgCUDMaxAge ) );
$ids = $dbw->selectFieldValues( 'cu_changes',
'cuc_id',
@@ -336,10 +483,8 @@ class CheckUserHooks {
if ( $ids ) {
$dbw->delete( 'cu_changes', [ 'cuc_id' => $ids ], $fname );
}
- } );
- }
-
- return true;
+ }
+ ) );
}
/**
@@ -366,12 +511,7 @@ class CheckUserHooks {
$ipchain = array_map( 'trim', explode( ',', $xff ) );
$ipchain = array_reverse( $ipchain );
- if ( class_exists( ProxyLookup::class ) ) { // MW 1.28+
- $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
- } else {
- // This is kind of sketch, but is good enough for back-compat
- $proxyLookup = new IP();
- }
+ $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
$client = null; // best guess of the client IP
$isSquidOnly = false; // all proxy servers where site Squid/Varnish servers?
@@ -380,7 +520,7 @@ class CheckUserHooks {
# unless the address is not sensible (e.g. private). However, prefer private
# IP addresses over proxy servers controlled by this site (more sensible).
foreach ( $ipchain as $i => $curIP ) {
- $curIP = IP::canonicalize( $curIP );
+ $curIP = IPUtils::canonicalize( $curIP );
if ( $curIP === null ) {
break; // not a valid IP address
}
@@ -391,14 +531,14 @@ class CheckUserHooks {
}
if (
isset( $ipchain[$i + 1] ) &&
- IP::isIPAddress( $ipchain[$i + 1] ) &&
+ IPUtils::isIPAddress( $ipchain[$i + 1] ) &&
(
- IP::isPublic( $ipchain[$i + 1] ) ||
+ IPUtils::isPublic( $ipchain[$i + 1] ) ||
$wgUsePrivateIPs ||
$curIsSquid // bug 48919
)
) {
- $client = IP::canonicalize( $ipchain[$i + 1] );
+ $client = IPUtils::canonicalize( $ipchain[$i + 1] );
$isSquidOnly = ( $isSquidOnly && $curIsSquid );
continue;
}
@@ -446,7 +586,7 @@ class CheckUserHooks {
// First time so populate cu_changes with recentchanges data.
// Note: We cannot completely rely on updatelog here for old entries
// as populateCheckUserTable.php doesn't check for duplicates
- $updater->addPostDatabaseUpdateMaintenance( 'PopulateCheckUserTable' );
+ $updater->addPostDatabaseUpdateMaintenance( PopulateCheckUserTable::class );
}
}
@@ -487,7 +627,9 @@ class CheckUserHooks {
) {
$user = $sp->getUser();
$linkRenderer = $sp->getLinkRenderer();
- if ( $user->isAllowed( 'checkuser' ) ) {
+ $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+
+ if ( $permissionManager->userHasRight( $user, 'checkuser' ) ) {
$links['checkuser'] = $linkRenderer->makeKnownLink(
SpecialPage::getTitleFor( 'CheckUser' ),
$sp->msg( 'checkuser-contribs' )->text(),
@@ -495,7 +637,7 @@ class CheckUserHooks {
[ 'user' => $nt->getText() ]
);
}
- if ( $user->isAllowed( 'checkuser-log' ) ) {
+ if ( $permissionManager->userHasRight( $user, 'checkuser-log' ) ) {
$links['checkuser-log'] = $linkRenderer->makeKnownLink(
SpecialPage::getTitleFor( 'CheckUserLog' ),
$sp->msg( 'checkuser-contribs-log' )->text(),
@@ -510,13 +652,13 @@ class CheckUserHooks {
/**
* Retroactively autoblocks the last IP used by the user (if it is a user)
- * blocked by this Block.
+ * blocked by this block.
*
- * @param Block $block
+ * @param DatabaseBlock $block
* @param array &$blockIds
* @return bool
*/
- public static function doRetroactiveAutoblock( Block $block, array &$blockIds ) {
+ public static function doRetroactiveAutoblock( DatabaseBlock $block, array &$blockIds ) {
$dbr = wfGetDB( DB_REPLICA );
$user = User::newFromName( (string)$block->getTarget(), false );