Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.58% covered (warning)
81.58%
31 / 38
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbuseLogPrivateDetails
81.58% covered (warning)
81.58%
31 / 38
50.00% covered (danger)
50.00%
1 / 2
15.23
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 mustBePosted
n/a
0 / 0
n/a
0 / 0
1
 isWriteMode
n/a
0 / 0
n/a
0 / 0
1
 needsToken
n/a
0 / 0
n/a
0 / 0
1
 execute
81.08% covered (warning)
81.08%
30 / 37
0.00% covered (danger)
0.00%
0 / 1
7.33
 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
19namespace MediaWiki\Extension\AbuseFilter\Api;
20
21use MediaWiki\Api\ApiBase;
22use MediaWiki\Api\ApiMain;
23use MediaWiki\Extension\AbuseFilter\AbuseFilterLogDetailsLookup;
24use MediaWiki\Extension\AbuseFilter\AbuseFilterPermissionManager;
25use MediaWiki\Extension\AbuseFilter\FilterLookup;
26use MediaWiki\Extension\AbuseFilter\Special\SpecialAbuseLog;
27use Wikimedia\ParamValidator\ParamValidator;
28use Wikimedia\Rdbms\IConnectionProvider;
29
30/**
31 * API module to allow accessing private details (the user's IP) from AbuseLog entries
32 *
33 * @ingroup API
34 * @ingroup Extensions
35 */
36class AbuseLogPrivateDetails extends ApiBase {
37
38    public function __construct(
39        ApiMain $main,
40        string $action,
41        private readonly AbuseFilterPermissionManager $afPermManager,
42        private readonly IConnectionProvider $dbProvider,
43        private readonly AbuseFilterLogDetailsLookup $afLogPrivateDetailsLookup,
44        private readonly FilterLookup $afFilterLookup
45    ) {
46        parent::__construct( $main, $action );
47    }
48
49    /**
50     * @codeCoverageIgnore Merely declarative
51     * @inheritDoc
52     */
53    public function mustBePosted() {
54        return true;
55    }
56
57    /**
58     * @codeCoverageIgnore Merely declarative
59     * @inheritDoc
60     */
61    public function isWriteMode() {
62        return true;
63    }
64
65    /**
66     * @codeCoverageIgnore Merely declarative
67     * @inheritDoc
68     */
69    public function needsToken() {
70        return 'csrf';
71    }
72
73    /**
74     * @inheritDoc
75     */
76    public function execute() {
77        if ( !$this->afPermManager->canSeePrivateDetails( $this->getAuthority() ) ) {
78            $this->dieWithError( 'abusefilter-log-cannot-see-privatedetails' );
79        }
80        $params = $this->extractRequestParams();
81        $logId = $params['logid'];
82        $reason = $params['reason'];
83
84        if ( !SpecialAbuseLog::checkPrivateDetailsAccessReason( $reason ) ) {
85            // Double check, in case we add some extra validation
86            $this->dieWithError( 'abusefilter-noreason' );
87        }
88
89        $ipForAbuseFilterLogStatus = $this->afLogPrivateDetailsLookup->getIPForAbuseFilterLog(
90            $this->getAuthority(), $logId
91        );
92        if ( !$ipForAbuseFilterLogStatus->isGood() ) {
93            $this->dieStatus( $ipForAbuseFilterLogStatus );
94        }
95
96        $dbr = $this->dbProvider->getReplicaDatabase();
97        $row = $dbr->newSelectQueryBuilder()
98            ->select( [ 'afl_user_text', 'afl_filter_id', 'afl_global' ] )
99            ->from( 'abuse_filter_log' )
100            ->where( [ 'afl_id' => $logId ] )
101            ->caller( __METHOD__ )
102            ->fetchRow();
103
104        if ( !$row ) {
105            $this->dieWithError( 'abusefilter-log-nonexistent' );
106        }
107
108        $ip = $ipForAbuseFilterLogStatus->getValue();
109        $filter = $this->afFilterLookup->getFilter( $row->afl_filter_id, $row->afl_global );
110
111        // Log accessing private details
112        if ( $this->getConfig()->get( 'AbuseFilterLogPrivateDetailsAccess' ) ) {
113            SpecialAbuseLog::addPrivateDetailsAccessLogEntry(
114                $logId,
115                $reason,
116                $this->getUser()
117            );
118        }
119
120        $result = [
121            'log-id' => $logId,
122            'user' => $row->afl_user_text,
123            'filter-id' => $filter->getId(),
124            'filter-description' => $filter->getName(),
125            'ip-address' => $ip !== '' ? $ip : null
126        ];
127        $this->getResult()->addValue( null, $this->getModuleName(), $result );
128    }
129
130    /**
131     * @codeCoverageIgnore Merely declarative
132     * @inheritDoc
133     */
134    public function getAllowedParams() {
135        $params = [
136            'logid' => [
137                ParamValidator::PARAM_TYPE => 'integer'
138            ],
139            'reason' => [
140                ParamValidator::PARAM_TYPE => 'string',
141            ]
142        ];
143        if ( $this->getConfig()->get( 'AbuseFilterPrivateDetailsForceReason' ) ) {
144            $params['reason'][ParamValidator::PARAM_REQUIRED] = true;
145        } else {
146            $params['reason'][ParamValidator::PARAM_DEFAULT] = '';
147        }
148        return $params;
149    }
150
151    /**
152     * @codeCoverageIgnore Merely declarative
153     * @inheritDoc
154     */
155    protected function getExamplesMessages() {
156        return [
157            'action=abuselogprivatedetails&logid=1&reason=example&token=ABC123'
158                => 'apihelp-abuselogprivatedetails-example-1'
159        ];
160    }
161}