Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 73 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
GlobalRenameUser | |
0.00% |
0 / 73 |
|
0.00% |
0 / 6 |
210 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
withSession | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
rename | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
12 | |||
setRenameStatuses | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
injectLocalRenameUserJobs | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getJob | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\CentralAuth\GlobalRename; |
4 | |
5 | use IDBAccessObject; |
6 | use Job; |
7 | use MediaWiki\Extension\CentralAuth\GlobalRename\LocalRenameJob\LocalRenameUserJob; |
8 | use MediaWiki\Extension\CentralAuth\User\CentralAuthAntiSpoofManager; |
9 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
10 | use MediaWiki\JobQueue\JobQueueGroupFactory; |
11 | use MediaWiki\Status\Status; |
12 | use MediaWiki\Title\Title; |
13 | use MediaWiki\User\UserIdentity; |
14 | |
15 | /** |
16 | * Rename a global user |
17 | * |
18 | * @license GPL-2.0-or-later |
19 | * @author Marius Hoch < hoo@online.de > |
20 | */ |
21 | class GlobalRenameUser { |
22 | /** |
23 | * @var UserIdentity |
24 | */ |
25 | private $performingUser; |
26 | |
27 | /** |
28 | * @var UserIdentity |
29 | */ |
30 | private $oldUser; |
31 | |
32 | /** |
33 | * @var CentralAuthUser |
34 | */ |
35 | private $oldCAUser; |
36 | |
37 | /** |
38 | * @var UserIdentity |
39 | */ |
40 | private $newUser; |
41 | |
42 | /** |
43 | * @var CentralAuthUser |
44 | */ |
45 | private $newCAUser; |
46 | |
47 | /** |
48 | * @var GlobalRenameUserStatus |
49 | */ |
50 | private $renameuserStatus; |
51 | |
52 | /** @var JobQueueGroupFactory */ |
53 | private $jobQueueGroupFactory; |
54 | |
55 | /** |
56 | * @var GlobalRenameUserDatabaseUpdates |
57 | */ |
58 | private $databaseUpdates; |
59 | |
60 | /** |
61 | * @var GlobalRenameUserLogger |
62 | */ |
63 | private $logger; |
64 | |
65 | private CentralAuthAntiSpoofManager $caAntiSpoofManager; |
66 | |
67 | /** |
68 | * @var array|null |
69 | */ |
70 | private ?array $session = null; |
71 | |
72 | /** |
73 | * @param UserIdentity $performingUser |
74 | * @param UserIdentity $oldUser |
75 | * @param CentralAuthUser $oldCAUser |
76 | * @param UserIdentity $newUser Validated (creatable!) new user |
77 | * @param CentralAuthUser $newCAUser |
78 | * @param GlobalRenameUserStatus $renameuserStatus |
79 | * @param JobQueueGroupFactory $jobQueueGroupFactory |
80 | * @param GlobalRenameUserDatabaseUpdates $databaseUpdates |
81 | * @param GlobalRenameUserLogger $logger |
82 | * @param CentralAuthAntiSpoofManager $caAntiSpoofManager |
83 | */ |
84 | public function __construct( |
85 | UserIdentity $performingUser, |
86 | UserIdentity $oldUser, |
87 | CentralAuthUser $oldCAUser, |
88 | UserIdentity $newUser, |
89 | CentralAuthUser $newCAUser, |
90 | GlobalRenameUserStatus $renameuserStatus, |
91 | JobQueueGroupFactory $jobQueueGroupFactory, |
92 | GlobalRenameUserDatabaseUpdates $databaseUpdates, |
93 | GlobalRenameUserLogger $logger, |
94 | CentralAuthAntiSpoofManager $caAntiSpoofManager |
95 | ) { |
96 | $this->performingUser = $performingUser; |
97 | $this->oldUser = $oldUser; |
98 | $this->oldCAUser = $oldCAUser; |
99 | $this->newUser = $newUser; |
100 | $this->newCAUser = $newCAUser; |
101 | $this->renameuserStatus = $renameuserStatus; |
102 | $this->jobQueueGroupFactory = $jobQueueGroupFactory; |
103 | $this->databaseUpdates = $databaseUpdates; |
104 | $this->logger = $logger; |
105 | $this->caAntiSpoofManager = $caAntiSpoofManager; |
106 | } |
107 | |
108 | /** |
109 | * Set session data to use with this rename. |
110 | * |
111 | * @param array $session |
112 | * @return GlobalRenameUser |
113 | */ |
114 | public function withSession( array $session ): GlobalRenameUser { |
115 | $this->session = $session; |
116 | return $this; |
117 | } |
118 | |
119 | /** |
120 | * Rename a global user (this assumes that the data has been verified before |
121 | * and that $newUser is being a creatable user)! |
122 | * |
123 | * @param array $options |
124 | * @return Status |
125 | */ |
126 | public function rename( array $options ) { |
127 | if ( $this->oldUser->getName() === $this->newUser->getName() ) { |
128 | return Status::newFatal( 'centralauth-rename-same-name' ); |
129 | } |
130 | |
131 | static $keepDetails = [ 'attachedMethod' => true, 'attachedTimestamp' => true ]; |
132 | |
133 | $wikisAttached = array_map( |
134 | static function ( $details ) use ( $keepDetails ) { |
135 | return array_intersect_key( $details, $keepDetails ); |
136 | }, |
137 | $this->oldCAUser->queryAttached() |
138 | ); |
139 | |
140 | $status = $this->setRenameStatuses( array_keys( $wikisAttached ) ); |
141 | if ( !$status->isOK() ) { |
142 | return $status; |
143 | } |
144 | |
145 | // Rename the user centrally and unattach the old user from all |
146 | // attached wikis. Each will be reattached as its LocalRenameUserJob |
147 | // runs. |
148 | $this->databaseUpdates->update( |
149 | $this->oldUser->getName(), |
150 | $this->newUser->getName() |
151 | ); |
152 | |
153 | // Update CA's AntiSpoof |
154 | $this->caAntiSpoofManager |
155 | ->getSpoofUser( $this->newUser->getName() ) |
156 | ->update( $this->oldUser->getName() ); |
157 | |
158 | // From this point on all code using CentralAuthUser |
159 | // needs to use the new username, except for |
160 | // the renameInProgress function. Probably. |
161 | |
162 | // Clear some caches... |
163 | $this->oldCAUser->quickInvalidateCache(); |
164 | $this->newCAUser->quickInvalidateCache(); |
165 | |
166 | // If job insertion fails, an exception will cause rollback of all DBs. |
167 | // The job will block on reading renameuser_status until this commits due to it using |
168 | // a locking read and the pending update from setRenameStatuses() above. If we end up |
169 | // rolling back, then the job will abort because the status will not be 'queued'. |
170 | $this->injectLocalRenameUserJobs( $wikisAttached, $options ); |
171 | |
172 | $this->logger->log( |
173 | $this->oldUser->getName(), |
174 | $this->newUser->getName(), |
175 | $options |
176 | ); |
177 | |
178 | return Status::newGood(); |
179 | } |
180 | |
181 | /** |
182 | * @param array $wikis |
183 | * |
184 | * @return Status |
185 | */ |
186 | private function setRenameStatuses( array $wikis ) { |
187 | $rows = []; |
188 | foreach ( $wikis as $wiki ) { |
189 | // @TODO: This shouldn't know about these column names |
190 | $rows[] = [ |
191 | 'ru_wiki' => $wiki, |
192 | 'ru_oldname' => $this->oldUser->getName(), |
193 | 'ru_newname' => $this->newUser->getName(), |
194 | 'ru_status' => 'queued' |
195 | ]; |
196 | } |
197 | |
198 | $success = $this->renameuserStatus->setStatuses( $rows ); |
199 | if ( !$success ) { |
200 | // Race condition: Another admin already started the rename! |
201 | return Status::newFatal( 'centralauth-rename-alreadyinprogress', $this->newUser->getName() ); |
202 | } |
203 | |
204 | return Status::newGood(); |
205 | } |
206 | |
207 | /** |
208 | * @param array $wikisAttached Attached wiki info |
209 | * @param array $options |
210 | */ |
211 | private function injectLocalRenameUserJobs( |
212 | array $wikisAttached, array $options |
213 | ) { |
214 | $job = $this->getJob( $options, $wikisAttached ); |
215 | $statuses = $this->renameuserStatus->getStatuses( IDBAccessObject::READ_LATEST ); |
216 | foreach ( $statuses as $wiki => $status ) { |
217 | if ( $status === 'queued' ) { |
218 | $this->jobQueueGroupFactory->makeJobQueueGroup( $wiki )->push( $job ); |
219 | break; |
220 | } |
221 | } |
222 | } |
223 | |
224 | /** |
225 | * @param array $options |
226 | * @param array $wikisAttached Attached wiki info |
227 | * |
228 | * @return Job |
229 | */ |
230 | private function getJob( array $options, array $wikisAttached ) { |
231 | $params = [ |
232 | 'from' => $this->oldUser->getName(), |
233 | 'to' => $this->newUser->getName(), |
234 | 'renamer' => $this->performingUser->getName(), |
235 | 'reattach' => $wikisAttached, |
236 | 'movepages' => $options['movepages'], |
237 | 'suppressredirects' => $options['suppressredirects'], |
238 | 'promotetoglobal' => false, |
239 | 'reason' => $options['reason'], |
240 | 'force' => isset( $options['force'] ) && $options['force'], |
241 | ]; |
242 | if ( $this->session !== null ) { |
243 | $params['session'] = $this->session; |
244 | } |
245 | |
246 | // This isn't used anywhere! |
247 | $title = Title::newFromText( 'Global rename job' ); |
248 | return new LocalRenameUserJob( $title, $params ); |
249 | } |
250 | } |