Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 161 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
SpecialGlobalRenameUser | |
0.00% |
0 / 161 |
|
0.00% |
0 / 9 |
930 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getFormFields | |
0.00% |
0 / 66 |
|
0.00% |
0 / 1 |
20 | |||
getSubpageField | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
validate | |
0.00% |
0 / 52 |
|
0.00% |
0 / 1 |
240 | |||
onSubmit | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
30 | |||
onSuccess | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\CentralAuth\Special; |
4 | |
5 | use ExtensionRegistry; |
6 | use MediaWiki\Extension\CentralAuth\CentralAuthUIService; |
7 | use MediaWiki\Extension\CentralAuth\GlobalRename\GlobalRenameDenylist; |
8 | use MediaWiki\Extension\CentralAuth\GlobalRename\GlobalRenameFactory; |
9 | use MediaWiki\Extension\CentralAuth\GlobalRename\GlobalRenameUserValidator; |
10 | use MediaWiki\Extension\CentralAuth\User\CentralAuthAntiSpoofManager; |
11 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
12 | use MediaWiki\Extension\TitleBlacklist\TitleBlacklist; |
13 | use MediaWiki\Extension\TitleBlacklist\TitleBlacklistEntry; |
14 | use MediaWiki\SpecialPage\FormSpecialPage; |
15 | use MediaWiki\Status\Status; |
16 | use MediaWiki\Title\Title; |
17 | use MediaWiki\User\User; |
18 | use MediaWiki\User\UserNameUtils; |
19 | use MediaWiki\User\UserRigorOptions; |
20 | use Message; |
21 | |
22 | class SpecialGlobalRenameUser extends FormSpecialPage { |
23 | |
24 | /** |
25 | * @var string |
26 | */ |
27 | private $newUsername; |
28 | |
29 | /** |
30 | * @var string |
31 | */ |
32 | private $oldUsername; |
33 | |
34 | /** |
35 | * @var bool |
36 | */ |
37 | private $overrideAntiSpoof = false; |
38 | |
39 | /** |
40 | * @var bool |
41 | */ |
42 | private $allowHighEditcount = false; |
43 | |
44 | /** |
45 | * @var bool |
46 | */ |
47 | private $overrideTitleBlacklist = false; |
48 | |
49 | private UserNameUtils $userNameUtils; |
50 | private CentralAuthAntiSpoofManager $caAntiSpoofManager; |
51 | |
52 | /** @var CentralAuthUIService */ |
53 | private $uiService; |
54 | |
55 | /** @var GlobalRenameDenylist */ |
56 | private $globalRenameDenylist; |
57 | |
58 | private GlobalRenameFactory $globalRenameFactory; |
59 | |
60 | /** @var GlobalRenameUserValidator */ |
61 | private $globalRenameUserValidator; |
62 | |
63 | /** |
64 | * Require confirmation if olduser has more than this many global edits |
65 | */ |
66 | private const EDITCOUNT_THRESHOLD = 100000; |
67 | |
68 | /** |
69 | * @param UserNameUtils $userNameUtils |
70 | * @param CentralAuthAntiSpoofManager $caAntiSpoofManager |
71 | * @param CentralAuthUIService $uiService |
72 | * @param GlobalRenameDenylist $globalRenameDenylist |
73 | * @param GlobalRenameFactory $globalRenameFactory |
74 | * @param GlobalRenameUserValidator $globalRenameUserValidator |
75 | */ |
76 | public function __construct( |
77 | UserNameUtils $userNameUtils, |
78 | CentralAuthAntiSpoofManager $caAntiSpoofManager, |
79 | CentralAuthUIService $uiService, |
80 | GlobalRenameDenylist $globalRenameDenylist, |
81 | GlobalRenameFactory $globalRenameFactory, |
82 | GlobalRenameUserValidator $globalRenameUserValidator |
83 | ) { |
84 | parent::__construct( 'GlobalRenameUser', 'centralauth-rename' ); |
85 | $this->userNameUtils = $userNameUtils; |
86 | $this->caAntiSpoofManager = $caAntiSpoofManager; |
87 | $this->uiService = $uiService; |
88 | $this->globalRenameDenylist = $globalRenameDenylist; |
89 | $this->globalRenameFactory = $globalRenameFactory; |
90 | $this->globalRenameUserValidator = $globalRenameUserValidator; |
91 | } |
92 | |
93 | public function doesWrites() { |
94 | return true; |
95 | } |
96 | |
97 | /** |
98 | * @param string|null $par Subpage string if one was specified |
99 | */ |
100 | public function execute( $par ) { |
101 | parent::execute( $par ); |
102 | $this->getOutput()->addModules( 'ext.centralauth.globalrenameuser' ); |
103 | $this->getOutput()->addModules( 'ext.centralauth.globaluserautocomplete' ); |
104 | $this->getOutput()->addModuleStyles( 'ext.centralauth.misc.styles' ); |
105 | } |
106 | |
107 | /** |
108 | * @return array[] |
109 | */ |
110 | public function getFormFields() { |
111 | $fields = [ |
112 | 'oldname' => [ |
113 | 'id' => 'mw-globalrenameuser-oldname', |
114 | 'name' => 'oldname', |
115 | 'label-message' => 'centralauth-rename-form-oldname', |
116 | 'type' => 'text', |
117 | 'required' => true, |
118 | 'cssclass' => 'mw-autocomplete-global-user' |
119 | ], |
120 | 'newname' => [ |
121 | 'id' => 'mw-globalrenameuser-newname', |
122 | 'name' => 'newname', |
123 | 'label-message' => 'centralauth-rename-form-newname', |
124 | 'type' => 'text', |
125 | 'required' => true |
126 | ], |
127 | 'reason' => [ |
128 | 'id' => 'mw-globalrenameuser-reason', |
129 | 'name' => 'reason', |
130 | 'label-message' => 'centralauth-rename-form-reason', |
131 | 'type' => 'text', |
132 | ], |
133 | 'movepages' => [ |
134 | 'id' => 'mw-globalrenameuser-movepages', |
135 | 'name' => 'movepages', |
136 | 'label-message' => 'centralauth-rename-form-movepages', |
137 | 'type' => 'check', |
138 | 'default' => 1, |
139 | ], |
140 | 'suppressredirects' => [ |
141 | 'id' => 'mw-globalrenameuser-suppressredirects', |
142 | 'name' => 'suppressredirects', |
143 | 'label-message' => 'centralauth-rename-form-suppressredirects', |
144 | 'type' => 'check', |
145 | ], |
146 | 'overrideantispoof' => [ |
147 | 'id' => 'mw-globalrenameuser-overrideantispoof', |
148 | 'name' => 'overrideantispoof', |
149 | 'label-message' => 'centralauth-rename-form-overrideantispoof', |
150 | 'type' => 'check' |
151 | ], |
152 | 'overridetitleblacklist' => [ |
153 | 'id' => 'mw-globalrenameuser-overridetitleblacklist', |
154 | 'name' => 'overridetitleblacklist', |
155 | 'label-message' => 'centralauth-rename-form-overridetitleblacklist', |
156 | 'type' => 'check' |
157 | ], |
158 | 'allowhigheditcount' => [ |
159 | 'name' => 'allowhigheditcount', |
160 | 'type' => 'hidden', |
161 | 'default' => '', |
162 | ] |
163 | ]; |
164 | |
165 | // Ask for confirmation if the user has more than 100k edits globally |
166 | $oldName = trim( $this->getRequest()->getText( 'oldname' ) ); |
167 | if ( $oldName !== '' ) { |
168 | $oldUser = User::newFromName( $oldName ); |
169 | if ( $oldUser ) { |
170 | $caUser = CentralAuthUser::getInstance( $oldUser ); |
171 | if ( $caUser->getGlobalEditCount() > self::EDITCOUNT_THRESHOLD ) { |
172 | $fields['allowhigheditcount'] = [ |
173 | 'id' => 'mw-globalrenameuser-allowhigheditcount', |
174 | 'label-message' => [ 'centralauth-rename-form-allowhigheditcount', |
175 | Message::numParam( self::EDITCOUNT_THRESHOLD ) ], |
176 | 'type' => 'check' |
177 | ]; |
178 | } |
179 | } |
180 | } |
181 | |
182 | return $fields; |
183 | } |
184 | |
185 | /** @inheritDoc */ |
186 | protected function getSubpageField() { |
187 | return 'oldname'; |
188 | } |
189 | |
190 | /** |
191 | * Perform validation on the user submitted data |
192 | * and check that we can perform the rename |
193 | * @param array $data |
194 | * |
195 | * @return Status |
196 | */ |
197 | public function validate( array $data ) { |
198 | $oldUser = User::newFromName( $data['oldname'] ); |
199 | if ( !$oldUser ) { |
200 | return Status::newFatal( 'centralauth-rename-doesnotexist' ); |
201 | } |
202 | |
203 | if ( $oldUser->getName() === $this->getUser()->getName() ) { |
204 | return Status::newFatal( 'centralauth-rename-cannotself' ); |
205 | } |
206 | |
207 | if ( $oldUser->isTemp() ) { |
208 | return Status::newFatal( 'centralauth-rename-badusername' ); |
209 | } |
210 | |
211 | $newUser = User::newFromName( |
212 | $data['newname'], |
213 | // match GlobalRenameFactory |
214 | UserRigorOptions::RIGOR_CREATABLE |
215 | ); |
216 | if ( !$newUser ) { |
217 | return Status::newFatal( 'centralauth-rename-badusername' ); |
218 | } |
219 | |
220 | if ( $newUser->isTemp() ) { |
221 | return Status::newFatal( 'centralauth-rename-badusername' ); |
222 | } |
223 | |
224 | if ( !$this->overrideAntiSpoof ) { |
225 | $spoofUser = $this->caAntiSpoofManager->getSpoofUser( $newUser->getName() ); |
226 | $conflicts = $this->uiService->processAntiSpoofConflicts( |
227 | $this->getContext(), |
228 | $oldUser->getName(), |
229 | $spoofUser->getConflicts() |
230 | ); |
231 | |
232 | $renamedUser = $this->caAntiSpoofManager->getOldRenamedUserName( $newUser->getName() ); |
233 | if ( $renamedUser !== null ) { |
234 | $conflicts[] = $renamedUser; |
235 | } |
236 | |
237 | if ( $conflicts ) { |
238 | return Status::newFatal( |
239 | $this->msg( 'centralauth-rename-antispoofconflicts2' ) |
240 | ->params( $this->getLanguage()->listToText( $conflicts ) ) |
241 | ->numParams( count( $conflicts ) ) |
242 | ); |
243 | } |
244 | } |
245 | |
246 | // Let the performer know that olduser's editcount is more than the |
247 | // sysadmin-intervention-threshold and do the rename only if we've received |
248 | // confirmation that they want to do it. |
249 | $caOldUser = CentralAuthUser::getInstance( $oldUser ); |
250 | if ( !$this->allowHighEditcount && |
251 | $caOldUser->getGlobalEditCount() > self::EDITCOUNT_THRESHOLD |
252 | ) { |
253 | return Status::newFatal( |
254 | $this->msg( 'centralauth-rename-globaleditcount-threshold' ) |
255 | ->numParams( self::EDITCOUNT_THRESHOLD ) |
256 | ); |
257 | } |
258 | |
259 | // Ask for confirmation if the new username matches the title blacklist. |
260 | if ( |
261 | !$this->overrideTitleBlacklist |
262 | && ExtensionRegistry::getInstance()->isLoaded( 'TitleBlacklist' ) |
263 | ) { |
264 | $titleBlacklist = TitleBlacklist::singleton()->isBlacklisted( |
265 | Title::makeTitleSafe( NS_USER, $newUser->getName() ), |
266 | 'new-account' |
267 | ); |
268 | if ( $titleBlacklist instanceof TitleBlacklistEntry ) { |
269 | return Status::newFatal( |
270 | $this->msg( 'centralauth-rename-titleblacklist-match' ) |
271 | ->params( wfEscapeWikiText( $titleBlacklist->getRegex() ) ) |
272 | ); |
273 | } |
274 | } |
275 | |
276 | // Validate rename deny list |
277 | if ( !$this->globalRenameDenylist->checkUser( $oldUser ) ) { |
278 | return Status::newFatal( 'centralauth-rename-listed-on-denylist' ); |
279 | } |
280 | |
281 | return $this->globalRenameUserValidator->validate( $oldUser, $newUser ); |
282 | } |
283 | |
284 | /** |
285 | * @param array $data |
286 | * @return Status |
287 | */ |
288 | public function onSubmit( array $data ) { |
289 | if ( $data['overrideantispoof'] ) { |
290 | $this->overrideAntiSpoof = true; |
291 | } |
292 | |
293 | if ( $data['overridetitleblacklist'] ) { |
294 | $this->overrideTitleBlacklist = true; |
295 | } |
296 | |
297 | if ( $data['allowhigheditcount'] ) { |
298 | $this->allowHighEditcount = true; |
299 | } |
300 | |
301 | $valid = $this->validate( $data ); |
302 | if ( !$valid->isOK() ) { |
303 | return $valid; |
304 | } |
305 | |
306 | // Turn old username into canonical form as CentralAuthUser:;getInstanceByName |
307 | // does not do that by default. See T343958, T343963. |
308 | $this->oldUsername = $this->userNameUtils->getCanonical( |
309 | $data['oldname'], |
310 | UserRigorOptions::RIGOR_VALID |
311 | ); |
312 | |
313 | // This isn't strictly necessary as of writing, but let's do that just in case too. |
314 | // The username should already been validated in validate(). |
315 | $this->newUsername = $this->userNameUtils->getCanonical( |
316 | $data['newname'], |
317 | UserRigorOptions::RIGOR_CREATABLE |
318 | ); |
319 | |
320 | return $this->globalRenameFactory |
321 | ->newGlobalRenameUser( |
322 | $this->getUser(), |
323 | CentralAuthUser::getInstanceByName( $this->oldUsername ), |
324 | $this->newUsername |
325 | ) |
326 | ->withSession( $this->getContext()->exportSession() ) |
327 | ->rename( $data ); |
328 | } |
329 | |
330 | public function onSuccess() { |
331 | $msg = $this->msg( 'centralauth-rename-queued' ) |
332 | ->params( $this->oldUsername, $this->newUsername ) |
333 | ->parse(); |
334 | $this->getOutput()->addHTML( $msg ); |
335 | } |
336 | |
337 | /** @inheritDoc */ |
338 | protected function getGroupName() { |
339 | return 'users'; |
340 | } |
341 | } |