Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 113 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
0.00% |
0 / 113 |
|
0.00% |
0 / 8 |
870 | |
0.00% |
0 / 1 |
onRegistration | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
onGetUserPermissionsErrorsExpensive | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
56 | |||
onTitleGetEditNotices | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
onMovePageCheckPermissions | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
testUserName | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
12 | |||
onEditFilter | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
onPageSaveComplete | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
logFilterHitUsername | |
0.00% |
0 / 10 |
|
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 | |
9 | namespace MediaWiki\Extension\TitleBlacklist; |
10 | |
11 | use ApiMessage; |
12 | use ApiResult; |
13 | use ManualLogEntry; |
14 | use MediaWiki\EditPage\EditPage; |
15 | use MediaWiki\Hook\EditFilterHook; |
16 | use MediaWiki\Hook\MovePageCheckPermissionsHook; |
17 | use MediaWiki\Hook\TitleGetEditNoticesHook; |
18 | use MediaWiki\Html\Html; |
19 | use MediaWiki\Permissions\GrantsInfo; |
20 | use MediaWiki\Permissions\Hook\GetUserPermissionsErrorsExpensiveHook; |
21 | use MediaWiki\Revision\RevisionRecord; |
22 | use MediaWiki\Status\Status; |
23 | use MediaWiki\Storage\EditResult; |
24 | use MediaWiki\Storage\Hook\PageSaveCompleteHook; |
25 | use MediaWiki\Title\Title; |
26 | use MediaWiki\User\User; |
27 | use MediaWiki\User\UserIdentity; |
28 | use MessageSpecifier; |
29 | use RequestContext; |
30 | use StatusValue; |
31 | use WikiPage; |
32 | |
33 | /** |
34 | * Hooks for the TitleBlacklist class |
35 | * |
36 | * @ingroup Extensions |
37 | */ |
38 | class 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 | } |