Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
2.86% |
5 / 175 |
|
50.00% |
1 / 2 |
CRAP | |
0.00% |
0 / 1 |
QueryAbuseLog | |
2.86% |
5 / 175 |
|
50.00% |
1 / 2 |
2435.37 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
0.00% |
0 / 170 |
|
0.00% |
0 / 1 |
2256 | |||
getAllowedParams | n/a |
0 / 0 |
n/a |
0 / 0 |
2 | |||||
getExamplesMessages | n/a |
0 / 0 |
n/a |
0 / 0 |
1 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | */ |
18 | |
19 | namespace MediaWiki\Extension\AbuseFilter\Api; |
20 | |
21 | use ApiBase; |
22 | use ApiQuery; |
23 | use ApiQueryBase; |
24 | use InvalidArgumentException; |
25 | use MediaWiki\Extension\AbuseFilter\AbuseFilterPermissionManager; |
26 | use MediaWiki\Extension\AbuseFilter\CentralDBNotAvailableException; |
27 | use MediaWiki\Extension\AbuseFilter\Filter\FilterNotFoundException; |
28 | use MediaWiki\Extension\AbuseFilter\FilterLookup; |
29 | use MediaWiki\Extension\AbuseFilter\GlobalNameUtils; |
30 | use MediaWiki\Extension\AbuseFilter\Special\SpecialAbuseLog; |
31 | use MediaWiki\Extension\AbuseFilter\Variables\VariablesBlobStore; |
32 | use MediaWiki\Extension\AbuseFilter\Variables\VariablesManager; |
33 | use MediaWiki\Title\Title; |
34 | use MWTimestamp; |
35 | use User; |
36 | use Wikimedia\IPUtils; |
37 | use Wikimedia\ParamValidator\ParamValidator; |
38 | use Wikimedia\ParamValidator\TypeDef\IntegerDef; |
39 | |
40 | /** |
41 | * Query module to list abuse log entries. |
42 | * |
43 | * @copyright 2009 Alex Z. <mrzmanwiki AT gmail DOT com> |
44 | * Based mostly on code by Bryan Tong Minh and Roan Kattouw |
45 | * |
46 | * @ingroup API |
47 | * @ingroup Extensions |
48 | */ |
49 | class QueryAbuseLog extends ApiQueryBase { |
50 | |
51 | /** @var FilterLookup */ |
52 | private $afFilterLookup; |
53 | |
54 | /** @var AbuseFilterPermissionManager */ |
55 | private $afPermManager; |
56 | |
57 | /** @var VariablesBlobStore */ |
58 | private $afVariablesBlobStore; |
59 | |
60 | /** @var VariablesManager */ |
61 | private $afVariablesManager; |
62 | |
63 | /** |
64 | * @param ApiQuery $query |
65 | * @param string $moduleName |
66 | * @param FilterLookup $afFilterLookup |
67 | * @param AbuseFilterPermissionManager $afPermManager |
68 | * @param VariablesBlobStore $afVariablesBlobStore |
69 | * @param VariablesManager $afVariablesManager |
70 | */ |
71 | public function __construct( |
72 | ApiQuery $query, |
73 | $moduleName, |
74 | FilterLookup $afFilterLookup, |
75 | AbuseFilterPermissionManager $afPermManager, |
76 | VariablesBlobStore $afVariablesBlobStore, |
77 | VariablesManager $afVariablesManager |
78 | ) { |
79 | parent::__construct( $query, $moduleName, 'afl' ); |
80 | $this->afFilterLookup = $afFilterLookup; |
81 | $this->afPermManager = $afPermManager; |
82 | $this->afVariablesBlobStore = $afVariablesBlobStore; |
83 | $this->afVariablesManager = $afVariablesManager; |
84 | } |
85 | |
86 | /** |
87 | * @inheritDoc |
88 | */ |
89 | public function execute() { |
90 | $lookup = $this->afFilterLookup; |
91 | |
92 | // Same check as in SpecialAbuseLog |
93 | $this->checkUserRightsAny( 'abusefilter-log' ); |
94 | |
95 | $performer = $this->getAuthority(); |
96 | $params = $this->extractRequestParams(); |
97 | |
98 | $prop = array_fill_keys( $params['prop'], true ); |
99 | $fld_ids = isset( $prop['ids'] ); |
100 | $fld_filter = isset( $prop['filter'] ); |
101 | $fld_user = isset( $prop['user'] ); |
102 | $fld_title = isset( $prop['title'] ); |
103 | $fld_action = isset( $prop['action'] ); |
104 | $fld_details = isset( $prop['details'] ); |
105 | $fld_result = isset( $prop['result'] ); |
106 | $fld_timestamp = isset( $prop['timestamp'] ); |
107 | $fld_hidden = isset( $prop['hidden'] ); |
108 | $fld_revid = isset( $prop['revid'] ); |
109 | $isCentral = $this->getConfig()->get( 'AbuseFilterIsCentral' ); |
110 | $fld_wiki = $isCentral && isset( $prop['wiki'] ); |
111 | |
112 | if ( $fld_details ) { |
113 | $this->checkUserRightsAny( 'abusefilter-log-detail' ); |
114 | } |
115 | |
116 | // Map of [ [ id, global ], ... ] |
117 | $searchFilters = []; |
118 | // Match permissions for viewing events on private filters to SpecialAbuseLog (bug 42814) |
119 | // @todo Avoid code duplication with SpecialAbuseLog::showList, make it so that, if hidden |
120 | // filters are specified, we only filter them out instead of failing. |
121 | if ( $params['filter'] ) { |
122 | if ( !is_array( $params['filter'] ) ) { |
123 | $params['filter'] = [ $params['filter'] ]; |
124 | } |
125 | |
126 | $foundInvalid = false; |
127 | foreach ( $params['filter'] as $filter ) { |
128 | try { |
129 | $searchFilters[] = GlobalNameUtils::splitGlobalName( $filter ); |
130 | } catch ( InvalidArgumentException $e ) { |
131 | $foundInvalid = true; |
132 | continue; |
133 | } |
134 | } |
135 | if ( !$this->afPermManager->canViewPrivateFiltersLogs( $performer ) ) { |
136 | foreach ( $searchFilters as [ $filterID, $global ] ) { |
137 | try { |
138 | $isHidden = $lookup->getFilter( $filterID, $global )->isHidden(); |
139 | } catch ( CentralDBNotAvailableException $_ ) { |
140 | // Conservatively assume it's hidden, like in SpecialAbuseLog |
141 | $isHidden = true; |
142 | } catch ( FilterNotFoundException $_ ) { |
143 | $isHidden = false; |
144 | $foundInvalid = true; |
145 | } |
146 | if ( $isHidden ) { |
147 | $this->dieWithError( |
148 | [ 'apierror-permissiondenied', $this->msg( 'action-abusefilter-log-private' ) ] |
149 | ); |
150 | } |
151 | } |
152 | } |
153 | |
154 | if ( $foundInvalid ) { |
155 | // @todo Tell what the invalid IDs are |
156 | $this->addWarning( 'abusefilter-log-invalid-filter' ); |
157 | } |
158 | } |
159 | |
160 | $result = $this->getResult(); |
161 | |
162 | $this->addTables( 'abuse_filter_log' ); |
163 | $this->addFields( 'afl_timestamp' ); |
164 | $this->addFields( 'afl_rev_id' ); |
165 | $this->addFields( 'afl_deleted' ); |
166 | $this->addFields( 'afl_filter_id' ); |
167 | $this->addFields( 'afl_global' ); |
168 | $this->addFieldsIf( 'afl_id', $fld_ids ); |
169 | $this->addFieldsIf( 'afl_user_text', $fld_user ); |
170 | $this->addFieldsIf( [ 'afl_namespace', 'afl_title' ], $fld_title ); |
171 | $this->addFieldsIf( 'afl_action', $fld_action ); |
172 | $this->addFieldsIf( 'afl_var_dump', $fld_details ); |
173 | $this->addFieldsIf( 'afl_actions', $fld_result ); |
174 | $this->addFieldsIf( 'afl_wiki', $fld_wiki ); |
175 | |
176 | if ( $fld_filter ) { |
177 | $this->addTables( 'abuse_filter' ); |
178 | $this->addFields( 'af_public_comments' ); |
179 | |
180 | $this->addJoinConds( [ |
181 | 'abuse_filter' => [ |
182 | 'LEFT JOIN', |
183 | [ |
184 | 'af_id=afl_filter_id', |
185 | 'afl_global' => 0 |
186 | ] |
187 | ] |
188 | ] ); |
189 | } |
190 | |
191 | $this->addOption( 'LIMIT', $params['limit'] + 1 ); |
192 | |
193 | $this->addWhereIf( [ 'afl_id' => $params['logid'] ], isset( $params['logid'] ) ); |
194 | |
195 | $this->addWhereRange( 'afl_timestamp', $params['dir'], $params['start'], $params['end'] ); |
196 | |
197 | if ( isset( $params['user'] ) ) { |
198 | $u = User::newFromName( $params['user'] ); |
199 | if ( $u ) { |
200 | // Username normalisation |
201 | $params['user'] = $u->getName(); |
202 | $userId = $u->getId(); |
203 | } elseif ( IPUtils::isIPAddress( $params['user'] ) ) { |
204 | // It's an IP, sanitize it |
205 | $params['user'] = IPUtils::sanitizeIP( $params['user'] ); |
206 | $userId = 0; |
207 | } |
208 | |
209 | if ( isset( $userId ) ) { |
210 | // Only add the WHERE for user in case it's either a valid user |
211 | // (but not necessary an existing one) or an IP. |
212 | $this->addWhere( |
213 | [ |
214 | 'afl_user' => $userId, |
215 | 'afl_user_text' => $params['user'] |
216 | ] |
217 | ); |
218 | } |
219 | } |
220 | |
221 | $this->addWhereIf( [ 'afl_deleted' => 0 ], !$this->afPermManager->canSeeHiddenLogEntries( $performer ) ); |
222 | |
223 | if ( $searchFilters ) { |
224 | // @todo Avoid code duplication with SpecialAbuseLog::showList |
225 | $filterConds = [ 'local' => [], 'global' => [] ]; |
226 | foreach ( $searchFilters as $filter ) { |
227 | $isGlobal = $filter[1]; |
228 | $key = $isGlobal ? 'global' : 'local'; |
229 | $filterConds[$key][] = $filter[0]; |
230 | } |
231 | $conds = []; |
232 | if ( $filterConds['local'] ) { |
233 | $conds[] = $this->getDB()->makeList( |
234 | [ 'afl_global' => 0, 'afl_filter_id' => $filterConds['local'] ], |
235 | LIST_AND |
236 | ); |
237 | } |
238 | if ( $filterConds['global'] ) { |
239 | $conds[] = $this->getDB()->makeList( |
240 | [ 'afl_global' => 1, 'afl_filter_id' => $filterConds['global'] ], |
241 | LIST_AND |
242 | ); |
243 | } |
244 | $conds = $this->getDB()->makeList( $conds, LIST_OR ); |
245 | |
246 | $this->addWhere( $conds ); |
247 | } |
248 | |
249 | if ( isset( $params['wiki'] ) ) { |
250 | // 'wiki' won't be set if $wgAbuseFilterIsCentral = false |
251 | $this->addWhereIf( [ 'afl_wiki' => $params['wiki'] ], $isCentral ); |
252 | } |
253 | |
254 | $title = $params['title']; |
255 | if ( $title !== null ) { |
256 | $titleObj = Title::newFromText( $title ); |
257 | if ( $titleObj === null ) { |
258 | $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] ); |
259 | } |
260 | $this->addWhereFld( 'afl_namespace', $titleObj->getNamespace() ); |
261 | $this->addWhereFld( 'afl_title', $titleObj->getDBkey() ); |
262 | } |
263 | $res = $this->select( __METHOD__ ); |
264 | |
265 | $count = 0; |
266 | foreach ( $res as $row ) { |
267 | if ( ++$count > $params['limit'] ) { |
268 | // We've had enough |
269 | $ts = new MWTimestamp( $row->afl_timestamp ); |
270 | $this->setContinueEnumParameter( 'start', $ts->getTimestamp( TS_ISO_8601 ) ); |
271 | break; |
272 | } |
273 | $visibility = SpecialAbuseLog::getEntryVisibilityForUser( $row, $performer, $this->afPermManager ); |
274 | if ( $visibility !== SpecialAbuseLog::VISIBILITY_VISIBLE ) { |
275 | continue; |
276 | } |
277 | |
278 | $filterID = $row->afl_filter_id; |
279 | $global = $row->afl_global; |
280 | $fullName = GlobalNameUtils::buildGlobalName( $filterID, $global ); |
281 | $isHidden = $lookup->getFilter( $filterID, $global )->isHidden(); |
282 | $canSeeDetails = $this->afPermManager->canSeeLogDetailsForFilter( $performer, $isHidden ); |
283 | |
284 | $entry = []; |
285 | if ( $fld_ids ) { |
286 | $entry['id'] = intval( $row->afl_id ); |
287 | $entry['filter_id'] = $canSeeDetails ? $fullName : ''; |
288 | } |
289 | if ( $fld_filter ) { |
290 | if ( $global ) { |
291 | $entry['filter'] = $lookup->getFilter( $filterID, true )->getName(); |
292 | } else { |
293 | $entry['filter'] = $row->af_public_comments; |
294 | } |
295 | } |
296 | if ( $fld_user ) { |
297 | $entry['user'] = $row->afl_user_text; |
298 | } |
299 | if ( $fld_wiki ) { |
300 | $entry['wiki'] = $row->afl_wiki; |
301 | } |
302 | if ( $fld_title ) { |
303 | $title = Title::makeTitle( $row->afl_namespace, $row->afl_title ); |
304 | ApiQueryBase::addTitleInfo( $entry, $title ); |
305 | } |
306 | if ( $fld_action ) { |
307 | $entry['action'] = $row->afl_action; |
308 | } |
309 | if ( $fld_result ) { |
310 | $entry['result'] = $row->afl_actions; |
311 | } |
312 | if ( $fld_revid && $row->afl_rev_id !== null ) { |
313 | $entry['revid'] = $canSeeDetails ? (int)$row->afl_rev_id : ''; |
314 | } |
315 | if ( $fld_timestamp ) { |
316 | $ts = new MWTimestamp( $row->afl_timestamp ); |
317 | $entry['timestamp'] = $ts->getTimestamp( TS_ISO_8601 ); |
318 | } |
319 | if ( $fld_details ) { |
320 | $entry['details'] = []; |
321 | if ( $canSeeDetails ) { |
322 | $vars = $this->afVariablesBlobStore->loadVarDump( $row->afl_var_dump ); |
323 | $varManager = $this->afVariablesManager; |
324 | $entry['details'] = $varManager->exportAllVars( $vars ); |
325 | } |
326 | } |
327 | |
328 | if ( $fld_hidden ) { |
329 | $entry['hidden'] = (bool)$row->afl_deleted; |
330 | } |
331 | |
332 | if ( $entry ) { |
333 | $fit = $result->addValue( [ 'query', $this->getModuleName() ], null, $entry ); |
334 | if ( !$fit ) { |
335 | $ts = new MWTimestamp( $row->afl_timestamp ); |
336 | $this->setContinueEnumParameter( 'start', $ts->getTimestamp( TS_ISO_8601 ) ); |
337 | break; |
338 | } |
339 | } |
340 | } |
341 | $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'item' ); |
342 | } |
343 | |
344 | /** |
345 | * @codeCoverageIgnore Merely declarative |
346 | * @inheritDoc |
347 | */ |
348 | public function getAllowedParams() { |
349 | $params = [ |
350 | 'logid' => [ |
351 | ParamValidator::PARAM_TYPE => 'integer' |
352 | ], |
353 | 'start' => [ |
354 | ParamValidator::PARAM_TYPE => 'timestamp' |
355 | ], |
356 | 'end' => [ |
357 | ParamValidator::PARAM_TYPE => 'timestamp' |
358 | ], |
359 | 'dir' => [ |
360 | ParamValidator::PARAM_TYPE => [ |
361 | 'newer', |
362 | 'older' |
363 | ], |
364 | ParamValidator::PARAM_DEFAULT => 'older', |
365 | ApiBase::PARAM_HELP_MSG => 'api-help-param-direction', |
366 | ], |
367 | 'user' => null, |
368 | 'title' => null, |
369 | 'filter' => [ |
370 | ParamValidator::PARAM_TYPE => 'string', |
371 | ParamValidator::PARAM_ISMULTI => true, |
372 | ApiBase::PARAM_HELP_MSG => [ |
373 | 'apihelp-query+abuselog-param-filter', |
374 | GlobalNameUtils::GLOBAL_FILTER_PREFIX |
375 | ] |
376 | ], |
377 | 'limit' => [ |
378 | ParamValidator::PARAM_DEFAULT => 10, |
379 | ParamValidator::PARAM_TYPE => 'limit', |
380 | IntegerDef::PARAM_MIN => 1, |
381 | IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1, |
382 | IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2 |
383 | ], |
384 | 'prop' => [ |
385 | ParamValidator::PARAM_DEFAULT => 'ids|user|title|action|result|timestamp|hidden|revid', |
386 | ParamValidator::PARAM_TYPE => [ |
387 | 'ids', |
388 | 'filter', |
389 | 'user', |
390 | 'title', |
391 | 'action', |
392 | 'details', |
393 | 'result', |
394 | 'timestamp', |
395 | 'hidden', |
396 | 'revid', |
397 | ], |
398 | ParamValidator::PARAM_ISMULTI => true |
399 | ] |
400 | ]; |
401 | if ( $this->getConfig()->get( 'AbuseFilterIsCentral' ) ) { |
402 | $params['wiki'] = [ |
403 | ParamValidator::PARAM_TYPE => 'string', |
404 | ]; |
405 | $params['prop'][ParamValidator::PARAM_DEFAULT] .= '|wiki'; |
406 | $params['prop'][ParamValidator::PARAM_TYPE][] = 'wiki'; |
407 | $params['filter'][ApiBase::PARAM_HELP_MSG] = 'apihelp-query+abuselog-param-filter-central'; |
408 | } |
409 | return $params; |
410 | } |
411 | |
412 | /** |
413 | * @codeCoverageIgnore Merely declarative |
414 | * @inheritDoc |
415 | */ |
416 | protected function getExamplesMessages() { |
417 | return [ |
418 | 'action=query&list=abuselog' |
419 | => 'apihelp-query+abuselog-example-1', |
420 | 'action=query&list=abuselog&afltitle=API' |
421 | => 'apihelp-query+abuselog-example-2', |
422 | ]; |
423 | } |
424 | } |