Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.67% covered (warning)
66.67%
42 / 63
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
CheckMatch
66.67% covered (warning)
66.67%
42 / 63
50.00% covered (danger)
50.00%
1 / 2
30.00
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 execute
63.79% covered (warning)
63.79%
37 / 58
0.00% covered (danger)
0.00%
0 / 1
25.68
 getAllowedParams
n/a
0 / 0
n/a
0 / 0
1
 getExamplesMessages
n/a
0 / 0
n/a
0 / 0
1
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter\Api;
4
5use LogEventsList;
6use LogicException;
7use LogPage;
8use MediaWiki\Api\ApiBase;
9use MediaWiki\Api\ApiMain;
10use MediaWiki\Api\ApiResult;
11use MediaWiki\Extension\AbuseFilter\AbuseFilterPermissionManager;
12use MediaWiki\Extension\AbuseFilter\AbuseFilterServices;
13use MediaWiki\Extension\AbuseFilter\Parser\RuleCheckerFactory;
14use MediaWiki\Extension\AbuseFilter\Special\SpecialAbuseLog;
15use MediaWiki\Extension\AbuseFilter\VariableGenerator\VariableGeneratorFactory;
16use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
17use MediaWiki\Extension\AbuseFilter\Variables\VariablesBlobStore;
18use MediaWiki\Json\FormatJson;
19use MediaWiki\Revision\RevisionRecord;
20use RecentChange;
21use Wikimedia\ParamValidator\ParamValidator;
22
23class CheckMatch extends ApiBase {
24
25    private RuleCheckerFactory $ruleCheckerFactory;
26    private AbuseFilterPermissionManager $afPermManager;
27    private VariablesBlobStore $afVariablesBlobStore;
28    private VariableGeneratorFactory $afVariableGeneratorFactory;
29
30    public function __construct(
31        ApiMain $main,
32        string $action,
33        RuleCheckerFactory $ruleCheckerFactory,
34        AbuseFilterPermissionManager $afPermManager,
35        VariablesBlobStore $afVariablesBlobStore,
36        VariableGeneratorFactory $afVariableGeneratorFactory
37    ) {
38        parent::__construct( $main, $action );
39        $this->ruleCheckerFactory = $ruleCheckerFactory;
40        $this->afPermManager = $afPermManager;
41        $this->afVariablesBlobStore = $afVariablesBlobStore;
42        $this->afVariableGeneratorFactory = $afVariableGeneratorFactory;
43    }
44
45    /**
46     * @inheritDoc
47     */
48    public function execute() {
49        $performer = $this->getAuthority();
50        $params = $this->extractRequestParams();
51        $this->requireOnlyOneParameter( $params, 'vars', 'rcid', 'logid' );
52
53        // "Anti-DoS"
54        if ( !$this->afPermManager->canUseTestTools( $performer ) ) {
55            $this->dieWithError( 'apierror-abusefilter-canttest', 'permissiondenied' );
56        }
57
58        $vars = null;
59        if ( $params['vars'] ) {
60            $pairs = FormatJson::decode( $params['vars'], true );
61            $vars = VariableHolder::newFromArray( $pairs );
62        } elseif ( $params['rcid'] ) {
63            $rc = RecentChange::newFromId( $params['rcid'] );
64
65            if ( !$rc ) {
66                $this->dieWithError( [ 'apierror-nosuchrcid', $params['rcid'] ] );
67            }
68
69            $type = (int)$rc->getAttribute( 'rc_type' );
70            $deletedValue = $rc->getAttribute( 'rc_deleted' );
71            if (
72                (
73                    $type === RC_LOG &&
74                    !LogEventsList::userCanBitfield(
75                        $deletedValue,
76                        LogPage::SUPPRESSED_ACTION | LogPage::SUPPRESSED_USER,
77                        $performer
78                    )
79                ) || (
80                    $type !== RC_LOG &&
81                    !RevisionRecord::userCanBitfield( $deletedValue, RevisionRecord::SUPPRESSED_ALL, $performer )
82                )
83            ) {
84                // T223654 - Same check as in AbuseFilterChangesList
85                $this->dieWithError( 'apierror-permissiondenied-generic', 'deletedrc' );
86            }
87
88            $varGenerator = $this->afVariableGeneratorFactory->newRCGenerator( $rc, $this->getUser() );
89            $vars = $varGenerator->getVars();
90        } elseif ( $params['logid'] ) {
91            $row = $this->getDB()->newSelectQueryBuilder()
92                ->select( '*' )
93                ->from( 'abuse_filter_log' )
94                ->where( [ 'afl_id' => $params['logid'] ] )
95                ->caller( __METHOD__ )
96                ->fetchRow();
97
98            if ( !$row ) {
99                $this->dieWithError( [ 'apierror-abusefilter-nosuchlogid', $params['logid'] ], 'nosuchlogid' );
100            }
101
102            // TODO: Replace with dependency injection once security patch is uploaded publicly.
103            $afFilterLookup = AbuseFilterServices::getFilterLookup();
104            $privacyLevel = $afFilterLookup->getFilter( $row->afl_filter_id, $row->afl_global )
105                ->getPrivacyLevel();
106            $canSeeDetails = $this->afPermManager->canSeeLogDetailsForFilter( $performer, $privacyLevel );
107            if ( !$canSeeDetails ) {
108                $this->dieWithError( 'apierror-permissiondenied-generic', 'cannotseedetails' );
109            }
110
111            $visibility = SpecialAbuseLog::getEntryVisibilityForUser( $row, $performer, $this->afPermManager );
112            if ( $visibility !== SpecialAbuseLog::VISIBILITY_VISIBLE ) {
113                // T223654 - Same check as in SpecialAbuseLog. Both the visibility of the AbuseLog entry
114                // and the corresponding revision are checked.
115                $this->dieWithError( 'apierror-permissiondenied-generic', 'deletedabuselog' );
116            }
117
118            $vars = $this->afVariablesBlobStore->loadVarDump( $row );
119        }
120        if ( $vars === null ) {
121            // @codeCoverageIgnoreStart
122            throw new LogicException( 'Impossible.' );
123            // @codeCoverageIgnoreEnd
124        }
125
126        $ruleChecker = $this->ruleCheckerFactory->newRuleChecker( $vars );
127        if ( !$ruleChecker->checkSyntax( $params['filter'] )->isValid() ) {
128            $this->dieWithError( 'apierror-abusefilter-badsyntax', 'badsyntax' );
129        }
130
131        $result = [
132            ApiResult::META_BC_BOOLS => [ 'result' ],
133            'result' => $ruleChecker->checkConditions( $params['filter'] )->getResult(),
134        ];
135
136        $this->getResult()->addValue(
137            null,
138            $this->getModuleName(),
139            $result
140        );
141    }
142
143    /**
144     * @codeCoverageIgnore Merely declarative
145     * @inheritDoc
146     */
147    public function getAllowedParams() {
148        return [
149            'filter' => [
150                ParamValidator::PARAM_REQUIRED => true,
151            ],
152            'vars' => null,
153            'rcid' => [
154                ParamValidator::PARAM_TYPE => 'integer'
155            ],
156            'logid' => [
157                ParamValidator::PARAM_TYPE => 'integer'
158            ],
159        ];
160    }
161
162    /**
163     * @codeCoverageIgnore Merely declarative
164     * @inheritDoc
165     */
166    protected function getExamplesMessages() {
167        return [
168            'action=abusefiltercheckmatch&filter=!("autoconfirmed"%20in%20user_groups)&rcid=15'
169                => 'apihelp-abusefiltercheckmatch-example-1',
170        ];
171    }
172}