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