Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 113
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hooks
0.00% covered (danger)
0.00%
0 / 113
0.00% covered (danger)
0.00%
0 / 8
870
0.00% covered (danger)
0.00%
0 / 1
 onRegistration
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 onGetUserPermissionsErrorsExpensive
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
56
 onTitleGetEditNotices
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 onMovePageCheckPermissions
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 testUserName
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
12
 onEditFilter
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 onPageSaveComplete
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 logFilterHitUsername
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Hooks for Title Blacklist
4 * @author Victor Vasiliev
5 * @copyright © 2007-2010 Victor Vasiliev et al
6 * @license GPL-2.0-or-later
7 */
8
9namespace MediaWiki\Extension\TitleBlacklist;
10
11use ApiMessage;
12use ApiResult;
13use ManualLogEntry;
14use MediaWiki\EditPage\EditPage;
15use MediaWiki\Hook\EditFilterHook;
16use MediaWiki\Hook\MovePageCheckPermissionsHook;
17use MediaWiki\Hook\TitleGetEditNoticesHook;
18use MediaWiki\Html\Html;
19use MediaWiki\Permissions\GrantsInfo;
20use MediaWiki\Permissions\Hook\GetUserPermissionsErrorsExpensiveHook;
21use MediaWiki\Revision\RevisionRecord;
22use MediaWiki\Status\Status;
23use MediaWiki\Storage\EditResult;
24use MediaWiki\Storage\Hook\PageSaveCompleteHook;
25use MediaWiki\Title\Title;
26use MediaWiki\User\User;
27use MediaWiki\User\UserIdentity;
28use MessageSpecifier;
29use RequestContext;
30use StatusValue;
31use WikiPage;
32
33/**
34 * Hooks for the TitleBlacklist class
35 *
36 * @ingroup Extensions
37 */
38class Hooks implements
39    EditFilterHook,
40    TitleGetEditNoticesHook,
41    MovePageCheckPermissionsHook,
42    GetUserPermissionsErrorsExpensiveHook,
43    PageSaveCompleteHook
44{
45
46    public static function onRegistration() {
47        global $wgGrantRiskGroups;
48        // Make sure the risk rating is at least 'security'. TitleBlacklist adds the
49        // tboverride-account right to the createaccount grant, which makes it possible
50        // to use it for social engineering attacks with restricted usernames.
51        if ( $wgGrantRiskGroups['createaccount'] !== GrantsInfo::RISK_INTERNAL ) {
52            $wgGrantRiskGroups['createaccount'] = GrantsInfo::RISK_SECURITY;
53        }
54    }
55
56    /**
57     * getUserPermissionsErrorsExpensive hook
58     *
59     * @param Title $title
60     * @param User $user
61     * @param string $action
62     * @param array|string|MessageSpecifier &$result
63     *
64     * @return bool
65     */
66    public function onGetUserPermissionsErrorsExpensive( $title, $user, $action, &$result ) {
67        # Some places check createpage, while others check create.
68        # As it stands, upload does createpage, but normalize both
69        # to the same action, to stop future similar bugs.
70        if ( $action === 'createpage' || $action === 'createtalk' ) {
71            $action = 'create';
72        }
73        if ( $action !== 'create' && $action !== 'edit' && $action !== 'upload' ) {
74            return true;
75        }
76        $blacklisted = TitleBlacklist::singleton()->userCannot( $title, $user, $action );
77        if ( !$blacklisted ) {
78            return true;
79        }
80
81        $errmsg = $blacklisted->getErrorMessage( 'edit' );
82        $params = [
83            $blacklisted->getRaw(),
84            $title->getFullText()
85        ];
86        ApiResult::setIndexedTagName( $params, 'param' );
87        $result = ApiMessage::create(
88            wfMessage(
89                $errmsg,
90                wfEscapeWikiText( $blacklisted->getRaw() ),
91                $title->getFullText()
92            ),
93            'titleblacklist-forbidden',
94            [
95                'message' => [
96                    'key' => $errmsg,
97                    'params' => $params,
98                ],
99                'line' => $blacklisted->getRaw(),
100                // As $errmsg usually represents a non-default message here, and ApiBase
101                // uses ->inLanguage( 'en' )->useDatabase( false ) for all messages, it will
102                // never result in useful 'info' text in the API. Try this, extra data seems
103                // to override the default.
104                'info' => 'TitleBlacklist prevents this title from being created',
105            ]
106        );
107        return false;
108    }
109
110    /**
111     * Display a notice if a user is only able to create or edit a page
112     * because they have tboverride.
113     *
114     * @param Title $title
115     * @param int $oldid
116     * @param array &$notices
117     */
118    public function onTitleGetEditNotices( $title, $oldid, &$notices ) {
119        if ( !RequestContext::getMain()->getUser()->isAllowed( 'tboverride' ) ) {
120            return;
121        }
122
123        $blacklisted = TitleBlacklist::singleton()->isBlacklisted(
124            $title,
125            $title->exists() ? 'edit' : 'create'
126        );
127        if ( !$blacklisted ) {
128            return;
129        }
130
131        $params = $blacklisted->getParams();
132        if ( isset( $params['autoconfirmed'] ) ) {
133            return;
134        }
135
136        $msg = wfMessage( 'titleblacklist-warning' );
137        $notices['titleblacklist'] = $msg->plaintextParams( $blacklisted->getRaw() )
138            ->parseAsBlock();
139    }
140
141    /**
142     * MovePageCheckPermissions hook (1.25+)
143     *
144     * @param Title $oldTitle
145     * @param Title $newTitle
146     * @param User $user
147     * @param string $reason
148     * @param Status $status
149     *
150     * @return bool
151     */
152    public function onMovePageCheckPermissions(
153        $oldTitle, $newTitle, $user, $reason, $status
154    ) {
155        $titleBlacklist = TitleBlacklist::singleton();
156        $blacklisted = $titleBlacklist->userCannot( $newTitle, $user, 'move' );
157        if ( !$blacklisted ) {
158            $blacklisted = $titleBlacklist->userCannot( $oldTitle, $user, 'edit' );
159        }
160        if ( $blacklisted instanceof TitleBlacklistEntry ) {
161            $status->fatal( ApiMessage::create( [
162                $blacklisted->getErrorMessage( 'move' ),
163                wfEscapeWikiText( $blacklisted->getRaw() ),
164                $oldTitle->getFullText(),
165                $newTitle->getFullText()
166            ] ) );
167            return false;
168        }
169
170        return true;
171    }
172
173    /**
174     * Check whether a user name is acceptable for account creation or autocreation, and explain
175     * why not if that's the case.
176     *
177     * @param string $userName
178     * @param User $creatingUser
179     * @param bool $override Should the test be skipped, if the user has sufficient privileges?
180     * @param bool $log Log blacklist hits to Special:Log
181     *
182     * @return StatusValue
183     */
184    public static function testUserName(
185        $userName, User $creatingUser, $override = true, $log = false
186    ) {
187        $title = Title::makeTitleSafe( NS_USER, $userName );
188        $blacklisted = TitleBlacklist::singleton()->userCannot( $title, $creatingUser,
189            'new-account', $override );
190        if ( !$blacklisted ) {
191            return StatusValue::newGood();
192        }
193
194        if ( $log ) {
195            self::logFilterHitUsername( $creatingUser, $title, $blacklisted->getRaw() );
196        }
197        $message = $blacklisted->getErrorMessage( 'new-account' );
198        $params = [
199            $blacklisted->getRaw(),
200            $userName,
201        ];
202        ApiResult::setIndexedTagName( $params, 'param' );
203        return StatusValue::newFatal( ApiMessage::create(
204            [ $message, wfEscapeWikiText( $blacklisted->getRaw() ), $userName ],
205            'titleblacklist-forbidden',
206            [
207                'message' => [
208                    'key' => $message,
209                    'params' => $params,
210                ],
211                'line' => $blacklisted->getRaw(),
212                // The text of the message probably isn't useful API info, so do this instead
213                'info' => 'TitleBlacklist prevents this username from being created',
214            ]
215        ) );
216    }
217
218    /**
219     * EditFilter hook
220     *
221     * @param EditPage $editor
222     * @param string $text
223     * @param string $section
224     * @param string &$error
225     * @param string $summary
226     */
227    public function onEditFilter( $editor, $text, $section, &$error, $summary ) {
228        $title = $editor->getTitle();
229
230        if ( $title->getNamespace() == NS_MEDIAWIKI && $title->getDBkey() == 'Titleblacklist' ) {
231            $blackList = TitleBlacklist::singleton();
232            $bl = TitleBlacklist::parseBlacklist( $text, 'page' );
233            $ok = $blackList->validate( $bl );
234            if ( $ok === [] ) {
235                return;
236            }
237
238            $errmsg = wfMessage( 'titleblacklist-invalid' )->numParams( count( $ok ) )->text();
239            $errlines = '* <code>' .
240                implode( "</code>\n* <code>", array_map( 'wfEscapeWikiText', $ok ) ) .
241                '</code>';
242            $error = Html::errorBox(
243                    $errmsg . "\n" . $errlines
244                ) . "\n" .
245                Html::element( 'br', [ 'clear' => 'all' ] ) . "\n";
246
247            // $error will be displayed by the edit class
248        }
249    }
250
251    /**
252     * PageSaveComplete hook
253     *
254     * @param WikiPage $wikiPage
255     * @param UserIdentity $userIdentity
256     * @param string $summary
257     * @param int $flags
258     * @param RevisionRecord $revisionRecord
259     * @param EditResult $editResult
260     */
261    public function onPageSaveComplete(
262        $wikiPage,
263        $userIdentity,
264        $summary,
265        $flags,
266        $revisionRecord,
267        $editResult
268    ) {
269        $title = $wikiPage->getTitle();
270        if ( $title->getNamespace() === NS_MEDIAWIKI && $title->getDBkey() == 'Titleblacklist' ) {
271            TitleBlacklist::singleton()->invalidate();
272        }
273    }
274
275    /**
276     * Logs the filter username hit to Special:Log if
277     * $wgTitleBlacklistLogHits is enabled.
278     *
279     * @param User $user
280     * @param Title $title
281     * @param string $entry
282     */
283    public static function logFilterHitUsername( $user, $title, $entry ) {
284        global $wgTitleBlacklistLogHits;
285        if ( $wgTitleBlacklistLogHits ) {
286            $logEntry = new ManualLogEntry( 'titleblacklist', 'hit-username' );
287            $logEntry->setPerformer( $user );
288            $logEntry->setTarget( $title );
289            $logEntry->setParameters( [
290                '4::entry' => $entry,
291            ] );
292            $logid = $logEntry->insert();
293            $logEntry->publish( $logid );
294        }
295    }
296}