r88704 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r88703‎ | r88704 | r88705 >
Date:23:10, 23 May 2011
Author:reedy
Status:reverted (Comments)
Tags:
Comment:
* (bug 15129) CheckUser Api Module

Adding patch by SoxRed93/X!

A tonne of code duplication.. But bleh :/
Modified paths:
  • /trunk/extensions/CheckUser/CheckUser.php (modified) (history)
  • /trunk/extensions/CheckUser/CheckUser_api.php (added) (history)

Diff [purge]

Index: trunk/extensions/CheckUser/CheckUser.php
@@ -33,6 +33,9 @@
3434 'descriptionmsg' => 'checkuser-desc',
3535 );
3636
 37+$wgAutoloadClasses['CheckUserApi'] = dirname(__FILE__) . '/CheckUser_api.php';
 38+$wgAPIModules['checkuser'] = 'CheckUserApi';
 39+
3740 // New user rights
3841 // 'checkuser' right is required to query IPs/users through Special:CheckUser
3942 // 'checkuser-log' is required to view the private log of checkuser checks
Index: trunk/extensions/CheckUser/CheckUser_api.php
@@ -0,0 +1,830 @@
 2+<?php
 3+
 4+/*
 5+ * Created on Jan 14, 2009
 6+ *
 7+ * Copyright (C) 2009 Soxred93 soxred93 [-at-] gee mail [-dot-] com,
 8+ *
 9+ * This program is free software; you can redistribute it and/or modify
 10+ * it under the terms of the GNU General Public License as published by
 11+ * the Free Software Foundation; either version 2 of the License, or
 12+ * (at your option) any later version.
 13+ *
 14+ * This program is distributed in the hope that it will be useful,
 15+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 17+ * GNU General Public License for more details.
 18+ *
 19+ * You should have received a copy of the GNU General Public License along
 20+ * with this program; if not, write to the Free Software Foundation, Inc.,
 21+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 22+ * http://www.gnu.org/copyleft/gpl.html
 23+ */
 24+
 25+if ( !defined( 'MEDIAWIKI' ) ) {
 26+ // Eclipse helper - will be ignored in production
 27+ require_once ( 'ApiBase.php' );
 28+}
 29+
 30+class CheckUserApi extends ApiBase {
 31+
 32+ public function __construct( $main, $action ) {
 33+ parent :: __construct( $main, $action );
 34+ ApiBase::$messageMap['cantcheckuser'] = array( 'code' => 'cantcheckuser', 'info' => "You dont have permission to run a checkuser" );
 35+ ApiBase::$messageMap['checkuserlogfail'] = array( 'code' => 'checkuserlogfail', 'info' => "Inserting a log entry failed" );
 36+ ApiBase::$messageMap['nomatch'] = array( 'code' => 'nomatch', 'info' => "No matches found" );
 37+ ApiBase::$messageMap['nomatchedit'] = array( 'code' => 'nomatch', 'info' => "No matches found. Last edit was on $1 at $2" );
 38+ }
 39+
 40+ public function execute() {
 41+ global $wgUser;
 42+ $this->getMain()->requestWriteMode();
 43+ $params = $this->extractRequestParams();
 44+
 45+ if ( !isset( $params['reason'] ) ) {
 46+ $reason = '';
 47+ } else {
 48+ $reason = $params['reason'];
 49+ }
 50+ if ( !$wgUser->isAllowed( 'checkuser' ) ) {
 51+ $this->dieUsageMsg( array( 'cantcheckuser' ) );
 52+ }
 53+
 54+ $user = $params['user'];
 55+ $checktype = $params['type'];
 56+ $period = $params['duration'];
 57+
 58+ # An IPv4?
 59+ if ( preg_match( '#^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(/\d{1,2}|)$#', $user ) ) {
 60+ $ip = $user;
 61+ $name = '';
 62+ $xff = '';
 63+ # An IPv6?
 64+ } else if ( preg_match( '#^[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4})+(/\d{1,3}|)$#', $user ) ) {
 65+ $ip = IP::sanitizeIP( $user );
 66+ $name = '';
 67+ $xff = '';
 68+ # An IPv4 XFF string?
 69+ } else if ( preg_match( '#^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(/\d{1,2}|)/xff$#', $user, $matches ) ) {
 70+ list( $junk, $xffip, $xffbit ) = $matches;
 71+ $ip = '';
 72+ $name = '';
 73+ $xff = $xffip . $xffbit;
 74+ # An IPv6 XFF string?
 75+ } else if ( preg_match( '#^([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4})+)(/\d{1,3}|)/xff$#', $user, $matches ) ) {
 76+ list( $junk, $xffip, $xffbit ) = $matches;
 77+ $ip = '';
 78+ $name = '';
 79+ $xff = IP::sanitizeIP( $xffip ) . $xffbit;
 80+ # A user?
 81+ } else {
 82+ $ip = '';
 83+ $name = $user;
 84+ $xff = '';
 85+ }
 86+
 87+ if ( $checktype == 'subuserips' ) {
 88+ $res = $this->doUserIPsRequest( $name, $reason, $period );
 89+ } else if ( $xff && $checktype == 'subipedits' ) {
 90+ $res = $this->doIPEditsRequest( $xff, true, $reason, $period );
 91+ } else if ( $checktype == 'subipedits' ) {
 92+ $res = $this->doIPEditsRequest( $ip, false, $reason, $period );
 93+ } else if ( $xff && $checktype == 'subipusers' ) {
 94+ $res = $this->doIPUsersRequest( $xff, true, $reason, $period );
 95+ } else if ( $checktype == 'subipusers' ) {
 96+ $res = $this->doIPUsersRequest( $ip, false, $reason, $period );
 97+ } else if ( $checktype == 'subuseredits' ) {
 98+ $res = $this->doUserEditsRequest( $user, $reason, $period );
 99+ }
 100+
 101+ if ( !is_null( $res ) ) {
 102+ $this->getResult()->setIndexedTagName( $res, 'cu' );
 103+ $this->getResult()->addValue( null, $this->getModuleName(), $res );
 104+ }
 105+ }
 106+
 107+ function doUserIPsRequest( $user , $reason = '', $period = 0 ) {
 108+ global $wgTitle, $wgLang, $wgUser;
 109+
 110+ $userTitle = Title::newFromText( $user, NS_USER );
 111+ if ( !is_null( $userTitle ) ) {
 112+ // normalize the username
 113+ $user = $userTitle->getText();
 114+ }
 115+ # IPs are passed in as a blank string
 116+ if ( !$user ) {
 117+ $this->dieUsageMsg( array( 'nosuchusershort' ) );
 118+ }
 119+ # Get ID, works better than text as user may have been renamed
 120+ $user_id = User::idFromName( $user );
 121+
 122+ # If user is not IP or nonexistent
 123+ if ( !$user_id ) {
 124+ $this->dieUsageMsg( array( 'nosuchusershort' ) );
 125+ }
 126+
 127+ if ( !$this->addLogEntry( 'userips', 'user', $user, $reason, $user_id ) ) {
 128+ $this->dieUsageMsg( array( 'checkuserlogfail' ) );
 129+ }
 130+
 131+ $dbr = wfGetDB( DB_SLAVE );
 132+ $time_conds = $this->getTimeConds( $period );
 133+
 134+ # Ordering by the latest timestamp makes a small filesort on the IP list
 135+
 136+ $ret = $dbr->select(
 137+ 'cu_changes',
 138+ array( 'cuc_ip', 'cuc_ip_hex', 'MIN(cuc_timestamp) AS first', 'MAX(cuc_timestamp) AS last' ),
 139+ array( 'cuc_user' => $user_id, $time_conds ),
 140+ __METHOD__,
 141+ array(
 142+ 'ORDER BY' => 'last DESC',
 143+ 'GROUP BY' => 'cuc_ip,cuc_ip_hex',
 144+ 'LIMIT' => 5001,
 145+ 'USE INDEX' => 'cuc_user_ip_time',
 146+ )
 147+ );
 148+
 149+ if ( !$dbr->numRows( $ret ) ) {
 150+ $s = $this->noMatchesMessage( $user ) . "\n";
 151+ } else {
 152+ $blockip = SpecialPage::getTitleFor( 'blockip' );
 153+ $ips_edits = array();
 154+ $counter = 0;
 155+ foreach ( $ret as $row ) {
 156+ if ( $counter >= 5000 ) {
 157+ break;
 158+ }
 159+ $ips_edits[$row->cuc_ip] = $row->count;
 160+ $ips_first[$row->cuc_ip] = $row->first;
 161+ $ips_last[$row->cuc_ip] = $row->last;
 162+ $ips_hex[$row->cuc_ip] = $row->cuc_ip_hex;
 163+ ++$counter;
 164+ }
 165+ // Count pinging might take some time...make sure it is there
 166+ wfSuppressWarnings();
 167+ set_time_limit( 60 );
 168+ wfRestoreWarnings();
 169+
 170+ $s = array();
 171+ foreach ( $ips_edits as $ip => $edits ) {
 172+ $block = new Block();
 173+ $block->fromMaster( false ); // use slaves
 174+ $timestart = wfTimestamp( TS_MW, $ips_first[$ip] );
 175+ $timeend = wfTimestamp( TS_MW, $ips_last[$ip] );
 176+ $item = array(
 177+ 'ip' => $ip,
 178+ 'edits' => $edits,
 179+ 'timestart' => $timestart,
 180+ 'timeend' => $timeend
 181+ );
 182+ if ( $block->load( $ip, 0 ) ) {
 183+ $item['blocked'] = '';
 184+ }
 185+ $s[] = $item;
 186+ }
 187+ }
 188+
 189+ return $s;
 190+ }
 191+
 192+ function doIPUsersRequest( $ip, $xfor = false, $reason = '', $period = 0 ) {
 193+ global $wgUser, $wgLang, $wgTitle;
 194+
 195+ # Invalid IPs are passed in as a blank string
 196+ if ( !$ip ) {
 197+ $this->dieUsageMsg( array( 'badipaddress' ) );
 198+ }
 199+
 200+ $logType = 'ipusers';
 201+ if ( $xfor ) {
 202+ $logType .= '-xff';
 203+ }
 204+ # Log the check...
 205+ if ( !$this->addLogEntry( $logType, 'ip', $ip, $reason ) ) {
 206+ $this->dieUsageMsg( array( 'checkuserlogfail' ) );
 207+ }
 208+
 209+ $dbr = wfGetDB( DB_SLAVE );
 210+ $ip_conds = $dbr->makeList( $this->getIpConds( $dbr, $ip, $xfor ), LIST_AND );
 211+ $time_conds = $this->getTimeConds( $period );
 212+ $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time';
 213+ # Ordered in descent by timestamp. Can cause large filesorts on range scans.
 214+ # Check how many rows will need sorting ahead of time to see if this is too big.
 215+ if ( strpos( $ip, '/' ) !== false ) {
 216+ # Quick index check only OK if no time constraint
 217+ if ( $period ) {
 218+ $rangecount = $dbr->selectField( 'cu_changes', 'COUNT(*)',
 219+ array( $ip_conds, $time_conds ),
 220+ __METHOD__,
 221+ array( 'USE INDEX' => $index ) );
 222+ } else {
 223+ $rangecount = $dbr->estimateRowCount( 'cu_changes', '*',
 224+ array( $ip_conds ),
 225+ __METHOD__,
 226+ array( 'USE INDEX' => $index ) );
 227+ }
 228+ // Sorting might take some time...make sure it is there
 229+ wfSuppressWarnings();
 230+ set_time_limit( 120 );
 231+ wfRestoreWarnings();
 232+ }
 233+ $counter = 0;
 234+ $over_users = array();
 235+ # See what is best to do after testing the waters...
 236+ if ( isset( $rangecount ) && $rangecount > 10000 ) {
 237+ $ret = $dbr->select( 'cu_changes',
 238+ array( 'cuc_ip_hex', 'COUNT(*) AS count', 'MIN(cuc_timestamp) AS first', 'MAX(cuc_timestamp) AS last' ),
 239+ array( $ip_conds, $time_conds ),
 240+ __METHOD___,
 241+ array(
 242+ 'GROUP BY' => 'cuc_ip_hex',
 243+ 'ORDER BY' => 'cuc_ip_hex',
 244+ 'LIMIT' => 5001,
 245+ 'USE INDEX' => $index,
 246+ )
 247+ );
 248+ foreach( $ret as $row ) {
 249+ if ( $counter >= 10000 ) {
 250+ break;
 251+ }
 252+ # Convert the IP hexes into normal form
 253+ if ( strpos( $row->cuc_ip_hex, 'v6-' ) !== false ) {
 254+ $ip = substr( $row->cuc_ip_hex, 3 );
 255+ $ip = IP::HextoOctet( $ip );
 256+ } else {
 257+ $ip = long2ip( wfBaseConvert( $row->cuc_ip_hex, 16, 10, 8 ) );
 258+ }
 259+
 260+ $over_users[] = array(
 261+ 'ip' => $ip,
 262+ 'edits' => $row->count,
 263+ 'timestart' => wfTimestamp( TS_MW, $row->first ),
 264+ 'timeend' => wfTimestamp( TS_MW, $row->last )
 265+ );
 266+ ++$counter;
 267+ }
 268+
 269+ $this->getResult()->setIndexedTagName( $res, 'ou' );
 270+ $this->getResult()->addValue( null, 'overips', $over_users );
 271+
 272+ return;
 273+ } else if ( isset( $rangecount ) && !$rangecount ) {
 274+ $s = $this->noMatchesMessage( $ip );
 275+ if ( !$s ) {
 276+ $this->dieUsageMsg( array( 'nomatch' ) );
 277+ } else {
 278+ $this->dieUsageMsg( array( 'nomatchedit', $s[0], $s[1] ) );
 279+ }
 280+ return;
 281+ }
 282+
 283+ global $wgMemc;
 284+ # OK, do the real query...
 285+
 286+ $ret = $dbr->select(
 287+ 'cu_changes',
 288+ array(
 289+ 'cuc_user_text', 'cuc_timestamp', 'cuc_user', 'cuc_ip', 'cuc_agent', 'cuc_xff'
 290+ ),
 291+ array( $ip_conds, $time_conds ),
 292+ __METHOD__,
 293+ array(
 294+ 'ORDER BY' => 'cuc_timestamp DESC',
 295+ 'LIMIT' => 10000,
 296+ 'USE INDEX' => $index,
 297+ )
 298+ );
 299+
 300+ $users_first = $users_last = $users_edits = $users_ids = array();
 301+ if ( !$dbr->numRows( $ret ) ) {
 302+ $s = $this->noMatchesMessage( $ip ) . "\n";
 303+ } else {
 304+ global $wgAuth;
 305+ foreach ( $ret as $row ) {
 306+ if ( !array_key_exists( $row->cuc_user_text, $users_edits ) ) {
 307+ $users_last[$row->cuc_user_text] = $row->cuc_timestamp;
 308+ $users_edits[$row->cuc_user_text] = 0;
 309+ $users_ids[$row->cuc_user_text] = $row->cuc_user;
 310+ $users_infosets[$row->cuc_user_text] = array();
 311+ $users_agentsets[$row->cuc_user_text] = array();
 312+ }
 313+ $users_edits[$row->cuc_user_text] += 1;
 314+ $users_first[$row->cuc_user_text] = $row->cuc_timestamp;
 315+ # Treat blank or NULL xffs as empty strings
 316+ $xff = empty( $row->cuc_xff ) ? null : $row->cuc_xff;
 317+ $xff_ip_combo = array( $row->cuc_ip, $xff );
 318+ # Add this IP/XFF combo for this username if it's not already there
 319+
 320+ if ( !in_array( $xff_ip_combo, $users_infosets[$row->cuc_user_text] ) ) {
 321+ $users_infosets[$row->cuc_user_text][] = $xff_ip_combo;
 322+ }
 323+ # Add this agent string if it's not already there; 10 max.
 324+ if ( count( $users_agentsets[$row->cuc_user_text] ) < 10 ) {
 325+ if ( !in_array( $row->cuc_agent, $users_agentsets[$row->cuc_user_text] ) ) {
 326+ $users_agentsets[$row->cuc_user_text][] = $row->cuc_agent;
 327+ }
 328+ }
 329+ }
 330+
 331+ $s = array();
 332+ foreach ( $users_edits as $name => $count ) {
 333+ # Load user object
 334+ $user = User::newFromName( $name, false );
 335+ $s[$name] = array(
 336+ 'user' => $name,
 337+ 'edits' => $count,
 338+ 'timestart' => wfTimestamp( TS_MW, $users_first[$name] ),
 339+ 'timeend' => wfTimestamp( TS_MW, $users_last[$name] )
 340+ );
 341+
 342+ # Check if this user or IP is blocked. If so, give a link to the block log...
 343+ $block = new Block();
 344+ $block->fromMaster( false ); // use slaves
 345+ $ip = IP::isIPAddress( $name ) ? $name : '';
 346+ $flags = array();
 347+ if ( $block->load( $ip, $users_ids[$name] ) ) {
 348+ // Range blocked?
 349+ if ( IP::isIPAddress( $block->mAddress ) && strpos( $block->mAddress, '/' ) ) {
 350+ $s[$name]['rangeblock'] = '';
 351+ // Auto blocked?
 352+ } else if ( $block->mAuto ) {
 353+ $s[$name]['autoblock'] = '';
 354+ } else {
 355+ $s[$name]['blocked'] = '';
 356+ }
 357+ // IP that is blocked on all wikis?
 358+ } else if ( $ip === $name && $user->isBlockedGlobally( $ip ) ) {
 359+ $s[$name]['globalblock'] = '';
 360+ } else if ( self::userWasBlocked( $name ) ) {
 361+ $s[$name]['pastblocked'] = '';
 362+ }
 363+ # Show if account is local only
 364+ $authUser = $wgAuth->getUserInstance( $user );
 365+ if ( $user->getId() && $authUser->getId() === 0 ) {
 366+ $s[$name]['local'] = 'yes';
 367+ } else {
 368+ $s[$name]['local'] = 'no';
 369+ }
 370+ # Check for extra user rights...
 371+ if ( $users_ids[$name] ) {
 372+ $user = User::newFromId( $users_ids[$name] );
 373+ if ( $user->isLocked() ) {
 374+ $s[$name]['locked'] = '';
 375+ }
 376+ if ( $user->getGroups() ) {
 377+ $s[$name]['groups'] = $user->getGroups();
 378+ }
 379+ }
 380+ # Check how many accounts the user made recently?
 381+ if ( $ip ) {
 382+ $key = wfMemcKey( 'acctcreate', 'ip', $ip );
 383+ $count = intval( $wgMemc->get( $key ) );
 384+ if ( $count ) {
 385+ $s[$name]['acctcreate'] = wfMsgExt( 'checkuser-accounts', array( 'parsemag' ), $count );
 386+ }
 387+ }
 388+ # List out each IP/XFF combo for this username
 389+ for ( $i = ( count( $users_infosets[$name] ) - 1 ); $i >= 0; $i-- ) {
 390+ $set = $users_infosets[$name][$i];
 391+
 392+ $s[$name][$i]['ip'] = $set[0];
 393+
 394+ # XFF string, link to /xff search
 395+ if ( $set[1] ) {
 396+ $s[$name][$i]['xff'] = '';
 397+ # Flag our trusted proxies
 398+ list( $client, $trusted ) = efGetClientIPfromXFF( $set[1], $set[0] );
 399+
 400+ if ( $trusted ) {
 401+ $s[$name][$i]['trusted'] = yes;
 402+ }
 403+ else {
 404+ $s[$name][$i]['trusted'] = no;
 405+ }
 406+ $s[$name][$i]['client'] = $client;
 407+ }
 408+ }
 409+ # List out each agent for this username
 410+ for ( $i = ( count( $users_agentsets[$name] ) - 1 ); $i >= 0; $i-- ) {
 411+ $agent = $users_agentsets[$name][$i];
 412+ $s[$name][$i]['agent'] = $agent;
 413+ }
 414+ }
 415+ }
 416+
 417+ return $s;
 418+ }
 419+
 420+ function doIPEditsRequest( $ip, $xfor = false, $reason = '', $period = 0 ) {
 421+ global $wgUser, $wgLang, $wgTitle;
 422+
 423+ # Invalid IPs are passed in as a blank string
 424+ if ( !$ip ) {
 425+ $this->dieUsageMsg( array( 'badipaddress' ) );
 426+ return;
 427+ }
 428+
 429+ $logType = 'ipedits';
 430+ if ( $xfor ) {
 431+ $logType .= '-xff';
 432+ }
 433+ # Record check...
 434+ if ( !$this->addLogEntry( $logType, 'ip', $ip, $reason ) ) {
 435+ $this->dieUsageMsg( array( 'checkuserlogfail' ) );
 436+ }
 437+
 438+ $dbr = wfGetDB( DB_SLAVE );
 439+ $ip_conds = $dbr->makeList( $this->getIpConds( $dbr, $ip, $xfor ), LIST_AND );
 440+ $time_conds = $this->getTimeConds( $period );
 441+ $cu_changes = $dbr->tableName( 'cu_changes' );
 442+ # Ordered in descent by timestamp. Can cause large filesorts on range scans.
 443+ # Check how many rows will need sorting ahead of time to see if this is too big.
 444+ # Also, if we only show 5000, too many will be ignored as well.
 445+ $index = $xfor ? 'cuc_xff_hex_time' : 'cuc_ip_hex_time';
 446+ if ( strpos( $ip, '/' ) !== false ) {
 447+ # Quick index check only OK if no time constraint
 448+ if ( $period ) {
 449+ $rangecount = $dbr->selectField( 'cu_changes', 'COUNT(*)',
 450+ array( $ip_conds, $time_conds ),
 451+ __METHOD__,
 452+ array( 'USE INDEX' => $index ) );
 453+ } else {
 454+ $rangecount = $dbr->estimateRowCount( 'cu_changes', '*',
 455+ array( $ip_conds ),
 456+ __METHOD__,
 457+ array( 'USE INDEX' => $index ) );
 458+ }
 459+ // Sorting might take some time...make sure it is there
 460+ wfSuppressWarnings();
 461+ set_time_limit( 60 );
 462+ wfRestoreWarnings();
 463+ }
 464+ $counter = 0;
 465+ $over_ips = array();
 466+ # See what is best to do after testing the waters...
 467+ if ( isset( $rangecount ) && $rangecount > 5000 ) {
 468+ $ret = $dbr->select( 'cu_changes',
 469+ array( 'cuc_ip_hex', 'COUNT(*) AS count', 'MIN(cuc_timestamp) AS first', 'MAX(cuc_timestamp) AS last' ),
 470+ array( $ip_conds, $time_conds ),
 471+ __METHOD___,
 472+ array(
 473+ 'GROUP BY' => 'cuc_ip_hex',
 474+ 'ORDER BY' => 'cuc_ip_hex',
 475+ 'LIMIT' => 5001,
 476+ 'USE INDEX' => $index,
 477+ )
 478+ );
 479+ foreach( $ret as $row ) {
 480+ if ( $counter >= 5000 ) {
 481+ break;
 482+ }
 483+ # Convert the IP hexes into normal form
 484+ if ( strpos( $row->cuc_ip_hex, 'v6-' ) !== false ) {
 485+ $ip = substr( $row->cuc_ip_hex, 3 );
 486+ $ip = IP::HextoOctet( $ip );
 487+ } else {
 488+ $ip = long2ip( wfBaseConvert( $row->cuc_ip_hex, 16, 10, 8 ) );
 489+ }
 490+
 491+ $over_ips[] = array(
 492+ 'ip' => $ip,
 493+ 'edits' => $row->count,
 494+ 'timestart' => wfTimestamp( TS_MW, $row->first ),
 495+ 'timeend' => wfTimestamp( TS_MW, $row->last )
 496+ );
 497+ ++$counter;
 498+ }
 499+
 500+ $this->getResult()->setIndexedTagName( $res, 'oi' );
 501+ $this->getResult()->addValue( null, 'overips', $over_ips );
 502+
 503+ return;
 504+ } else if ( isset( $rangecount ) && !$rangecount ) {
 505+ $s = $this->noMatchesMessage( $ip );
 506+ if ( !$s ) {
 507+ $this->dieUsageMsg( array( 'nomatch' ) );
 508+ } else {
 509+ $this->dieUsageMsg( array( 'nomatchedit', $s[0], $s[1] ) );
 510+ }
 511+ return;
 512+ }
 513+ # OK, do the real query...
 514+ $ret = $dbr->select(
 515+ 'cu_changes',
 516+ array(
 517+ 'cuc_namespace','cuc_title', 'cuc_user', 'cuc_user_text', 'cuc_comment', 'cuc_actiontext',
 518+ 'cuc_timestamp', 'cuc_minor', 'cuc_page_id', 'cuc_type', 'cuc_this_oldid',
 519+ 'cuc_last_oldid', 'cuc_ip', 'cuc_xff','cuc_agent'
 520+ ),
 521+ array( $ip_conds, $time_conds ),
 522+ __METHOD__,
 523+ array(
 524+ 'ORDER BY' => 'cuc_timestamp DESC',
 525+ 'LIMIT' => 5001,
 526+ 'USE INDEX' => $index,
 527+ )
 528+ );
 529+
 530+ if ( !$dbr->numRows( $ret ) ) {
 531+ $s = null;
 532+ } else {
 533+ $s = array();
 534+ foreach( $ret as $row ) {
 535+ if ( $counter >= 5000 ) {
 536+ break;
 537+ }
 538+ $s[] = array(
 539+ 'ip' => $row->cuc_ip,
 540+ 'edits' => $row->count,
 541+ 'timestart' => wfTimestamp( TS_MW, $row->first ),
 542+ 'timeend' => wfTimestamp( TS_MW, $row->last )
 543+ );
 544+
 545+ ++$counter;
 546+ }
 547+ }
 548+ return $s;
 549+ }
 550+
 551+ function doUserEditsRequest( $user, $reason = '', $period = 0 ) {
 552+ global $wgUser, $wgLang, $wgTitle;
 553+
 554+ $userTitle = Title::newFromText( $user, NS_USER );
 555+ if ( !is_null( $userTitle ) ) {
 556+ // normalize the username
 557+ $user = $userTitle->getText();
 558+ }
 559+ # IPs are passed in as a blank string
 560+ if ( !$user ) {
 561+ $this->dieUsageMsg( array( 'nosuchusershort' ) );
 562+ }
 563+ # Get ID, works better than text as user may have been renamed
 564+ $user_id = User::idFromName( $user );
 565+
 566+ # If user is not IP or nonexistent
 567+ if ( !$user_id ) {
 568+ $this->dieUsageMsg( array( 'nosuchusershort' ) );
 569+ }
 570+
 571+ # Record check...
 572+ if ( !$this->addLogEntry( 'useredits', 'user', $user, $reason, $user_id ) ) {
 573+ $this->dieUsageMsg( array( 'checkuserlogfail' ) );
 574+ }
 575+
 576+ $dbr = wfGetDB( DB_SLAVE );
 577+ $user_cond = "cuc_user = '$user_id'";
 578+ $time_conds = $this->getTimeConds( $period );
 579+ $cu_changes = $dbr->tableName( 'cu_changes' );
 580+ # Ordered in descent by timestamp. Causes large filesorts if there are many edits.
 581+ # Check how many rows will need sorting ahead of time to see if this is too big.
 582+ # If it is, sort by IP,time to avoid the filesort.
 583+ if ( $period ) {
 584+ $count = $dbr->selectField( 'cu_changes', 'COUNT(*)',
 585+ array( $user_cond, $time_conds ),
 586+ __METHOD__,
 587+ array( 'USE INDEX' => 'cuc_user_ip_time' ) );
 588+ } else {
 589+ $count = $dbr->estimateRowCount( 'cu_changes', '*',
 590+ array( $user_cond, $time_conds ),
 591+ __METHOD__,
 592+ array( 'USE INDEX' => 'cuc_user_ip_time' ) );
 593+ }
 594+ # See what is best to do after testing the waters...
 595+ if ( $count > 5000 ) {
 596+ $ret = $dbr->select(
 597+ 'cu_changes',
 598+ '*',
 599+ array( $user_cond, $time_conds ),
 600+ __METHOD__,
 601+ array(
 602+ 'ORDER BY' => 'cuc_ip ASC, cuc_timestamp DESC',
 603+ 'LIMIT' => 5000,
 604+ 'USE INDEX' => 'cuc_user_ip_time'
 605+ )
 606+ );
 607+
 608+ foreach( $ret as $row ) {
 609+ $over_ips[] = array(
 610+ 'ip' => $ip,
 611+ 'edits' => $row->count,
 612+ 'timestart' => wfTimestamp( TS_MW, $row->first ),
 613+ 'timeend' => wfTimestamp( TS_MW, $row->last )
 614+ );
 615+ ++$counter;
 616+ }
 617+
 618+ $this->getResult()->setIndexedTagName( $res, 'oi' );
 619+ $this->getResult()->addValue( null, 'overips', $over_ips );
 620+
 621+ return;
 622+ }
 623+ // Sorting might take some time...make sure it is there
 624+ wfSuppressWarnings();
 625+ set_time_limit( 60 );
 626+ wfRestoreWarnings();
 627+ # OK, do the real query...
 628+ $counter = 0;
 629+
 630+ $ret = $dbr->select(
 631+ 'cu_changes',
 632+ '*',
 633+ array( $user_cond, $time_conds ),
 634+ __METHOD__,
 635+ array(
 636+ 'ORDER BY' => 'cuc_ip ASC, cuc_timestamp DESC',
 637+ 'LIMIT' => 5000,
 638+ 'USE INDEX' => 'cuc_user_ip_time'
 639+ )
 640+ );
 641+ if ( !$dbr->numRows( $ret ) ) {
 642+ $s = null;
 643+ } else {
 644+ $s = array();
 645+ foreach( $ret as $row ) {
 646+ if ( $counter >= 5000 ) {
 647+ break;
 648+ }
 649+ $s[] = array(
 650+ 'ip' => $row->cuc_ip,
 651+ 'edits' => $row->count,
 652+ 'timestart' => wfTimestamp( TS_MW, $row->first ),
 653+ 'timeend' => wfTimestamp( TS_MW, $row->last )
 654+ );
 655+
 656+ ++$counter;
 657+ }
 658+ }
 659+ return $s;
 660+ }
 661+
 662+ function getTimeConds( $period ) {
 663+ if ( !$period ) {
 664+ return "1 = 1";
 665+ }
 666+ $dbr = wfGetDB( DB_SLAVE );
 667+ $cutoff_unixtime = time() - ( $period * 24 * 3600 );
 668+ $cutoff_unixtime = $cutoff_unixtime - ( $cutoff_unixtime % 86400 );
 669+ $cutoff = $dbr->addQuotes( $dbr->timestamp( $cutoff_unixtime ) );
 670+ return "cuc_timestamp > $cutoff";
 671+ }
 672+
 673+ function addLogEntry( $logType, $targetType, $target, $reason, $targetID = 0 ) {
 674+ global $wgUser;
 675+
 676+ if ( $targetType == 'ip' ) {
 677+ list( $rangeStart, $rangeEnd ) = IP::parseRange( $target );
 678+ $targetHex = $rangeStart;
 679+ if ( $rangeStart == $rangeEnd ) {
 680+ $rangeStart = $rangeEnd = '';
 681+ }
 682+ } else {
 683+ $targetHex = $rangeStart = $rangeEnd = '';
 684+ }
 685+
 686+ $dbw = wfGetDB( DB_MASTER );
 687+ $cul_id = $dbw->nextSequenceValue( 'cu_log_cul_id_seq' );
 688+ $dbw->insert( 'cu_log',
 689+ array(
 690+ 'cul_id' => $cul_id,
 691+ 'cul_timestamp' => $dbw->timestamp(),
 692+ 'cul_user' => $wgUser->getID(),
 693+ 'cul_user_text' => $wgUser->getName(),
 694+ 'cul_reason' => $reason,
 695+ 'cul_type' => $logType,
 696+ 'cul_target_id' => $targetID,
 697+ 'cul_target_text' => $target,
 698+ 'cul_target_hex' => $targetHex,
 699+ 'cul_range_start' => $rangeStart,
 700+ 'cul_range_end' => $rangeEnd,
 701+ ), __METHOD__ );
 702+ return true;
 703+ }
 704+
 705+ protected function getIpConds( $db, $ip, $xfor = false ) {
 706+ $type = ( $xfor ) ? 'xff' : 'ip';
 707+ // IPv4 CIDR, 16-32 bits
 708+ if ( preg_match( '#^(\d+\.\d+\.\d+\.\d+)/(\d+)$#', $ip, $matches ) ) {
 709+ if ( $matches[2] < 16 || $matches[2] > 32 ) {
 710+ return array( 'cuc_' . $type . '_hex' => -1 );
 711+ }
 712+ list( $start, $end ) = IP::parseRange( $ip );
 713+ return array( 'cuc_' . $type . '_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) );
 714+ } else if ( preg_match( '#^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}/(\d+)$#', $ip, $matches ) ) {
 715+ // IPv6 CIDR, 64-128 bits
 716+ if ( $matches[1] < 64 || $matches[1] > 128 ) {
 717+ return array( 'cuc_' . $type . '_hex' => -1 );
 718+ }
 719+
 720+ list( $start, $end ) = IP::parseRange( $ip );
 721+ return array( 'cuc_' . $type . '_hex BETWEEN ' . $db->addQuotes( $start ) . ' AND ' . $db->addQuotes( $end ) );
 722+ } else if ( preg_match( '#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#', $ip ) ) {
 723+ // 32 bit IPv4
 724+ $ip_hex = IP::toHex( $ip );
 725+ return array( 'cuc_' . $type . '_hex' => $ip_hex );
 726+ } else if ( preg_match( '#^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}$#', $ip ) ) {
 727+ // 128 bit IPv6
 728+ $ip_hex = IP::toHex( $ip );
 729+ return array( 'cuc_' . $type . '_hex' => $ip_hex );
 730+ } else {
 731+ // throw away this query, incomplete IP, these don't get through the entry point anyway
 732+ return array( 'cuc_' . $type . '_hex' => -1 );
 733+ }
 734+ }
 735+
 736+ function noMatchesMessage( $userName ) {
 737+ global $wgLang;
 738+ $dbr = wfGetDB( DB_SLAVE );
 739+ $user_id = User::idFromName( $userName );
 740+ if ( $user_id ) {
 741+ $revEdit = $dbr->selectField( 'revision',
 742+ 'rev_timestamp',
 743+ array( 'rev_user' => $user_id ),
 744+ __METHOD__,
 745+ array( 'ORDER BY' => 'rev_timestamp DESC' )
 746+ );
 747+ } else {
 748+ $revEdit = $dbr->selectField( 'revision',
 749+ 'rev_timestamp',
 750+ array( 'rev_user_text' => $userName ),
 751+ __METHOD__,
 752+ array( 'ORDER BY' => 'rev_timestamp DESC' )
 753+ );
 754+ }
 755+ $logEdit = 0;
 756+ if ( $user_id ) {
 757+ $logEdit = $dbr->selectField( 'logging',
 758+ 'log_timestamp',
 759+ array( 'log_user' => $user_id ),
 760+ __METHOD__,
 761+ array( 'ORDER BY' => 'log_timestamp DESC' )
 762+ );
 763+ }
 764+ $lastEdit = max( $revEdit, $logEdit );
 765+ if ( $lastEdit ) {
 766+ $lastEditDate = wfTimestamp( TS_MW, $lastEdit );
 767+ $lastEditTime = wfTimestamp( TS_MW, $lastEdit );
 768+ return array( $lastEditDate, $lastEditTime );
 769+ }
 770+ return null;
 771+ }
 772+
 773+ static function userWasBlocked( $name ) {
 774+ $userpage = Title::makeTitle( NS_USER, $name );
 775+ return wfGetDB( DB_SLAVE )->selectField( 'logging', '1',
 776+ array( 'log_type' => 'block',
 777+ 'log_action' => 'block',
 778+ 'log_namespace' => $userpage->getNamespace(),
 779+ 'log_title' => $userpage->getDBKey() ),
 780+ __METHOD__,
 781+ array( 'USE INDEX' => 'page_time' ) );
 782+ }
 783+
 784+ public function mustBePosted() {
 785+ return true;
 786+ }
 787+
 788+ public function getAllowedParams() {
 789+ return array (
 790+ 'user' => array(
 791+ ApiBase::PARAM_TYPE => 'string',
 792+ ApiBase::PARAM_REQUIRED => true,
 793+ ),
 794+ 'type' => array(
 795+ ApiBase::PARAM_TYPE => 'string',
 796+ ApiBase::PARAM_REQUIRED => true,
 797+ ),
 798+ 'duration' => array(
 799+ ApiBase::PARAM_TYPE => 'string',
 800+ ApiBase::PARAM_REQUIRED => true,
 801+ ),
 802+ 'reason' => null,
 803+ );
 804+ }
 805+
 806+ public function getParamDescription() {
 807+ return array (
 808+ 'user' => 'The user (or IP) you want to check',
 809+ 'type' => 'The type of check you want to make (subuserips, subipedits, subipusers, or subuseredits)',
 810+ 'duration' => 'How far back you want to check',
 811+ 'reason' => 'The reason for checking',
 812+ );
 813+ }
 814+
 815+ public function getDescription() {
 816+ return array (
 817+ 'Run a CheckUser on a username or IP address'
 818+ );
 819+ }
 820+
 821+ protected function getExamples() {
 822+ return array(
 823+ 'api.php?action=checkuser&user=127.0.0.1/xff&type=subipedits&duration=all',
 824+ 'api.php?action=checkuser&user=Example&type=subuserips&duration=2_weeks',
 825+ );
 826+ }
 827+
 828+ public function getVersion() {
 829+ return __CLASS__ . ': $Id$';
 830+ }
 831+}
Property changes on: trunk/extensions/CheckUser/CheckUser_api.php
___________________________________________________________________
Added: svn:eol-style
1832 + native
Added: svn:keywords
2833 + Id

Follow-up revisions

RevisionCommit summaryAuthorDate
r88719Followup r88704, disabled module by default as untested etcreedy15:34, 24 May 2011
r88728Revert r88704, r88719: experimental CheckUser API module was checked in from ...brion17:47, 24 May 2011

Comments

#Comment by X! (talk | contribs)   10:42, 24 May 2011

This is horrible code (duplication, etc) that I'm trying to fix. I've got a plan for what to do for the entire CheckUser extension this summer, and I'm planning on completely fixing this up. If this works until then, meh.

#Comment by Reedy (talk | contribs)   11:05, 24 May 2011

I was going to look at refactoring the code once in svn.

I'm not adverse leaving it in svn, and then commenting out the api module in the loader code...

#Comment by Freakolowsky (talk | contribs)   11:59, 24 May 2011

Yetanotherx says (mediawiki.org is blocked at his school):

I've got a significant amount of work done in a branch that just needs to be updated to the latest version. I'll take care of the refactoring once I get a chance.

#Comment by Brion VIBBER (talk | contribs)   17:41, 24 May 2011

Please avoid checking in code duplication; recommend reverting.

Status & tagging log